C++ C++;:为什么这会加速我的代码?
我有以下功能C++ C++;:为什么这会加速我的代码?,c++,performance,gcc,C++,Performance,Gcc,我有以下功能 double single_channel_add(int patch_top_left_row, int patch_top_left_col, int image_hash_key, Mat* preloaded_images, int* random_values){ int first_pixel_row = patch_top_left_row + random_values[0]; int first
double single_channel_add(int patch_top_left_row, int patch_top_left_col,
int image_hash_key,
Mat* preloaded_images,
int* random_values){
int first_pixel_row = patch_top_left_row + random_values[0];
int first_pixel_col = patch_top_left_col + random_values[1];
int second_pixel_row = patch_top_left_row + random_values[2];
int second_pixel_col = patch_top_left_col + random_values[3];
int channel = random_values[4];
Vec3b* first_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(first_pixel_row, first_pixel_col);
Vec3b* second_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(second_pixel_row, second_pixel_col);
return (*first_pixel_bgr)[channel] + (*second_pixel_bgr)[channel];
}
编辑:
我已经从函数的两个版本粘贴了程序集
使用参数:
使用常量:
编辑:
使用-O3编译后,我得到以下时钟信号和速度:
使用参数:1990000个刻度和1.99秒
使用常数:330000个刻度和0.33秒
编辑:
将argumenst与-03编译一起使用:
将常量与-03编译一起使用:好吧,
立即常量与内存
格式的二进制算术运算预计会比内存与内存
格式的二进制算术运算生成更快的代码,但您观察到的时间效应似乎过于极端,特别是考虑到该函数中还有其他操作
是否编译器决定内联您的函数?内联将允许编译器轻松消除与第二版本中未使用的patch\u top\u left\u row
和patch\u top\u left\u col
参数相关的所有内容,包括在调用代码中准备/计算这些参数的任何步骤
从技术上讲,即使函数不是内联的,也可以这样做,但它通常更复杂。在x86平台上,有一些指令可以非常快速地将小整数添加到寄存器中。这些指令是
lea
(又称“加载有效地址”)指令,用于计算结构等的地址偏移量。正在添加的小整数实际上是指令的一部分。智能编译器知道这些指令非常快,即使不涉及地址,也可以使用它们进行添加
我敢打赌,如果你将常数更改为至少24位长的随机值,你会看到大部分加速消失
其次,这些常数是已知值。编译器可以尽可能高效地安排这些值在寄存器中结束。对于参数,除非参数在寄存器中传递(我认为您的函数有太多的参数用于调用约定),否则编译器别无选择,只能使用堆栈偏移量加载指令从内存中获取数字。这不是一个特别慢的指令或任何东西,但是对于常量,编译器可以自由地做一些比从指令本身获取数字快得多的事情。lea
指令就是最极端的例子
编辑:既然你已经粘贴了组件,事情就清楚多了
在非常量代码中,以下是如何进行添加:
addl -68(%rbp), %eax
addl $5, %eax
这将从堆栈中获取偏移量-68(%rpb)
的值,并将其添加到%eax%
寄存器中
在常量代码中,以下是如何进行添加:
addl -68(%rbp), %eax
addl $5, %eax
如果你看一下实际的数字,你会发现:
0138 83C005
很明显,被添加的常量作为一个小值直接编码到指令中。这将比从堆栈偏移量获取值快得多,原因有很多。首先它比较小。其次,它是没有分支的指令流的一部分。因此,它将被预取和管道化,不可能出现任何类型的缓存暂停
因此,虽然我对lea
指令的猜测不正确,但我仍然走在正确的轨道上。常量版本使用一条专门用于向寄存器中添加小整数的小指令。非常量版本必须从堆栈偏移量中获取一个可能大小不确定的整数(因此它必须获取所有位,而不仅仅是低位位)(这将添加一个额外的add,以根据偏移量和堆栈基址计算实际地址)
编辑2:现在您已经发布了-O3
结果
现在更让人困惑了。它显然是内联函数,在内联函数的代码和调用函数的代码之间跳了一整吨。我需要查看整个文件的原始代码,以便进行适当的分析
但我强烈怀疑现在正在发生的是,从get\u range
中获取的值的不可预测性严重限制了编译器可用的优化选项。事实上,在常量版本中,它甚至不需要调用get\u range\u random\u number\u
,因为该值被抛出,从未使用过
我假设
patch\u top\u left\u row
和patch\u top\u left\u col
的值在某个地方的循环中生成。我将把这个循环推到这个函数中。如果编译器知道这些值是作为循环的一部分生成的,那么有很多优化选项可供选择。在极端情况下,它可以使用各种SSE或3dnow中的一些SIMD指令!指令套件使事情比使用常量的版本快一整吨
另一个选项是将此函数内联,这将提示编译器应该尝试将其插入调用它的循环中。如果编译器接受这个提示(这个函数有点大,所以编译器可能不会),它将产生与将循环填充到函数中几乎相同的效果。
gcc
具有可用于查看汇编语言输出的命令行选项。为什么不获取此处显示的两个代码段的程序集,diff
并在问题中发布生成的diff?您不能将其复制为一个,可以吗?请查看生成的程序集代码。在第二种情况下,编译器可能会使用一些SSE指令。另外,这两个实现似乎做的事情并不相同。为什么第二个示例5、6、8和10中的值