我有一个代码段,它很简单:
for( int i = 0; i < n; ++i)
{
if( data[i] > c && data[i] < r )
{
--data[i];
}
}
它是大型功能和项目的一部分 . 这实际上是对不同循环的重写,这被证明是耗时的(长循环),但我对两件事感到惊讶:
当data [i]临时存储时,如下所示:
for( int i = 0; i < n; ++i)
{
const int tmp = data[i];
if( tmp > c && tmp < r )
{
--data[i];
}
}
它变得更慢 . 我并不认为这应该更快,但我不明白为什么它应该这么慢,编译器应该能够弄清楚是否应该使用tmp .
但更重要的是,当我将代码段移动到一个单独的函数时,它变得慢了四倍 . 我想了解发生了什么,所以我查看了opt报告,在这两种情况下,循环都是矢量化的,似乎做了同样的优化 .
所以我的问题是什么可以在一个不被称为百万次的函数上产生这样的差异,但它本身是耗时的?在opt报告中要查找什么?
我可以通过保持内联来避免它,但是为什么要烦我 .
更新:
我应该强调,我的主要关注点是要理解,为什么它变得更慢,当移动到一个单独的功能 . 使用tmp变量给出的代码示例只是我在此过程中遇到的一个奇怪示例 .
3 回答
你很可能确保本机x86汇编指令可以使内存地址运行 - 即,编译器可以保持这些寄存器空闲 . 但是通过将其设为本地,您可以改变行为 . 别名和编译器可能无法证明更快的版本具有相同的语义,特别是如果这里存在某种形式的多线程,允许它更改代码 .
可能因为函数调用不仅会破坏管道,而且还会产生较差的指令缓存性能(参数push / pop /等的额外代码),因此在新段中函数可能较慢 .
经验教训:让编译器进行优化,它比你聪明 . 我并不是说这是一种侮辱,它也比我更聪明 . 但实际上,尤其是英特尔编译器,这些人知道他们在定位自己的平台时正在做什么 .
编辑:更重要的是,您需要认识到编译器的目标是优化未优化的代码 . 它们没有被命中,即使代码在语义上相同,也可以避免执行优化 .
而且您还需要考虑实施成本 . 并非每个理想的内联函数都可以内联 - 只是因为内联逻辑过于复杂而无法让编译器处理 . 我知道VC很少会内联循环,即使内联会带来好处 . 您可能在英特尔编译器中看到这一点 - 编译器编写者只是认为不值得花时间实现 .
我在处理VC中的循环时遇到过这种情况 - 编译器会以略微不同的格式为两个循环生成不同的程序集,即使它们都实现了相同的结果 . 当然,他们的标准库使用了理想的格式 . 您可以使用
std::for_each
和一个函数对象观察加速 .你是对的,编译器应该能够将其识别为未使用的代码并将其删除/不编译 . 这并不意味着它确实识别并删除它 .
您最好的选择是查看生成的装配并检查确切的结果 . 请记住,仅仅因为聪明的编译器能够弄清楚如何进行优化,并不意味着它可以 .
如果您确实检查并看到代码未被删除,您可能需要向intel编译器团队报告 . 听起来他们可能有一个错误 .
我很惊讶这一点
完全编译 . 可能会混淆编译器 . 尝试
代替 . 通常使用size_t(uint)来循环 . Signed int与unsigned的编码不同,因此可能存在不必要的bitshift . 所以我试试
发布您的结果 .