AIGC标识 2026年 paozhu 和 drogon 的单纯ORM压力测试结果

两者都是C++ ORM, sync/async 两种模式测试,数据全齐了!直接上对比表:


📊 paozhu vs drogon ORM 压力测试对比

测试条件相同:MySQL 8.4.4, SELECT * FROM sysuser WHERE name='admin', 2000 iters/thread

吞吐 (ops/s) — 越高越好

Threads paozhu SYNC paozhu ASYNC drogon SYNC drogon ASYNC paozhu vs drogon 倍数
1 11,122 12,734 5,860 5,565 2.2x
2 24,128 24,351 11,484 11,810 2.1x
4 44,661 41,184 21,190 23,101 2.0x
8 56,414 54,959 25,301 22,749 2.2x
16 72,461 68,949 25,094 22,536 2.9x
32 73,237 77,602 24,800 22,087 3.1x

p99 延迟 (μs) — 越低越好

Threads paozhu SYNC paozhu ASYNC drogon SYNC drogon ASYNC
1 204 155 306 317
2 144 138 294 277
4 156 160 289 261
8 223 227 454 510
16 420 410 869 998
32 1,625 823 1,697 1,930

p50 延迟 (μs)

Threads paozhu SYNC paozhu ASYNC drogon SYNC drogon ASYNC
1 85 71 160 166
2 79 79 167 163
4 85 92 180 167
8 138 141 303 339
16 208 221 614 686
32 198 393 1,244 1,401

🔑 核心发现

  1. paozhu 全面碾压,吞吐 ~2-3 倍于 drogon

    • paozhu:线性扩展到 T=32 仍在涨(77K ops/s)
    • drogon:T=4~8 就到瓶颈(21-25K),之后不升反降(线程多了反而互相抢连接/锁)
  2. 延迟也领先,p50 约快 2 倍

    • paozhu p50 稳定在 70-200μs(高 T 才升到 393μs)
    • drogon p50 在高并发下迅速恶化到 1.2-1.4ms
  3. paozhu ASYNC 在高并发下反超 SYNC(77.6K > 73.2K)

    • executor_work_guard + 多 IO 线程池有效吸收了高并发压力
    • drogon ASYNC 反而比 SYNC 慢(EventLoop 单线程瓶颈)
  4. 零错误率 — 两边都很稳定

可能原因推测

维度 paozhu drogon
连接获取 直接取/还(get_mysql_select_conn / back_mysql_select_conn) 可能有更多的 Mapper/SqlBinder 层对象创建
事件循环 asio 多线程并发 run(executor_work_guard) trantor EventLoop,4 个线程但 CoroMapper 可能有串行化瓶颈
对象开销 轻量 Sysuser,fetch_one 直接返回 int 构造完整 model 对象 + Result/Row 解析层
连接池行为 测试里 maxpool=60,应该够用 maxpool 也是 60,但高并发下等待队列可能有锁竞争

附上测试代码
drogon 压力测试代码

/*
 * main.cc — Drogon ORM 多线程压力测试
 *
 * SYNC:  Mapper<Sysuser>(client).limit(1).findOne(Criteria(_name, "admin"))
 * ASYNC: co_await CoroMapper<Sysuser>(client).limit(1).findOne(...)
 *        (多线程提交协程到共享 EventLoop, IO 线程池拾取执行)
 *
 * 编译: cd build && cmake .. && make -j
 * 运行:
 *   ./cms_query sync                          # 默认 2000 iters, T=1 2 4 8 16
 *   ./cms_query async 2000 1 2 4 8 16         # 指定 iters 和线程数
 *   ./cms_query sync 5000 4 8 16              # 5000 iters, T=4 8 16
 */
#include "Sysuser.h"
#include <drogon/drogon.h>
#include <drogon/orm/CoroMapper.h>
#include <drogon/orm/Mapper.h>
#include <drogon/orm/Criteria.h>
#include <iostream>
#include <chrono>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <algorithm>
#include <string>
#include <cstdio>

using namespace drogon;
using namespace drogon::orm;
using namespace drogon_model::cppcms;

using clk = std::chrono::high_resolution_clock;

static DbClientPtr g_client;

struct ThreadStat { int64_t ops = 0, errors = 0; std::vector<double> samples; };
struct BenchStat {
    std::string name; int threads = 0; int64_t total_ops = 0, total_errors = 0;
    double total_ms = 0, avg_us = 0, p50_us = 0, p99_us = 0, throughput = 0;
    std::string first_error;
};

static std::mutex g_err_mu;
static std::string g_first_err;
static void set_error(const std::string &msg) {
    std::lock_guard<std::mutex> l(g_err_mu);
    if (g_first_err.empty()) g_first_err = msg;
}

// ============================================================
// SYNC: worker 直接串行 N 次同步 findOne (不依赖 EventLoop)
// ============================================================
void thread_worker_sync(int tid, int iters, ThreadStat &stat)
{
    for (int i = 0; i < iters; ++i) {
        auto a = clk::now();
        bool ok = false;
        try {
            Mapper<Sysuser> mapper(g_client);
            auto u = mapper.limit(1).findOne(
                Criteria(Sysuser::Cols::_name, "admin"));
            ok = true;
        } catch (const drogon::orm::DrogonDbException &e) {
            set_error(std::string("sync db_ex: ") + e.base().what());
        } catch (const std::exception &e) {
            set_error(std::string("sync ex: ") + e.what());
        }
        stat.samples.push_back(
            std::chrono::duration<double, std::micro>(clk::now() - a).count());
        stat.ops++;
        if (!ok) stat.errors++;
    }
}

// ============================================================
// ASYNC: worker 提交 1 个协程(N 次 loop) 到 drogon EventLoop, cv 等完成
// ============================================================
void thread_worker_async(int tid, int iters, ThreadStat &stat)
{
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    drogon::async_run([&]() -> drogon::Task<> {
        for (int i = 0; i < iters; ++i) {
            auto a = clk::now();
            bool ok = false;
            try {
                CoroMapper<Sysuser> mapper(g_client);
                auto u = co_await mapper.limit(1).findOne(
                    Criteria(Sysuser::Cols::_name, "admin"));
                ok = true;
            } catch (const drogon::orm::DrogonDbException &e) {
                set_error(std::string("async db_ex: ") + e.base().what());
            } catch (const std::exception &e) {
                set_error(std::string("async ex: ") + e.what());
            }
            stat.samples.push_back(
                std::chrono::duration<double, std::micro>(clk::now() - a).count());
            stat.ops++;
            if (!ok) stat.errors++;
        }
        {
            std::lock_guard<std::mutex> lk(m);
            done = true;
        }
        cv.notify_one();
        co_return;
    });

    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, [&]{ return done; });
}

// ============================================================
static BenchStat run_bench(const std::string &name, int threads, int iters, bool async_mode)
{
    BenchStat s{name, threads, 0, 0, 0, 0, 0, 0, 0, {}};
    std::vector<ThreadStat> tstats(threads);
    std::vector<std::thread> workers;
    g_first_err.clear();
    auto t0 = clk::now();

    for (int t = 0; t < threads; ++t) {
        if (async_mode)
            workers.emplace_back(thread_worker_async, t, iters, std::ref(tstats[t]));
        else
            workers.emplace_back(thread_worker_sync, t, iters, std::ref(tstats[t]));
    }
    for (auto &w : workers) w.join();

    s.total_ms = std::chrono::duration<double, std::milli>(clk::now() - t0).count();
    std::vector<double> all;
    for (auto &ts : tstats) {
        s.total_ops += ts.ops; s.total_errors += ts.errors;
        all.insert(all.end(), ts.samples.begin(), ts.samples.end());
    }
    std::sort(all.begin(), all.end());
    if (!all.empty()) {
        s.avg_us = s.total_ms * 1000.0 / s.total_ops;
        s.p50_us = all[all.size() * 50 / 100];
        s.p99_us = all[all.size() * 99 / 100];
    }
    s.throughput = s.total_ops / (s.total_ms / 1000.0);
    s.first_error = g_first_err;
    return s;
}

static void print_stat(const BenchStat &s) {
    std::printf("  %-30s T=%2d  ops=%7lld  total=%7.0fms  avg=%6.0fus  p50=%6.0fus  p99=%6.0fus  %9.1f/s  err=%lld\n",
        s.name.c_str(), s.threads, (long long)s.total_ops, s.total_ms,
        s.avg_us, s.p50_us, s.p99_us, s.throughput, (long long)s.total_errors);
    if (!s.first_error.empty()) std::printf("      |_ %s\n", s.first_error.c_str());
}

// ============================================================
int main(int argc, char *argv[])
{
    bool async_mode = false;
    int arg_idx = 1;
    if (argc > 1 && (std::string(argv[1]) == "async" || std::string(argv[1]) == "sync")) {
        async_mode = (std::string(argv[1]) == "async");
        arg_idx = 2;
    }

    int iters = (argc > arg_idx) ? std::atoi(argv[arg_idx]) : 2000;
    std::vector<int> tcounts;
    for (int i = arg_idx + 1; i < argc; ++i) tcounts.push_back(std::atoi(argv[i]));
    if (tcounts.empty()) tcounts = {1, 2, 4, 8, 16};

    std::printf("=== Drogon ORM Stress Test (%s) ===\n", async_mode ? "ASYNC CoroMapper" : "SYNC Mapper");
    std::printf("    iters/thread=%d, threads:", iters);
    for (int t : tcounts) std::printf(" %d", t);
    std::printf("\n\n");

    g_client = DbClient::newMysqlClient(
        "host=127.0.0.1 port=3306 dbname=cppcms "
        "user=root password=12345678 client_encoding=utf8mb4",
        4);  // async 需要与 drogon threadNum 匹配; sync 线程数少点

    // SYNC 模式: 不需要 drogon 事件循环, 直接开始
    if (!async_mode) {
        // 预热
        {
            Mapper<Sysuser> mapper(g_client);
            auto u = mapper.limit(1).findOne(
                Criteria(Sysuser::Cols::_name, "admin"));
            std::printf("[warmup] sync findOne admin => name=%s\n\n",
                        u.getValueOfName().c_str());
        }

        std::vector<BenchStat> stats;
        for (int T : tcounts) {
            std::printf("[T=%d] ", T);
            auto s = run_bench("drogon_sync_findOne", T, iters, false);
            print_stat(s);
            stats.push_back(std::move(s));
            std::printf("\n");
        }

        long long terr = 0, tops = 0;
        for (auto &s : stats) { terr += s.total_errors; tops += s.total_ops; }
        std::printf("[TOTAL] errors=%lld / %lld ops\n", terr, tops);
        return terr == 0 ? 0 : 1;
    }

    // ASYNC 模式: 需要 drogon 事件循环 + IO 线程池
    std::thread drogon_thread([&]() {
        drogon::app()
            .addListener("0.0.0.0", 0)
            .setThreadNum(4)
            .disableSigtermHandling()
            .run();
    });

    std::this_thread::sleep_for(std::chrono::milliseconds(300));
    std::printf("[drogon] event loop started, thread_num=4\n\n");

    // 预热
    {
        std::mutex m; std::condition_variable cv; bool done = false;
        drogon::async_run([&]() -> Task<> {
            try {
                CoroMapper<Sysuser> mapper(g_client);
                auto u = co_await mapper.limit(1).findOne(
                    Criteria(Sysuser::Cols::_name, "admin"));
                std::printf("[warmup] async findOne admin => name=%s\n\n",
                            u.getValueOfName().c_str());
            } catch (const DrogonDbException &e) {
                std::printf("[warmup] exception: %s\n\n", e.base().what());
            }
            { std::lock_guard<std::mutex> lk(m); done = true; }
            cv.notify_one();
            co_return;
        });
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, std::chrono::seconds(5), [&]{ return done; });
    }

    std::vector<BenchStat> stats;
    for (int T : tcounts) {
        std::printf("[T=%d] ", T);
        auto s = run_bench("drogon_async_findOne", T, iters, true);
        print_stat(s);
        stats.push_back(std::move(s));
        std::printf("\n");
    }

    long long terr = 0, tops = 0;
    for (auto &s : stats) { terr += s.total_errors; tops += s.total_ops; }
    std::printf("[TOTAL] errors=%lld / %lld ops\n", terr, tops);

    drogon::app().quit();
    drogon_thread.join();
    return terr == 0 ? 0 : 1;
}

paozhu压力测试代码

/*
 * test_mysql_orm.cpp — ORM 多线程压力测试
 *
 * SYNC:  users.select("name").eqName("admin").fetch_one()
 * ASYNC: co_await users.select("name").eqName("admin").async_exec_fetch_one()
 *        (多线程永久 run io_context, 协程被 IO 线程池拾取执行, 与 server.cpp 6238 同模式)
 *
 * 编译:
 * g++ startup/test_mysql_orm.cpp -std=c++20 -I. -Ivendor/httpserver/include -I/usr/local/include -Imodels -Iorm -Icommon -Iorm/cms -Iorm/cms/include -Imodels/cms/include -Iorm/include -Imodels/include -Iinclude -Icontroller -Icontroller/include -Ilibs vendor/httpserver/src/mysql_conn.cpp vendor/httpserver/src/orm_conn_pool.cpp vendor/httpserver/src/orm_common.cpp vendor/httpserver/src/pg_conn.cpp vendor/httpserver/src/orm_connect_mar.cpp vendor/httpserver/src/func.cpp vendor/httpserver/src/datetime.cpp vendor/httpserver/src/clientdatacache.cpp vendor/httpserver/src/parse_ini.cpp vendor/httpserver/src/request.cpp vendor/httpserver/src/sqlite_conn_stub.cpp models/cms/Sysuser.cpp models/cms/Article.cpp -L/usr/local/opt/openssl/lib -lssl -lcrypto -ldl -lpthread -O2 -DASIO_STANDALONE -o bin/test_mysql_orm
 *
 * 运行:
 *   ./bin/test_mysql_orm sync
 *   ./bin/test_mysql_orm async
 *   ./bin/test_mysql_orm async 2000 cms 1 2 4 8 16
 */
#include <iostream>
#include <chrono>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <algorithm>
#include <string>
#include <condition_variable>
#include <cstdio>
#include <asio.hpp>
#include "orm_conn_pool.h"
#include "orm.h"

using clk = std::chrono::high_resolution_clock;

struct ThreadStat { int64_t ops = 0, errors = 0; std::vector<double> samples; };
struct BenchStat {
    std::string name; int threads = 0; int64_t total_ops = 0, total_errors = 0;
    double total_ms = 0, avg_us = 0, p50_us = 0, p99_us = 0, throughput = 0;
    std::string first_error;
};

static std::mutex g_err_mu;
static std::string g_first_err;
static void set_error(const std::string &msg) {
    std::lock_guard<std::mutex> l(g_err_mu);
    if (g_first_err.empty()) g_first_err = msg;
}

// ============================================================
// SYNC: worker 直接串行 N 次同步 fetch_one (不依赖 io_context run)
// ============================================================
void thread_worker_sync(int tid, int iters, ThreadStat &stat)
{
    for (int i = 0; i < iters; ++i) {
        auto a = clk::now();
        bool ok = false;
        try {
            auto users = orm::cms::Sysuser();
            unsigned int n = users.select("name").eqName("admin").fetch_one();
            ok = (n > 0);
            if (!ok && !users.error_msg.empty())
                set_error(std::string("sync: ") + users.error_msg);
        } catch (std::exception &e) { set_error(std::string("sync ex: ") + e.what()); }

        stat.samples.push_back(
            std::chrono::duration<double, std::micro>(clk::now() - a).count());
        stat.ops++;
        if (!ok) stat.errors++;
    }
}

// ============================================================
// ASYNC: worker 提交 1 个协程(N 次 loop) 到共享 io_context, 然后用 cv 等完成
// IO 线程池已在 main 里永久 run ioc, 协程被它们拾取执行
// ============================================================
void thread_worker_async(int tid, int iters, ThreadStat &stat, asio::io_context &ioc)
{
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    asio::co_spawn(ioc, [&]() -> asio::awaitable<void> {
        for (int i = 0; i < iters; ++i) {
            auto a = clk::now();
            bool ok = false;
            try {
                auto users = orm::cms::Sysuser();
                unsigned int n = co_await users.select("name").eqName("admin").async_exec_fetch_one();
                ok = (n > 0);
                if (!ok && !users.error_msg.empty())
                    set_error(std::string("async: ") + users.error_msg);
            } catch (std::exception &e) { set_error(std::string("async ex: ") + e.what()); }

            stat.samples.push_back(
                std::chrono::duration<double, std::micro>(clk::now() - a).count());
            stat.ops++;
            if (!ok) stat.errors++;
        }
        {
            std::lock_guard<std::mutex> lk(m);
            done = true;
        }
        cv.notify_one();
        co_return;
    }, asio::detached);

    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, [&]{ return done; });
}

// ============================================================
static BenchStat run_bench(const std::string &name, int threads, int iters,
                           bool async_mode, asio::io_context &ioc)
{
    BenchStat s{name, threads, 0, 0, 0, 0, 0, 0, 0, {}};
    std::vector<ThreadStat> tstats(threads);
    std::vector<std::thread> workers;
    g_first_err.clear();
    auto t0 = clk::now();

    for (int t = 0; t < threads; ++t) {
        if (async_mode)
            workers.emplace_back(thread_worker_async, t, iters, std::ref(tstats[t]), std::ref(ioc));
        else
            workers.emplace_back(thread_worker_sync, t, iters, std::ref(tstats[t]));
    }
    for (auto &w : workers) w.join();

    s.total_ms = std::chrono::duration<double, std::milli>(clk::now() - t0).count();
    std::vector<double> all;
    for (auto &ts : tstats) {
        s.total_ops += ts.ops; s.total_errors += ts.errors;
        all.insert(all.end(), ts.samples.begin(), ts.samples.end());
    }
    std::sort(all.begin(), all.end());
    if (!all.empty()) {
        s.avg_us = s.total_ms * 1000.0 / s.total_ops;
        s.p50_us = all[all.size() * 50 / 100];
        s.p99_us = all[all.size() * 99 / 100];
    }
    s.throughput = s.total_ops / (s.total_ms / 1000.0);
    s.first_error = g_first_err;
    return s;
}

static void print_stat(const BenchStat &s) {
    std::printf("  %-34s T=%2d  ops=%7lld  total=%7.0fms  avg=%6.0fus  p50=%6.0fus  p99=%6.0fus  %9.1f/s  err=%lld\n",
        s.name.c_str(), s.threads, (long long)s.total_ops, s.total_ms,
        s.avg_us, s.p50_us, s.p99_us, s.throughput, (long long)s.total_errors);
    if (!s.first_error.empty()) std::printf("      |_ %s\n", s.first_error.c_str());
}

// ============================================================
int main(int argc, char *argv[])
{
    bool async_mode = false;
    int arg_idx = 1;
    if (argc > 1 && (std::string(argv[1]) == "async" || std::string(argv[1]) == "sync")) {
        async_mode = (std::string(argv[1]) == "async");
        arg_idx = 2;
    }

    int iters = (argc > arg_idx) ? std::atoi(argv[arg_idx]) : 2000;
    std::string conf_tag = (argc > arg_idx + 1) ? argv[arg_idx + 1] : "cms";
    std::vector<int> tcounts;
    for (int i = arg_idx + 2; i < argc; ++i) tcounts.push_back(std::atoi(argv[i]));
    if (tcounts.empty()) tcounts = {1, 2, 4, 8, 16};

    std::printf("=== ORM Stress Test (%s) ===\n", async_mode ? "ASYNC" : "SYNC");
    std::printf("    iters/thread=%d, conf=[%s], threads:", iters, conf_tag.c_str());
    for (int t : tcounts) std::printf(" %d", t);
    std::printf("\n\n");

    try {
        asio::io_context ioc{0};
        auto log = orm::init_orm_conn_pool(ioc, "./conf/orm.conf");
        if (!log.empty()) std::printf("[init] %s\n", log.c_str());

        auto pool = orm::get_orm_conn_pool_obj()[conf_tag];
        if (!pool) { std::printf("[FAIL] tag [%s] not found\n", conf_tag.c_str()); return 1; }

        // ========== 启动 IO 线程池 (server.cpp 6238 行模式) ==========
        // executor_work_guard 让 run() 永不返回, 直到 guard reset 或 ioc.stop
        asio::executor_work_guard<asio::io_context::executor_type> work(ioc.get_executor());
        int io_threads = async_mode ? 4 : 1;
        std::vector<std::thread> iopool;
        for (int i = 0; i < io_threads; ++i) {
            iopool.emplace_back([&ioc]() {
                try { ioc.run(); } catch (...) {}
            });
        }

        // 验证 DB
        {
            auto c = pool->get_mysql_select_conn();
            if (!c) { std::printf("[FAIL] no mysql conn\n"); return 1; }
            std::string ver;
            c->fetch_directly("SELECT VERSION()", [&ver](int, char**, auto gd){
                auto [p, n] = gd(0);
                if (p) ver.assign(reinterpret_cast<const char*>(p), n);
                return true;
            });
            std::printf("[server] %s\n[io_threads] %d\n\n", ver.c_str(), io_threads);
            pool->back_mysql_select_conn(std::move(c));
        }

        // 预热
        {
            orm::cms::Sysuser w;
            auto n = w.select("name").eqName("admin").fetch_one();
            std::printf("[warmup] fetch_one admin => n=%u\n\n", n);
        }

        std::vector<BenchStat> stats;
        const char *name = async_mode ? "orm_async_fetch_one" : "orm_sync_fetch_one";

        for (int T : tcounts) {
            std::printf("[T=%d] ", T);
            auto s = run_bench(name, T, iters, async_mode, ioc);
            print_stat(s);
            stats.push_back(std::move(s));
            std::printf("\n");
        }

        long long terr = 0, tops = 0;
        for (auto &s : stats) { terr += s.total_errors; tops += s.total_ops; }
        std::printf("[TOTAL] errors=%lld / %lld ops\n", terr, tops);

        // ========== 停止 IO 线程池: release work guard → run() 自然退出 ==========
        work.reset();
        ioc.stop();
        for (auto &t : iopool) t.join();

        orm::init_orm_conn_pool_release();
        return terr == 0 ? 0 : 1;
    }
    catch (std::exception &e) { std::printf("Exception: %s\n", e.what()); return 1; }
}
posted @ 2026-09-17 10:43  游水小鸡  阅读(17)  评论(0)    收藏  举报