引言:为什么计算机“思考”得比程序员更快?
\n\n想象一下:你花了一个晚上,在纸上写下数字 27 的 Collatz 序列。然后你的同事写了一个 C++ 循环,在一秒钟内遍历了十亿个数字。你愤怒吗?等等,这仅仅是个开始。
\n\n在 2025 年,Stack Overflow 上再次热烈讨论了一个老问题,但这是一个非常有代表性的问题:“为什么用于测试 Collatz 猜想的 C++ 代码比手写汇编运行得更快?”。表面上看,汇编是与处理器直接对话,没有中间商。为什么高级语言 C++ 能够超越人类编写的汇编代码呢?
\n\n让我们以 Collatz 猜想(也称为 3n+1 问题)为例来分析这个悖论。您将了解到为什么现代 C++ 编译器是真正的时光机,以及如何在 2025 年编写比“手动”寄存器操作更快的代码。
\n\n什么是 Collatz 猜想,它与 C++ 有什么关系?
\n\n让我们回顾一下本质:取任意自然数 n。如果它是偶数,则除以 2。如果它是奇数,则乘以 3 再加 1。该猜想断言,任何数字最终都会变为 1。程序员的任务是尽可能在更大的数字范围内验证这一点。
经典的 C++ 实现如下所示:
\n\n#include <iostream>\n\nint collatz_steps(long long n) {\n int steps = 0;\n while (n != 1) {\n if (n % 2 == 0) {\n n /= 2;\n } else {\n n = 3 * n + 1;\n }\n ++steps;\n }\n return steps;\n}\n\nint main() {\n long long max_steps = 0;\n long long best_num = 0;\n for (long long i = 1; i <= 1000000; ++i) {\n int s = collatz_steps(i);\n if (s > max_steps) {\n max_steps = s;\n best_num = i;\n }\n }\n std::cout << "数字: " << best_num << " 步数: " << max_steps;\n}\n\n\n看起来并不复杂。但正是这段简单的代码引发了一场与汇编的传奇争论。
\n\n为什么汇编会输?2025 年的主要错误
\n\n当开发人员编写汇编代码时,他们常常会想:“我知道处理器是如何工作的,我会写出完美的代码。” 但现代处理器不仅仅是指令执行器。它们是复杂的超标量流水线,具有分支预测、推测执行和缓存。C++ 编译器(GCC、Clang、MSVC)完全了解其目标架构。
\n\n错误 #1:忽略分支优化
\n\n在手写汇编中,程序员通常直接编写分支:
\n; 伪汇编\ncheck_even:\n test rax, 1\n jnz is_odd\n shr rax, 1\n jmp next\nis_odd:\n mov rbx, rax\n shl rax, 1\n add rax, rbx\n inc rax\nnext:\n ...\n\n带有 -O3 标志的 C++ 编译器则更聪明。它使用 CMOV(条件移动)和位运算魔法。用算术运算代替分支:
; GCC 14 会生成类似这样的代码:\n mov rcx, rax\n shr rcx, 1 ; 除以 2(如果是偶数)\n and eax, 1 ; 检查最低位\n lea rdx, [rax + rcx*2 + 1] ; 奇数的巧妙计算\n test eax, eax\n cmove rax, rcx ; 如果是偶数 - 取除法结果\n cmovne rax, rdx ; 如果是奇数 - 取 3n+1 结果\n\n\n结果:没有因错误预测跳转而导致的流水线停顿。在 2025 年,这一点至关重要,因为 CPU 中的分支预测器虽然变得更智能,但对于随机数仍然不完美。
\n\n错误 #2:忘记向量化(AVX-512)
\n\n现代 C++ 编译器(尤其是 Clang 18+ 和 GCC 14+)会自动向量化循环。如果您编写一个简单的检查循环,编译器可以在一个时钟周期内同时处理 8 个数字,使用