强制编译器不优化无副作用的语句

时间:2009-07-20 08:32:28

标签: c++ optimization

我正在阅读一些旧的游戏编程书籍,正如你们中的一些人可能知道的那样,在那一天,做一些黑客攻击通常比以标准方式做事更快。 (将float转换为int,屏蔽符号位,转换回绝对值,而不是仅调用fabs(),例如)

现在使用标准库数学函数几乎总是更好,因为这些微小的东西无论如何都不是导致大多数瓶颈的原因。

但我仍然想做一个比较,只是出于好奇心的缘故。所以我想确保在我描述时,我没有得到偏差的结果。因此,我想确保编译器不会优化没有副作用的语句,例如:

void float_to_int(float f)
{
    int i = static_cast<int>(f); // has no side-effects
}

有办法做到这一点吗?据我所知,做i += 10这样的事情仍然没有副作用,因此无法解决问题。

我唯一能想到的是拥有一个全局变量int dummy;,并且在执行类似dummy += i之类的操作之后,使用i的值。但我觉得这种虚拟操作会妨碍我想要的结果。

我正在使用Visual Studio 2008 / G ++(3.4.4)。

修改

为了澄清,我希望最大限度地优化所有优化,以获得良好的个人资料结果。问题在于,没有副作用的陈述将被优化出来,因此就是这种情况。

再次编辑

再次澄清,请阅读: 我不是要尝试在某种生产代码中对此进行微观优化。

我们都知道旧的伎俩不再有用了,我只是好奇 对他们没有用处。只是好奇心。当然,如果没有我知道这些旧的黑客对现代CPU的表现如何,生活可以继续下去,但知道它永远不会伤害。

所以告诉我“这些技巧不再有用了,不要试图微观优化等等”是一个完全忽略这一点的答案。我知道它们没用,我不使用它们。

过早引用Knuth是所有烦恼的根源。

11 个答案:

答案 0 :(得分:7)

永远不应该优化volatile变量的分配,因此这可能会为您提供所需的结果:

static volatile int i = 0;

void float_to_int(float f)
{
    i = static_cast<int>(f); // has no side-effects
}

答案 1 :(得分:6)

  

所以我想确保在我描述时,我没有得到偏差的结果。因此,我想确保编译器不优化out语句

根据定义,你倾向于结果。

以下是如何修复尝试分析您编写的“虚拟”代码以进行测试的问题:对于分析,将结果保存到全局/静态数组并打印数组的一个成员到程序结束时的输出。编译器将无法优化 out 任何在数组中放置值的计算,但您仍然可以获得任何其他优化以使代码快速生成。

答案 2 :(得分:6)

在这种情况下,我建议你让函数返回整数值:

int float_to_int(float f)
{
   return static_cast<int>(f);
}

然后,您的调用代码可以使用printf来执行它,以确保它不会优化它。还要确保float_to_int位于单独的编译单元中,因此编译器无法播放任何技巧。

extern int float_to_int(float f)
int sum = 0;
// start timing here
for (int i = 0; i < 1000000; i++)
{
   sum += float_to_int(1.0f);
}
// end timing here
printf("sum=%d\n", sum);

现在将它与一个空函数进行比较,如:

int take_float_return_int(float /* f */)
{
   return 1;
}

哪个也应该是外部的。

时间的差异应该让你知道你想要衡量的费用。

答案 3 :(得分:4)

不幸的是,如果代码表现得好像没有进行优化,即使没有任何显式切换,编译器也可以根据自己的喜好进行优化。但是,如果您指示稍后可能会使用该值,则通常会欺骗他们不这样做,因此我会将您的代码更改为:

int float_to_int(float f)
{
    return static_cast<int>(f); // has no side-effects
}

正如其他人所建议的那样,你需要检查一下assemnler输出来检查这种方法是否真的有效。

答案 4 :(得分:3)

到目前为止,我用过的所有编译器总是有用的东西:

extern volatile int writeMe = 0;

void float_to_int(float f)
{    
  writeMe = static_cast<int>(f); 
}

请注意,这会导致结果偏差,boith方法应该写入writeMe

volatile告诉编译器“可能在没有通知的情况下访问该值”,因此编译器不能省略计算并删除结果。要阻止输入常量的传播,您可能还需要通过extern volatile来运行它们:

extern volatile float readMe = 0;
extern volatile int writeMe = 0;

void float_to_int(float f)
{    
  writeMe = static_cast<int>(f); 
}

int main()
{
  readMe = 17;
  float_to_int(readMe);
}

尽管如此,读取和写入之间的所有优化都可以“全力”应用。在检查生成的程序集时,对全局变量的读写通常是很好的“fenceposts”。

如果没有extern,编译器可能会注意到从未采用对变量的引用,因此确定它不能是volatile。从技术上讲,使用链接时间代码生成,它可能还不够,但我还没有发现 的编译器。 (对于确实删除了访问权限的编译器,需要将引用传递给运行时加载的DLL中的函数)

答案 5 :(得分:2)

函数调用会产生相当多的开销,所以无论如何我都会删除它。

添加虚拟+ = i;没问题,只要你在备用配置文件中保留相同的代码。 (所以你要比较它的代码)。

最后但并非最不重要:生成asm代码。即使您无法在asm中编码,生成的代码通常也是可以理解的,因为它后面会有标签和注释的C代码。所以你知道(sortoff)会发生什么,以及保留哪些位。

[R

P.S。发现了这个:

 inline float pslNegFabs32f(float x){
       __asm{
         fld  x //Push 'x' into st(0) of FPU stack
         fabs
         fchs   //change sign
         fstp x //Pop from st(0) of FPU stack
        }
       return x;
 } 
据说也很快。您可能也想对此进行分析。 (虽然它几乎不是便携式代码)

答案 6 :(得分:2)

您只需要跳到学习内容的部分,然后阅读已发布的Intel CPU optimisation manual

这些非常清楚地表明在float和int之间进行转换是一个非常糟糕的主意,因为它需要从int寄存器到内存的存储,然后加载到float寄存器中。这些操作会在管道中产生气泡并浪费许多宝贵的周期。

答案 7 :(得分:1)

返回值?

int float_to_int(float f)
{
    return static_cast<int>(f); // has no side-effects
}

然后在呼叫站点,您可以将所有返回值相加,并在基准测试完成后打印出结果。通常的做法是以某种方式确保你依赖于结果。

您可以使用全局变量,但似乎会产生更多缓存未命中。通常,只需将值返回给调用者(并确保调用者实际上对其执行某些操作)就可以解决问题。

答案 8 :(得分:1)

如果您使用的是Microsoft的编译器- cl.exe ,则可以使用以下语句在每个功能级别[link to doc]上启用/禁用优化。

#pragma optimize("" ,{ on |off })

关闭对当前行之后定义的函数的优化:

#pragma optimize("" ,off)

重新打开优化:

#pragma optimize("" ,on)

例如,在下图中,您会注意到3件事情。

  1. 设置了编译器优化标记-/O2,因此代码将得到优化
  2. 对第一个功能- square() 的优化已关闭,然后在开启之前将其打开 square2() 已定义。
  3. 为第一个功能生成的汇编代码数量较多。在第二个函数中,没有为代码中的int i = num;语句生成汇编代码。

因此,虽然第一个功能未优化,但第二个功能却未优化。

compiler explorer 请参见https://godbolt.org/z/qJTBHg,以获取在编译器资源管理器上与此代码的链接。

gcc 也存在类似的指令-https://gcc.gnu.org/onlinedocs/gcc/Function-Specific-Option-Pragmas.html

答案 9 :(得分:0)

围绕此声明的微观基准将无法代表在真实场景中使用此方法;周围的指令及其对管道和缓存的影响通常与任何给定的语句本身一样重要。

答案 10 :(得分:0)

GCC 4现在做了很多微优化,GCC 3.4从未做过。 GCC4包含一个树矢量化器,它可以很好地利用SSE和MMX做好非常好的工作。它还使用GMP和MPFR库来帮助优化对sin()fabs()等内容的调用,以及优化对其FPU,SSE或3D的调用!等同物。

我知道英特尔编译器在这些优化方面也非常擅长。

我的建议是不要担心像这样的微优化 - 在相对较新的硬件上(过去5年或6年内建造的任何东西),它们几乎完全没有实际意义。

编辑:在最近的CPU上,FPU的fabs指令比转换为int和位掩码快得多,fsin指令通常比预先计算更快表或推断泰勒系列。你会发现许多优化,例如“游戏编程大师的技巧”,完全没有实际意义,正如另一个答案所指出的那样,可能比FPU和SSE上的指令慢。

所有这一切都是因为较新的CPU是流水线的 - 指令被解码并分派到快速计算单元。指令不再以时钟周期运行,并且对高速缓存未命中和指令间依赖性更敏感。

查看AMD和英特尔处理器编程手册,了解所有细节。