【发布时间】:2014-08-29 21:18:41
【问题描述】:
我选择了 David 的答案,因为他是唯一一个在没有优化标志的情况下针对 for 循环中的差异提出解决方案的人。其他答案演示了设置优化标志时会发生什么。
Jerry Coffin 的回答解释了为本示例设置优化标志时发生的情况。仍然没有答案的是为什么 superCalculationA 比 superCalculationB 运行得慢,当 B 为每次迭代执行一个额外的内存引用和一个加法时。 Nemo 的帖子显示了汇编器的输出。根据 Matteo Italia 的建议,我在运行 Ubuntu 12.04 64 位的 PC 上使用 -S 标志确认了此编译,2.9GHz Sandy Bridge (i5-2310)。
当我偶然发现以下案例时,我正在试验 for 循环的性能。
我有以下代码以两种不同的方式进行相同的计算。
#include <cstdint>
#include <chrono>
#include <cstdio>
using std::uint64_t;
uint64_t superCalculationA(int init, int end)
{
uint64_t total = 0;
for (int i = init; i < end; i++)
total += i;
return total;
}
uint64_t superCalculationB(int init, int todo)
{
uint64_t total = 0;
for (int i = init; i < init + todo; i++)
total += i;
return total;
}
int main()
{
const uint64_t answer = 500000110500000000;
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
double elapsed;
std::printf("=====================================================\n");
start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationA(111, 1000000111);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationB(111, 1000000000);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
if (ret1 == answer)
{
std::printf("The first method, i.e. superCalculationA, succeeded.\n");
}
if (ret2 == answer)
{
std::printf("The second method, i.e. superCalculationB, succeeded.\n");
}
std::printf("=====================================================\n");
return 0;
}
用
编译这段代码g++ main.cpp -o 输出 --std=c++11
导致以下结果:
=====================================================
Elapsed time: 2.859 s | 2859.441 ms | 2859440.968 us
Elapsed time: 2.204 s | 2204.059 ms | 2204059.262 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
我的第一个问题是:为什么第二个循环的运行速度比第一个快 23%?
另一方面,如果我用
编译代码g++ main.cpp -o 输出 --std=c++11 -O1
结果进步了很多,
=====================================================
Elapsed time: 0.318 s | 317.773 ms | 317773.142 us
Elapsed time: 0.314 s | 314.429 ms | 314429.393 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
时间差几乎消失了。
但是当我设置 -O2 标志时我简直不敢相信自己的眼睛,
g++ main.cpp -o 输出 --std=c++11 -O2
得到了这个:
=====================================================
Elapsed time: 0.000 s | 0.000 ms | 0.328 us
Elapsed time: 0.000 s | 0.000 ms | 0.208 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
那么,我的第二个问题是:当我设置 -O1 和 -O2 标志导致这种巨大的性能提升时,编译器在做什么?
我检查了Optimized Option - Using the GNU Compiler Collection (GCC),但这并没有说明问题。
顺便说一句,我正在用 g++ (GCC) 4.9.1 编译这段代码。
编辑以确认 Basile Starynkevitch 的假设
我编辑了代码,现在main 看起来像这样:
int main(int argc, char **argv)
{
int start = atoi(argv[1]);
int end = atoi(argv[2]);
int delta = end - start + 1;
std::chrono::time_point<std::chrono::high_resolution_clock> t_start, t_end;
double elapsed;
std::printf("=====================================================\n");
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationB(start, delta);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationA(start, end);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
std::printf("Results were %s\n", (ret1 == ret2) ? "the same!" : "different!");
std::printf("=====================================================\n");
return 0;
}
这些修改确实增加了-O1 和-O2 的计算时间。现在两者都给了我大约 620 毫秒。 这证明 -O2 确实在编译时进行了一些计算。
我仍然不明白这些标志在提高性能方面做了什么,-Ofast 做得更好,大约 320 毫秒。
还请注意,我更改了调用函数 A 和 B 的顺序来测试 Jerry Coffin 的假设。在没有优化器标志的情况下编译这段代码仍然给我大约 2.2 秒的 B 和 2.8 秒的 A。所以我认为这不是缓存的事情。只是强调我不是谈论第一种情况下的优化(没有标志的情况),我只是想知道是什么让秒循环比第一种运行得更快。
【问题讨论】:
-
运行
g++和-S并检查程序集。 -
如果没有开启优化(你的第一种情况),比较时间是没有意义的,因为生成的代码几乎是直接将你的代码翻译成汇编。通过优化,编译器几乎可以肯定在这种情况下完全消除您的循环。
-
我猜想
-O2GCC 在编译时进行大部分计算。SuperCalculationA&SuperCalculationB的参数应该是可变的,例如通过程序参数给出(例如,main中的int init = atoi(argv[1]); int end = atoi(argv[2]);) -
我必须纠正自己:查看
-O0的程序集并不能说明问题。发出的程序集显然是一个幼稚的 C-> 程序集翻译,但是,尽管在非常相似的代码中做了 more 事情(并访问堆栈上的另一个位置),但事实证明superCalculationB更快(由探查器确认)。结果甚至在for循环中重复两次计算。 -
@jcmonteiro 感谢您选择我的答案。我做了更多的功课,现在认为我有一个更可靠的解释,没有任何谜团。请查看我修改后的答案。
标签: c++ performance gcc