《C++ High Performance》学习总结:把性能优化从玄学变成方法论

《C++ High Performance》学习总结:把性能优化从玄学变成方法论 一句话评价:这本书不教你怎么"更快",而是教你怎么系统地发现慢在哪、为什么慢、以及怎么改。看完最大的收获不是某个技巧,而是一整套性能思维框架。 写在前面:为什么读这本书 说实话,做服务器开发这几年,性能优化基本靠"直觉 + 经验 + 试":感觉这里慢就优化这里,测一下看有没有变化。这种打法偶尔有效,但有两个致命问题: 没有量测就跑优化——经常优化了半天,发现改的根本不是热点(Amdahl 定律的教训:优化一个只占 5% 时间的函数,改出花来整体也就快 5%)。 不知道还有哪些工具——工具链一直在进化,perf、火焰图、Sanitizer、PGO,很多只知道名字没实际用过。 这本书正好把这两块补齐了。它不是零基础教程,需要你对 C++ 有一定基础(至少要懂 STL、模板、RAII),但它能把你已有的知识用性能的视角重新串一遍。 下面按书的脉络讲,但重点讲对我们服务器开发真正有用的部分,并且每条都尽量落到我们的实际场景。 这篇总结我写得比较长,因为它本质上是我重读这本书时给自己补的"教学设计":每一个之前只记结论的知识点,我都尽量补上"为什么"。读的时候你可以把它当成一条讲解链路:它是什么 → 它为什么这么设计(底层原理)→ 一个生活化的类比 → 配图或代码 → 落到我们服务器的实际场景。这样哪怕几个月后再回头看,也能靠推理而不是靠死记找回上下文。 一、先建立正确的性能思维(第 1-3 章) 1.1 值语义 vs 引用语义——很多人忽略的第一课 是什么 第 1 章从 C++ 最本质的特性讲起:C++ 默认是值语义。auto a = b 是拷贝,不是两个名字指向同一块内存。这一点和 Java、Python、C# 里"变量是引用"的直觉完全不同,也是很多新手刚从脚本语言转过来时栽的第一个跟头。 1 2 3 4 5 6 7 8 struct Player { int guid; int level; }; Player p1{10001, 60}; Player p2 = p1; // 拷贝:p2 拥有自己的内存,内容从 p1 复制 p2.level = 99; // 改 p2 不影响 p1 为什么(底层原理) ...

August 15, 2026 · 19 min · 4014 words

性能优化思路:从测量到代码的五层递进方法论

性能优化思路:从测量到代码的五层递进方法论 优化不靠直觉,靠数据。这篇文章是我在给 Hical 做性能优化时沉淀下来的一套方法论:从"用 profiler 找到真正的瓶颈",到架构选型、系统间交互、数据结构、再到最后的代码级微优化,一共五层,层层递进。每一层都有对应的工具、实战案例和"什么时候别优化"的提醒。看完你会发现,大部分性能问题根本轮不到写代码,先想清楚在哪一层出手,比埋头优化重要得多。 目录 第零层:测量——找到真正的瓶颈 性能优化五层模型 架构级性能优化 (子)系统间性能优化 数据结构与算法性能优化 代码级性能优化 性能回归检测:优化完了怎么守住 第零层:测量——找到真正的瓶颈 优化不是凭直觉猜,是数据驱动的工程决策。没有 profiling 数据就动手,相当于在黑屋子里开枪——打中算运气,打不中浪费子弹。我在 Hical 上踩过的第一个坑就是猜错了方向:光凭代码感觉觉得是协程帧太多,结果 perf 一跑,14.5% 的 CPU 其实耗在调度器上。所以,先量,再动。 工具链:每种工具回答一个问题 工具 回答什么问题 Hical 实战 perf record + 火焰图 CPU 时间花在哪了 perf record -g -F 99 → docker 内 10s wrk strace -c 系统调用频次和时间占比 epoll_ctl 31351 次占 25.92% GTest 性能用例 函数级性能断言 test_router_perf.cpp,100K 次 dispatch 计时 wrk / wrk2 端到端吞吐和延迟分布 wrk -c100 -t4 -d30s heaptrack / valgrind massif 内存分配热点 找意外的大分配 perf stat -d IPC、分支预测率、cache miss 看整体健康度 火焰图怎么看 拿 Hical 实际火焰图数据来说(docker 内 wrk -c10000 -d10s,perf record -g -F 99): ...

August 1, 2026 · 13 min · 2748 words

从零理解 MPSC 无锁写队列:高性能网络框架的发送引擎

写在前面 你有没有想过,一个高并发网络服务器在同时给几千个客户端发消息时,底层到底在忙什么? 最直觉的做法是加把锁——谁要发消息就抢锁、写 socket、放锁。但问题来了:如果 1000 个协程同时要给同一个连接发数据,它们就得排队等这把锁。锁竞争带来的上下文切换、cache 失效,会把性能拖进泥潭。 Hical 框架的解法很酷:用一个 MPSC(多生产者单消费者)无锁队列 做缓冲,配合一个协程写循环做消费。发消息的线程只管往队列里扔节点(wait-free,永远不阻塞),写循环协程在 IO 线程上批量取出、合并发送。 这篇文章会带你从零搞懂这个设计。不需要你有无锁编程的基础,但最好知道 C++ 的 atomic、shared_ptr 和"什么是协程"大概是怎么回事。 一、先搞清楚问题:为什么普通的锁不行? 1.1 一个典型场景 假设你写了个聊天服务器。用户 A 发了条群消息,服务器要转发给群里 200 个在线用户。这意味着: 1 用户A的消息 → 广播逻辑 → 同时调用 200 个连接的 send() 如果 send() 内部用 mutex 保护写操作: 1 2 3 4 5 6 7 8 9 10 11 // 朴素实现(有严重性能问题) void Connection::send(std::string data) { std::lock_guard lock(writeMutex_); // 200个协程在这里排队 writeBuffer_.append(data); if (!writing_) { writing_ = true; doWrite(); // 触发实际的 socket 写入 } } 问题出在哪? ...

May 28, 2026 · 10 min · 2127 words

Hical 性能优化全记录

优化背景 Hical 是我写的 C++20/26 Web 框架,跑 Hello World 压测时起初只有 27K QPS,而同类框架(Cinatra 165K、Drogon 170K)差了将近一个数量级。目标很明确:追平 Cinatra/Drogon 的水平。 整个优化过程分 6 个阶段,不是拍脑袋乱改,每一步都是 perf + 火焰图定位瓶颈 → 想方案 → 写代码 → 跑压测验证 的循环。能看到数字变化才算数。 阶段 1:协程帧削减(v2.5.1-v2.5.2) 发现问题 perf 火焰图第一个大头:14.5% CPU 在 scheduler::wake_one_thread_and_unlock + pthread_cond_signal。 一开始以为是跨线程调度问题,仔细一看不是——是 Boost.Asio scheduler 每次 co_await resume 都要走的内部调度流程太重了。一个 Hello World 请求居然走了 4 个协程帧: 1 2 3 4 5 handleSession: co_await async_read → 帧 1(必需,I/O 等待) co_await router_.dispatch() → 帧 2(Router 本身是协程) co_await handler(req) → 帧 3(同步 handler 被包装成协程,不必要!) co_await async_write → 帧 4(必需,I/O 等待) 帧 1 和 4 是真正的 I/O 等待不可消除,但帧 2 和 3 完全是浪费——一个同步的 return HttpResponse("Hello") 被裹了两层协程。 ...

May 22, 2026 · 9 min · 1794 words

Hical 生产部署实践:从编译优化到 Kubernetes 容器化

Hical 生产部署实践:从编译优化到容器化 框架开发完了,测试也通过了——然后呢?“本地跑得好好的"和"线上稳定运行"之间,隔着编译优化、进程管理、反向代理、监控告警、容器编排一整套工程实践。这篇文章把 Hical 从开发环境搬到生产环境的完整链路走一遍,每个环节都给出可直接复用的配置模板。 目录 Hical 生产部署实践:从编译优化到容器化 目录 一、编译优化:榨干最后一点性能 1.1 Release 基础参数 1.2 LTO(链接时优化) 1.3 PGO(Profile-Guided Optimization) 1.4 静态链接 vs 动态链接 二、进程管理:别让服务裸奔 2.1 systemd 服务配置 2.2 信号处理与 Graceful Shutdown 2.3 多线程与多 acceptor(SO_REUSEPORT) 三、反向代理:Nginx 挡在前面 3.1 HTTP 反向代理 3.2 WebSocket 代理 3.3 SSL 终止策略 四、监控与可观测性 4.1 Prometheus 指标暴露 4.2 日志接入 ELK / Loki 4.3 健康检查端点 五、容器化部署 5.1 多阶段 Dockerfile 5.2 docker-compose 完整示例 5.3 Kubernetes 部署参考 六、性能调优检查清单 系统级 Hical 应用级 PMR 内存池 数据库连接池 日志系统 调优流程 一、编译优化:榨干最后一点性能 1.1 Release 基础参数 开发阶段用 Debug 方便调试,上线必须切 Release。区别不只是 -O2,还有 assert 消除、NDEBUG 定义(Hical 的 HICAL_LOG_TRACE 宏在 NDEBUG 下编译期完全消除): ...

May 17, 2026 · 15 min · 2992 words

连接级 Atomic 时间戳超时的实现决策

起因 最初 Hical 的空闲超时实现就是传统做法:每个 HTTP 请求/每次 keep-alive 读等待都注册一个 steady_timer,读完成后取消,超时则关闭连接。实现上用的是 shared_ptr<function> 自引用环做回调链续期——每连接 2 次堆分配(shared_ptr 控制块 + function 对象),且每次续期都要重新构造回调。 v2.5.2 压测到 132K QPS 时,做热路径Review代码发现这个 timer 机制的问题: 每请求 2 次 epoll_ctl(注册 + 取消 timer) shared_ptr<function> 自引用环本身就有堆分配开销 140K QPS 下整体约产生 100 万次 epoll_ctl/sec,38% CPU 花在内核 _raw_spin_unlock_irqrestore(TCP spin_lock),而用户态框架代码只占不到 5% 瓶颈已经从用户态转移到内核态,减少进内核的次数成为核心策略。空闲超时的 timer 是明确可以砍掉的——30-60s 的超时精度要求本来就极低。 改良过程 分两步走: 第一步:先把 shared_ptr<function> 回调链改为独立协程 idleTimerLoop,消除自引用环和 2 次堆分配。这一步还是 per-connection 一个 timer 协程,只是实现更干净了。 第二步:发现即便使用协程,per-connection timer 仍然意味着每次 timer 到期时要走 scheduler 调度 + epoll_ctl。最终演化为"TcpServer 统一扫描"的设计——整个 server 只需要一个扫描协程,连接侧只写一个 atomic 值。 ...

May 12, 2026 · 2 min · 314 words

Hical v2.6.0 性能优化心得:从 27K 到 159K QPS 的完整旅程

Hical v2.6.0 性能优化心得:从 27K 到 159K QPS 的完整旅程 这篇文章记录了 Hical 从 v2.5.2 到 v2.6.0 的完整性能优化历程。不是罗列"我做了什么改动",而是分享怎么发现问题、怎么思考方案、怎么验证效果——以及那些"看起来应该有用但实际没用"的弯路。希望对做 C++ 高性能服务器开发的同学有参考价值。 目录 Hical v2.6.0 性能优化心得:从 27K 到 159K QPS 的完整旅程 目录 1. 起点:27K QPS,差距 6 倍 2. 第一个教训:不要猜,要量 3. 找对方向:火焰图告诉你真相 4. 三阶段优化路线 5. 阶段一:调度模型重构(27K → 132K) 5.1 SO_REUSEPORT:消除跨线程调度 5.2 连接级 Timer + atomic 时间戳 5.3 结果 6. 阶段二:去 Beast,自研 HTTP/WS 栈(132K → 140K) 6.1 四个 Phase 6.2 零拷贝请求解析 6.3 结果 7. 阶段三:热路径微优化(140K → 159K) 7.1 修复 readBuf 残留数据丢弃(功能 BUG + 性能) 7.2 scatter-gather I/O 替代单 buffer 合并 7.3 其他微优化(含后续延迟分配优化) 7.4 结果 8. 最终火焰图:确认优化到位 9. 走过的弯路 弯路 1:优化不是瓶颈的代码 弯路 2:FixedBuffer 栈缓冲区太大 弯路 3:过早放弃 10. 总结:性能优化的方法论 原则一:Profiling 驱动,不靠直觉 原则二:按占比排序,从大到小 原则三:每步验证,不要积累 原则四:知道何时停手 最终成绩单 1. 起点:27K QPS,差距 6 倍 v2.5.1 的 Hical 在 Docker 环境(Ubuntu 24.04, GCC 14, 4 线程)下跑 Hello World benchmark,wrk 报出 ~27K QPS。 ...

May 11, 2026 · 6 min · 1235 words

Hical v2.5.2 性能优化实战:SO_REUSEPORT + 连接级 Timer 实现 3 倍 QPS 提升

Hical v2.5.2 性能优化实战:SO_REUSEPORT + 连接级 Timer 实现 3 倍 QPS 提升 在 火焰图分析中,我们定位到 Hical 的 QPS 瓶颈在 Boost.Asio 的 epoll 交互模型——跨线程调度(14.5%)和 timer 相关 epoll_ctl(12.5%)合计吃掉了 27% 的 CPU。[P1 优化](Router 同步快速路径)无实质提升后,本文记录 P2/P3 两项优化的设计思路、实现细节和实测结果。 目录 1. 背景回顾 2. 优化方案 A:SO_REUSEPORT 多 Acceptor 3. 优化方案 B:连接级 Timer + Atomic 时间戳 4. 实测结果 5. 剩余差距与后续方向 6. 复现指南 1. 背景回顾 1.1 P1 优化无效的原因 v2.5.2 实现了 Router::dispatchSync() 同步快速路径,在无中间件场景下跳过协程帧分配。三轮 Docker 压测结果: 轮次 QPS 变化 v2.5.1(基线) 27,493 — v2.5.1(静态链接) 19,381 系统波动 v2.5.2(dispatchSync) 20,940 无实质提升 原因:Router::dispatch 在火焰图中仅占 0.24% CPU,同步快速路径省掉的协程帧(~40-130ns)被 Asio 调度层(27%)完全淹没。 ...

May 10, 2026 · 6 min · 1271 words

Hical 踩坑实录五部曲(四):PMR 三层内存池——从理论完美到实战翻车

Hical 踩坑实录五部曲(四):PMR 三层内存池——从理论完美到实战翻车 引言 Hical 的内存管理采用 C++20 PMR(Polymorphic Memory Resource)三层池架构:全局同步池 → 线程本地无锁池 → 请求级单调缓冲。理论上完美——每一层解决一个特定的性能瓶颈。 但理论和实战之间,隔着一堆坑。 这篇记录了三层 PMR 池在开发和压测过程中遇到的 7 个真实问题——从跨线程 UAF 到 GC 永远不触发、从 CAS 自旋到缓冲区膨胀,每个都是排查半天以上的教训。 目录 Hical 踩坑实录五部曲(四):PMR 三层内存池——从理论完美到实战翻车 引言 目录 坑 1:configure() 原地重建的 use-after-free 坑 2:generation 缓存失效的竞争窗口 坑 3:GC 标记了但永远不回收——死线程的内存泄漏 坑 4:CAS 峰值更新的缓存行风暴 坑 5:请求级单调池的 upstream 选错 坑 6:PmrBuffer 缩容不及时导致内存膨胀 坑 7:allocator 传播链断裂——PMR 白忙一场 总结:PMR 三层池的使用清单 坑 1:configure() 原地重建的 use-after-free 现象:在服务启动流程中调用 MemoryPool::configure() 后,偶发崩溃,堆栈指向 synchronized_pool_resource 的内部结构。 根因:configure() 使用 placement new 原地重建全局池——先析构旧池,再构造新池: ...

May 10, 2026 · 7 min · 1379 words

深入学习 ODB(七):性能调优与生产最佳实践

系列导航:编译器与注解 | 连接与事务 | 对象关系 | 类型安全查询 | 继承与视图 | 迁移与多库 | 性能与实践(本文) | 实战项目 引言:万人同服的存档难题 某 MMO 游戏服务器,5000 人同时在线,每 5 分钟执行一次全服存档: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 朴素实现:逐个存档 void saveAllPlayers(odb::database& db, const std::vector<Player*>& onlinePlayers) { for (auto* player : onlinePlayers) { odb::transaction t(db.begin()); db.update(*player); t.commit(); } } // 5000 个玩家 × 每次 1 个事务 × 每事务 1 条 UPDATE // = 5000 次网络往返 + 5000 次事务提交 // 耗时:约 15~30 秒(取决于网络延迟) // 这期间游戏逻辑被阻塞,玩家感受到明显卡顿 这段代码有三个性能杀手: ...

July 5, 2025 · 13 min · 2726 words