C++:为什么这会加速我的代码?

Aly*_*Aly 4 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_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];
}
Run Code Online (Sandbox Code Playgroud)

这被称为大约一百五十万次,具有不同的patch_top_left_rowpatch_top_left_col.这需要大约2秒的时间来运行,现在当我将first_pixel_row等的计算更改为不使用参数而是使用硬编码数字(如下所示)时,事物运行次秒,我不知道为什么.编译器是否在这里做了一些聪明的事情(我正在使用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 = 5 + random_values[0];
        int first_pixel_col = 6 + random_values[1];
        int second_pixel_row = 8 + random_values[2];
        int second_pixel_col = 10 + 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];
}
Run Code Online (Sandbox Code Playgroud)

编辑:

我使用参数从函数的两个版本粘贴了程序集:http://pastebin.com/tpCi8c0F 使用常量:http://pastebin.com/bV0d7QH7

编辑:

用-O3编译后,我得到以下时钟滴答和速度:

使用参数:1990000 ticks和1.99seconds使用常量:330000 ticks和0.33seconds

编辑:使用带有-03编译的参数:http://pastebin.com/fW2HCnHc 使用常量-03编译:http://pastebin.com/FHs68Agi

Omn*_*ous 5

在x86平台上,有一些指令可以非常快速地将小整数添加到寄存器中.这些指令是lea(也称为"加载有效地址")指令,它们用于计算结构的地址偏移等.添加的小整数实际上是指令的一部分.智能编译器知道这些指令非常快,即使不涉及地址也可以使用它们进行添加.

我敢打赌,如果你将常数改为一个至少24位长的随机值,你会看到很多加速消失.

其次,这些常数是已知值.编译器可以做很多事情来安排这些值以尽可能最有效的方式结束寄存器.使用参数,除非参数在寄存器中传递(并且我认为您的函数有太多参数用于使用该调用约定),编译器别无选择,只能使用堆栈偏移加载指令从内存中获取数字.这不是一个特别慢的指令或任何东西,但对于常量,编译器可以自由地做更快的事情,而不是简单地从指令本身获取数字.该lea指令是简单的这种最极端的例子.

编辑:现在你已经粘贴了组件,事情就更加清晰了

在非常量代码中,以下是添加的完成方式:

addl    -68(%rbp), %eax
Run Code Online (Sandbox Code Playgroud)

这将从堆栈中获取一个偏移值-68(%rpb)并将其添加到%eax%寄存器中.

在常量代码中,以下是添加的完成方式:

addl    $5, %eax
Run Code Online (Sandbox Code Playgroud)

如果你看实际的数字,你会看到:

0138 83C005
Run Code Online (Sandbox Code Playgroud)

很明显,添加的常量作为一个小值直接编码到指令中.由于多种原因,获取比从堆栈偏移中获取值要快得多.首先它更小.其次,它是没有分支的指令流的一部分.所以它将是预取和流水线的,不存在任何类型的高速缓存停顿.

因此,虽然我对lea指令的猜测不正确,但我还是走在了正确的轨道上.常量版本使用专门用于向寄存器添加小整数的小指令.非常量版本必须获取一个可能具有不确定大小的整数(因此它必须从堆栈偏移量中获取所有位,而不仅仅是低位)(这会增加一个额外的加法来计算来自的实际地址)偏移量和堆栈基地址).

编辑2:现在您已发布-O3结果

嗯,现在更令人困惑.它显然内联了有问题的函数,它在内联函数的代码和调用函数的代码之间跳了一整圈.我将需要查看整个文件的原始代码以进行适当的分析.

但我现在强烈怀疑的是,从中检索到的值的不可预测性get_random_number_in_range严重限制了编译器可用的优化选项.事实上,它似乎在常量版本中甚至懒得调用,get_random_number_in_range因为值被抛出并且从未使用过.


我假设在某个循环中生成patch_top_left_row和的值patch_top_left_col.我会把这个循环推到这个函数中.如果编译器知道值是作为循环的一部分生成的,那么就会有大量的优化选项.在极端情况下,它可以使用一些SIMD指令,这些指令是各种SSE或3dnow的一部分!指令套件使得事情比使用常量的版本更快.

另一种选择是使这个函数内联,这将暗示编译器应该尝试将它插入到调用它的循环中.如果编译器接受提示(此函数有点大,因此编译器可能不会),它将具有与将循环填充到函数中相同的效果.