《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

《深入理解计算机系统》(CSAPP)学习总结:从晶体管到网络协议的完整世界观

《深入理解计算机系统》(CSAPP)学习总结:从晶体管到网络协议的完整世界观 一句话评价:这本书解决的是"会写代码但不知道为什么"的困惑——它把从 CPU 到网络的所有中间层一次性打通。看完之后再看那些经典的 C++ 性能技巧、并发 bug、内存问题,全都"对上了号"。 写在前面:为什么服务器开发必须读这本书 做服务器开发,平时打交道最多的就是"程序为什么快/慢、为什么会崩、为什么内存涨"。这些问题你问一个纯应用层 C++ 开发者,他可能给你一堆经验;但这本书给的是底层原理——每个"经验"背后都有确切的硬件/操作系统机制支撑。 举个具体例子:我们写 std::vector 比 std::list 快,这个"经验"人尽皆知。但只有懂了缓存层次结构和 cache line(高速缓存行),你才真正明白为什么快、快多少、在什么极端情况下可能不成立。没有这个底层认知,性能优化永远是"背口诀"而不是"推导"。 这本书的三个核心理念贯穿始终: 程序员是系统的"中间层"——向上要满足应用需求,向下要理解硬件和 OS 如何配合。 抽象是管理复杂度的工具——指令集抽象 CPU,虚拟内存抽象物理内存,文件抽象 I/O,虚拟机和容器抽象整机。 “程序员的视野”——书中反复出现的经典表述:程序员看到的地址空间,是虚拟的;程序员看到的"文件",是内核在背后处理的字节流。 下面按书的 13 章讲,重点讲对服务器开发真正有用的部分,并尽量落到我们的实际场景。 一、计算机系统漫游(第 1 章) 第 1 章是全书总纲,用"hello world"程序的完整旅程串起整个系统:源文件 → 编译器 → 汇编器 → 链接器 → 可执行文件 → 加载器 → CPU 执行 → 标准输出。 1.1 一条 C 语句的一生 你写下 printf("hello, world\n"),它背后其实经历了"多国接力": 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 hello.c(文本) │ ① 预处理器:展开 #include、宏 ▼ hello.i(展开后的文本) │ ② 编译器 gcc:翻译成汇编 ▼ hello.s(汇编文本,mov/add/call 这些助记符) │ ③ 汇编器 as:把汇编翻译成机器指令(0101...) ▼ hello.o(目标文件,机器码,但地址还没定) │ ④ 链接器 ld:和库合并、分配最终地址 ▼ hello(可执行文件,躺在磁盘上) │ ⑤ 加载器:把它读进内存,从 main 开始执行 ▼ CPU 取指 → 译码 → 执行 → 访存 → 写回 ▼ 通过系统调用 write() 把字节交给操作系统 → 终端 注意一个细节:编译器的输出不是二进制,而是汇编——这是人和机器之间的一层"翻译手稿",后面第 3 章就是专门讲怎么读懂它。每一层都是下一层用"更接近机器的语言"重写一遍,直到变成 CPU 认识的 0/1。 ...

August 15, 2026 · 24 min · 5028 words

C++26 前瞻心得:下一代 C++ 最值得期待的特性

C++26 前瞻心得:下一代 C++ 最值得期待的特性 C++11 让 C++ 进入现代,C++20 让 C++ 追上时代,C++26 要让 C++ 重新定义「零开销抽象」的边界。 写在前面 C++26 标准预计 2026 年底正式发布。截至本文写作时(2026 年 5 月),核心特性已基本锁定,部分编译器开始提供实验性支持。 这篇文章不追求完整列举所有提案,只聊我认为对实际项目冲击最大的特性——尤其从游戏服务器和 Hical 框架开发的角度。这是 C++17 心得 和 C++20 心得 的续篇。 声明:部分特性的最终语法可能随标准定稿而调整,代码示例基于当前最新提案。 一、静态反射(Static Reflection)—— C++ 的 Game Changer 1.1 为什么反射是最重要的 C++26 特性 在 Java、C#、Go 中习以为常的操作——遍历结构体字段、获取类名、自动序列化——在 C++ 中一直只能靠宏或代码生成。C++26 的反射(P2996)让编译器在编译期暴露类型的元信息: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <meta> struct Player { uint64_t id; std::string name; int level; int64_t gold; }; // 编译期遍历所有成员 template <typename T> void printFields(const T& obj) { template for (constexpr auto member : std::meta::members_of(^T)) { if constexpr (std::meta::is_nonstatic_data_member(member)) { std::println(" {}: {}", std::meta::name_of(member), obj.[:member:]); } } } Player p{1001, "Hical", 85, 999999}; printFields(p); // 输出: // id: 1001 // name: Hical // level: 85 // gold: 999999 零运行时开销,不需要宏,不需要代码生成工具,编译器原生支持。 ...

April 21, 2026 · 8 min · 1554 words

C++20 实战心得:现代 C++ 真正成熟的一代

C++20 实战心得:现代 C++ 真正成熟的一代 C++11 是革命,C++17 是打磨,C++20 是让 C++ 终于像一门「现代语言」。 写在前面 如果说 C++17 的升级是务实的,那 C++20 就是一次结构性的飞跃。协程、Concepts、Ranges、Modules——每一个都是重量级特性。但老实说,截至 2026 年,并非所有特性都已经在生产环境中稳定好用。 这篇文章从我在游戏服务器和 Hical 框架开发中的实际使用出发,聊聊哪些 C++20 特性已经值得用、哪些还需要等等。 一、Concepts —— 模板错误信息终于能看懂了 1.1 C++20 之前的模板报错 先感受一下 C++17 时代的"恐怖": 1 2 std::list<int> lst; std::sort(lst.begin(), lst.end()); GCC 会喷出几十行模板展开错误,核心意思是 std::list::iterator 不是随机访问迭代器——但你得从一堆 __normal_iterator、__gnu_cxx 嵌套模板中自己悟出来。 1.2 Concepts:把约束说人话 1 2 3 4 5 6 7 8 9 template <std::random_access_iterator Iter> void mySort(Iter first, Iter last) { // ... } std::list<int> lst; mySort(lst.begin(), lst.end()); // 错误信息:约束 'random_access_iterator' 不满足 // 一行,清清楚楚 Concepts 的本质:给模板参数加上编译期的「类型契约」。SFINAE 能做的它都能做,但写法是人能读懂的。 ...

April 20, 2026 · 8 min · 1683 words

C++17 实战心得:那些真正改变我写代码方式的特性

C++17 实战心得:那些真正改变我写代码方式的特性 从游戏服务器开发的视角出发,不求面面俱到,只聊那些真正让我「回不去了」的 C++17 特性。 写在前面 C++17 的特性列表很长,但实际工作中高频使用的并不多。这篇文章只聊我在游戏服务器开发中真正用上了、且明显感到提升的特性,按「爽度」排序。 一、结构化绑定(Structured Bindings) 1.1 告别 .first / .second C++17 之前,遍历 std::map 是这样的: 1 2 3 4 5 for (auto it = playerMap.begin(); it != playerMap.end(); ++it) { auto playerId = it->first; auto& player = it->second; // ... } C++17 之后: 1 2 3 for (auto& [playerId, player] : playerMap) { LOG_DEBUG << "玩家 " << playerId << " 等级: " << player.level; } 一行搞定,变量名直接表达语义,可读性提升巨大。 1.2 配合 insert / emplace 的返回值 1 2 3 4 auto [iter, success] = onlinePlayers.emplace(playerId, std::move(session)); if (!success) { LOG_WARN << "玩家 " << playerId << " 重复登录"; } 比起 result.second 去判断是否插入成功,success 的语义一目了然。 1.3 多返回值函数 1 2 // 解析网络包头:返回包类型和包体长度 auto [msgType, bodyLen] = parsePacketHeader(buffer); 不用再纠结「该用 std::pair 还是定义一个临时结构体」的问题了。当然,如果返回值超过 3 个,还是老老实实定义结构体。 ...

April 19, 2026 · 5 min · 1014 words

深入学习 C++26 静态反射(Static Reflection)

深入学习 C++26 静态反射(Static Reflection) 提案:P2996R9(Reflection for C++26) 头文件:<meta> 命名空间:std::meta 编译器支持:Clang(P2996 实验分支)、EDG(部分)/ GCC 和 MSVC 计划中 注意:C++26 标准预计 2026 年底定稿,本文语法基于当前最新提案,最终可能有微调 一、为什么需要静态反射? 1.1 C++ 元编程的历史痛点 C++ 一直以"零开销抽象"著称,但在类型自省这件事上,四十年来只能靠旁门左道: 方案 A:宏暴力展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 用宏定义可序列化结构体 #define DEFINE_FIELDS(TYPE, ...) \ static constexpr auto fields() { \ return std::make_tuple(__VA_ARGS__); \ } struct Player { uint64_t id; std::string name; int level; int64_t gold; DEFINE_FIELDS(Player, FIELD(id), FIELD(name), FIELD(level), FIELD(gold) // 手动列举每个字段 ) }; 方案 B:代码生成工具(protobuf / flatbuffers / 自研工具) ...

April 9, 2026 · 27 min · 5613 words

深入学习 C++20 协程(Coroutines)

深入学习 C++20 协程(Coroutines) 头文件:<coroutine> 命名空间:std 编译器要求:GCC 11+ / Clang 14+ / MSVC 19.28+(均需 -std=c++20 或以上) 注意:GCC 10 / Clang 8~13 可通过 -fcoroutines 和 <experimental/coroutine> 使用实验性支持 一、为什么需要协程? 1.1 异步编程的传统痛点 游戏服务器中充斥着异步操作——数据库查询、网络 I/O、定时器回调。传统方案各有各的痛: 方案 A:回调地狱(Callback Hell) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void HandleLogin(Connection* conn, const LoginPacket& pkt) { // 第1步:查询数据库验证账号 dbManager->QueryAsync("SELECT * FROM accounts WHERE name=?", pkt.name, [conn, pkt](const DBResult& result) { if (!result.ok) { conn->SendError("DB错误"); return; } // 第2步:查询角色列表 dbManager->QueryAsync("SELECT * FROM characters WHERE account_id=?", result.accountId, [conn](const DBResult& charResult) { if (!charResult.ok) { conn->SendError("DB错误"); return; } // 第3步:加载角色数据 dbManager->QueryAsync("SELECT * FROM inventory WHERE char_id=?", charResult.charId, [conn, charResult](const DBResult& invResult) { // 第4步:终于可以发送登录成功了... conn->SendLoginSuccess(charResult, invResult); }); }); }); } 方案 B:状态机(State Machine) ...

April 8, 2026 · 22 min · 4611 words

深入学习 C++17 PMR(Polymorphic Memory Resource)

深入学习 C++17 PMR(Polymorphic Memory Resource) 头文件:<memory_resource> 命名空间:std::pmr 编译器要求:GCC 9+ / Clang 9+ / MSVC 19.13+(均需 -std=c++17 或以上) 一、为什么需要 PMR? 1.1 传统 Allocator 模型的痛点 C++98 引入的 Allocator 是模板参数,这意味着: 1 2 3 4 std::vector<int, MyAlloc<int>> vec1; std::vector<int, std::allocator<int>> vec2; // vec1 和 vec2 是不同类型!无法互相赋值、放进同一个容器 核心问题: 痛点 说明 类型传染 Allocator 是模板参数,换一个 Allocator 就变了类型,所有接口签名都要跟着改 无法运行时切换 编译期绑定,测试时想换成 debug allocator?重新编译 难以组合 想让 vector 内部的 string 也用同一个 arena?极其繁琐 状态传播困难 有状态 allocator(如持有内存池指针)在容器拷贝/移动时语义复杂 1.2 PMR 的解法:运行时多态 PMR 用一个虚基类 std::pmr::memory_resource 取代模板参数,容器统一使用 std::pmr::polymorphic_allocator<T>: ...

April 6, 2026 · 12 min · 2482 words

Docker 新手入门:从零开始容器化你的应用

Docker 新手入门:从零开始容器化你的应用 如果你的程序在你电脑上能跑,那就把你的电脑也一起发给客户吧。——Docker 之前的世界 写在前面 这篇文章适合谁? 听说过 Docker 但从未用过的开发者 被「在我电脑上明明能跑」折磨过的人 想了解容器化部署但不知道从哪开始的人 读完你将获得什么? 理解 Docker 核心概念(镜像、容器、仓库) 能独立编写 Dockerfile 并构建镜像 能用 Docker Compose 编排多容器应用 能将一个 Web 应用容器化部署 一、Docker 是什么? 1.1 一句话解释 Docker 是一个应用打包、分发、运行的平台。它把你的应用和所有依赖(库、配置、系统工具)打包成一个镜像,然后在任何安装了 Docker 的机器上以容器的形式运行。 1.2 虚拟机 vs 容器 对比项 虚拟机 (VM) Docker 容器 隔离级别 硬件级(Hypervisor) 进程级(内核共享) 启动速度 分钟级 秒级 体积 GB 级 MB 级 性能损耗 10-20% 接近原生 资源占用 高(每个 VM 一个完整 OS) 低(共享宿主内核) 1 2 3 4 5 6 7 8 9 10 11 12 13 ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ 虚拟机架构 │ │ Docker 架构 │ ├─────────────────────────────────┤ ├─────────────────────────────────┤ │ App A │ App B │ App C │ │ App A │ App B │ App C │ │ Libs │ Libs │ Libs │ │ Libs │ Libs │ Libs │ │ OS │ OS │ OS │ ├─────────────────────────────────┤ ├─────────────────────────────────┤ │ Docker Engine │ │ Hypervisor │ ├─────────────────────────────────┤ ├─────────────────────────────────┤ │ Host OS │ │ Host OS │ ├─────────────────────────────────┤ ├─────────────────────────────────┤ │ Hardware │ │ Hardware │ └─────────────────────────────────┘ └─────────────────────────────────┘ 1.3 核心三概念 镜像(Image):只读模板,包含运行应用所需的一切。类比:安装光盘 容器(Container):镜像的运行实例。类比:用光盘装好的一台电脑 仓库(Registry):存放镜像的地方。类比:应用商店(Docker Hub) 三者关系: ...

October 1, 2025 · 10 min · 2113 words

深入学习 std::flat_map

深入学习 std::flat_map 头文件:<flat_map> 命名空间:std 编译器要求:C++23 起(GCC 15+ / Clang 18+ / MSVC 19.38+) 一、设计动机:std::map 的性能痛点 1.1 红黑树的缓存问题 std::map 底层是红黑树——每个节点独立分配在堆上: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 std::map 内存布局(红黑树): ┌──────┐ │ Node │ ← 堆上随机位置 │ k=5 │ └──┬───┘ ┌───┴───┐ ┌────▼──┐ ┌─▼─────┐ │ Node │ │ Node │ ← 另一个堆上随机位置 │ k=3 │ │ k=8 │ └───────┘ └────────┘ 每次查找跳转 O(log n) 个节点,每个节点可能在不同的缓存行 → 大量 cache miss → 对于只读查找密集的场景,性能远不如连续内存 1.2 flat_map 的解法:排序 vector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 std::flat_map 内存布局(两个排序 vector): Keys vector(连续内存): ┌───┬───┬───┬───┬───┬───┬───┐ │ 1 │ 3 │ 5 │ 7 │ 9 │ 12│ 15│ ← 有序排列 └───┴───┴───┴───┴───┴───┴───┘ Values vector(连续内存): ┌───┬───┬───┬───┬───┬───┬───┐ │ A │ B │ C │ D │ E │ F │ G │ ← 与 keys 一一对应 └───┴───┴───┴───┴───┴───┴───┘ 查找 key=7: 二分查找 keys vector → 命中索引 3 → 返回 values[3] = D 二分查找在连续内存上进行 → CPU 预取高效 → 极少 cache miss 1.3 性能对比 操作 std::map std::flat_map 原因 查找 O(log n),多次 cache miss O(log n),极少 cache miss 连续内存二分 vs 树节点跳转 有序遍历 O(n),频繁指针追逐 O(n),顺序内存访问 vector 遍历 vs 树 in-order 遍历 插入/删除 O(log n) O(n)(需移动元素) vector 中间插入需后移所有元素 内存占用 每节点 ≥ 32 bytes 开销 几乎零开销 无节点指针/颜色位 迭代器稳定性 插入/删除不影响其他 全部失效 vector reallocation 一句话总结:flat_map 用插入性能换取查找和遍历性能——适合"少写多读"的场景。 ...

June 10, 2025 · 10 min · 1996 words