看懂C++性能问题必须分析汇编,因push_back扩容、atomic内存序、shared_ptr引用计数、浮点优化等真实开销均隐藏在生成指令中,源码无法反映实际执行行为。

看懂 std::vector::push_back 为什么有时慢得反常
因为编译器生成的汇编里藏着内存重分配的真实开销。C++ 代码写得再优雅,push_back 触发扩容时,memcpy 或 rep movsb 指令一跑就是几千字节——而你根本没在源码里写 memcpy。
- 用
g++ -S -O2看汇编输出,重点找call __memmove_avx_unaligned_erms这类调用,它比你想象中更常出现 - 如果 vector 存的是非 trivial 类型(比如含虚函数或自定义析构的类),还会夹杂构造/析构循环,汇编里能看到成对的
call和test判断 - 别只信 profiler 的“热点函数”统计:它可能把耗时全算在
push_back上,但真正卡住的是后面那段内联展开的拷贝循环
调试 std::atomic<int></int> 的内存序问题时,光看 C++ 标准没用
标准说 memory_order_acquire “禁止重排”,但具体禁哪些、怎么禁,得看生成的汇编指令。x86 下 acquire 可能不生成 lfence,而 ARM64 下却必须插 ldar —— 这直接决定多线程 bug 能不能复现。
- 检查是否真有屏障:用
objdump -d看关键原子操作附近有没有mfence、ldar、stlr等指令 -
std::atomic_thread_fence在不同平台生成的指令差异极大,x86 常被优化掉,ARM/AArch64 却一定留着——不看汇编,你会误判 fence 是否生效 - Clang/GCC 对
memory_order_relaxed的优化激进程度不同,同一段代码,Clang 可能彻底删掉读操作,GCC 却保留一条mov;只有汇编能告诉你实际发生了什么
理解 std::shared_ptr 的引用计数为何不是原子加减那么简单
它的控制块布局、计数更新顺序、弱引用计数和强引用计数如何错开更新——这些细节全在汇编里暴露无遗。你以为 operator= 就是几个原子操作?其实可能包含分支预测失败导致的 pipeline stall。
- 控制块通常在堆上单独分配,
shared_ptr构造时的两次 malloc(一次控块、一次对象)在汇编里清清楚楚,影响 cache 局部性 - 强引用+1 和弱引用+1 不是并行执行的:先更新弱计数,再更新强计数,中间还有
test判断是否为零——这个顺序在 x86 下靠lock inc保证,但在 ARM 上要靠stlr+ldar配合 - 当
weak_ptr::lock()失败时,汇编里往往有一段短小的条件跳转,但现代 CPU 的分支预测器对这种“99% 成功、1% 失败”的模式很不友好,性能毛刺就藏在这里
排查 -O2 下诡异的数值误差或崩溃
浮点运算被重排、整数溢出被优化掉、甚至整个计算分支被删掉——这些都不是 bug,是编译器按标准做的合法变换。只有看汇编,才能确认你的 double 计算到底走的是 SSE 还是 x87,用了 addsd 还是 faddp。
立即学习“C++免费学习笔记(深入)”;
- 开启
-ffloat-store后对比汇编:你会发现原本存在 XMM 寄存器里的中间值,突然被强制写回栈,指令多了几条movsd—— 这就是精度变化的物理来源 -
int x = a * b + c;在 32 位平台可能被编译成imull+addl,但若 a,b 是 const,GCC 可能直接算出常量,连乘法指令都不生成 - 未初始化变量的“随机值”,在汇编里常常是寄存器残留值或栈上旧数据,而不是全零;用
valgrind --tool=memcheck报的 error,最终得靠汇编定位到哪条mov指令读了未定义内存
汇编不是让你手写,而是给你一把尺子——量清楚编译器到底对你写的每一行 C++ 做了什么。最麻烦的从来不是看不懂指令,而是以为自己看懂了,结果漏掉了那条隐式插入的 cmp 或者寄存器重用带来的副作用。










