引言:一个流传数十年的迷思
\n\n如果你曾经浏览过 Stack Overflow 的 C++ 板块,很可能见过这个问题:“< 比 <= 更快吗?”。这个问题积累了数千次浏览和数十页的讨论。这并不奇怪——代码优化 这个话题一直让各个水平的程序员都为之着迷。
\n\n这个逻辑看似坚不可摧:运算符 < 比较两个数并返回结果,而 <= 做同样的事情,但 额外检查了相等性。因此,< 应该运行得更快,对吗?尤其是在 循环 中,比较操作要执行数百万次。
今天,我们将一劳永逸地澄清这个迷思。你将了解到:
\n- \n
- 这些运算符在处理器层面究竟是如何工作的。 \n
- 为什么编译器是你最好的朋友(或者如果你不信任它,它也可能是你的敌人)。 \n
- 什么时候 微优化 有意义,什么时候是浪费时间。 \n
< 和 <= 运算符在汇编层面是如何工作的?
\n\n要理解是否存在差异,我们需要“深入引擎盖”——看看编译器将你的 C++ 代码转换成了什么。考虑两个简单的函数:
\n\nbool less_than(int a, int b) {\n return a < b;\n}\n\nbool less_or_equal(int a, int b) {\n return a <= b;\n}\n\n我们用 g++ 编译它们(不进行优化,使用 -O0 标志)。以下是我们在汇编清单(x86-64)中看到的内容:
less_than(int, int):\n cmp edi, esi ; 比较 a 和 b\n setl al ; 如果 a < b(小于),则设置 al=1\n ret\n\nless_or_equal(int, int):\n cmp edi, esi ; 比较 a 和 b\n setle al ; 如果 a <= b(小于或等于),则设置 al=1\n ret\n\n看到了吗?唯一的区别是指令 setl(如果小于则设置)和 setle(如果小于或等于则设置)。两条指令都只需要 一个 CPU 时钟周期 来执行。在现代 CPU(Intel Core i7, AMD Ryzen)上,它们具有相同的延迟——1 个周期。
结论 #1: 在机器指令层面,< 和 <= 之间没有性能差异。到此为止。
那循环呢?这里总该有区别了吧!
\n\n大多数争论都围绕着 循环 展开。比较以下两种方法:
\n\n// 方法 1:使用 <\nfor (int i = 0; i < n; ++i) {\n // 执行某些操作\n}\n\n// 方法 2:使用 <=\nfor (int i = 0; i <= n - 1; ++i) {\n // 执行相同的操作\n}\n\n直觉告诉我们:第二种方法多了一次计算 n - 1。但让我们看看在 编译器优化 之后会发生什么。
我们用 g++ -O2 对 n = 1 000 000 000 运行一个基准测试:
\n\n#include <iostream>\n#include <chrono>\n\nint main() {\n const long long n = 1'000'000'000;\n volatile long long sink = 0; // 防止编译器优化掉循环\n\n auto start = std::chrono::high_resolution_clock::now();\n \n for (long long i = 0; i < n; ++i) {\n sink += i;\n }\n \n auto end = std::chrono::high_resolution_clock::now();\n std::cout << "时间 (<): " \n << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n << " ms\\n";\n\n start = std::chrono::high_resolution_clock::now();\n \n for (long long i = 0; i <= n - 1; ++i) {\n sink += i;\n }\n \n end = std::chrono::high_resolution_clock::now();\n std::cout << "时间 (<=): " \n << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n << " ms\\n";\n\n return 0;\n}\n\n在我的机器(i7-107