我有一个代码段,很简单:
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-report,在这两种情况下,循环都是矢量化的,并且似乎做了相同的优化。
所以我的问题是,什么可以对一个没有被调用一百万次但本身很耗时的函数产生如此大的影响?在选择报告中要寻找什么?
我可以通过保持内联来避免它,但为什么让我烦恼。
更新:
我应该强调,我主要关心的是理解为什么当转移到单独的函数时它会变得更慢。使用 tmp 变量给出的代码示例只是我在过程中遇到的一个奇怪的例子。
您可能缺少寄存器,编译器必须加载和存储。我非常确定本机 x86 汇编指令可以使用内存地址进行操作,即编译器可以保持这些寄存器空闲。但是通过将其本地化,您“可能”会改变行为。别名和编译器可能无法证明更快的版本具有相同的语义,特别是如果这里存在某种形式的多线程,允许它更改代码。 函数在新段中速度较慢,可能是因为函数调用不仅会破坏管道,还会导致指令缓存性能较差(参数推送/弹出等有额外的代码)。
教训:让编译器来做优化,它比你聪明。我并不是说这是一种侮辱,它也比我聪明。但实际上,尤其是英特尔编译器,这些人知道他们在针对自己的平台时在做什么。
编辑:更重要的是,您需要认识到编译器的目标是优化未优化的代码。它们的目的不是识别半优化的代码。具体来说,编译器将为每个优化设置一组触发器,如果您碰巧以不触发它们的方式编写代码,则可以避免执行优化,即使代码在语义上是相同的。
而且您还需要考虑实施成本。并非每个适合内联的函数都可以内联 - 只是因为内联该逻辑对于编译器来说太复杂而无法处理。我知道 VC++ 很少会内联循环,即使内联会带来好处。您可能会在英特尔编译器中看到这一点 - 编译器编写者只是认为不值得花时间来实现。 我在处理 VC++ 中的循环时遇到了这种情况 - 编译器会以稍微不同的格式为两个循环生成不同的程序集,即使它们都实现了相同的结果。当然,他们的标准库使用了理想的格式。您
可以通过使用
std::for_each
和函数对象观察到加速。
你是对的,编译器应该能够将其识别为未使用的代码并将其删除/不编译它。这并不意味着它实际上确实识别并删除了它。如果您检查并发现代码未删除,您可能需要向英特尔编译器团队报告该情况。听起来他们可能有错误。