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 |
🔑 核心发现
-
paozhu 全面碾压,吞吐 ~2-3 倍于 drogon
- paozhu:线性扩展到 T=32 仍在涨(77K ops/s)
- drogon:T=4~8 就到瓶颈(21-25K),之后不升反降(线程多了反而互相抢连接/锁)
-
延迟也领先,p50 约快 2 倍
- paozhu p50 稳定在 70-200μs(高 T 才升到 393μs)
- drogon p50 在高并发下迅速恶化到 1.2-1.4ms
-
paozhu ASYNC 在高并发下反超 SYNC(77.6K > 73.2K)
- executor_work_guard + 多 IO 线程池有效吸收了高并发压力
- drogon ASYNC 反而比 SYNC 慢(EventLoop 单线程瓶颈)
-
零错误率 — 两边都很稳定
可能原因推测
| 维度 | 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; }
}

浙公网安备 33010602011771号