是``if`语句''在modulo之前和分配操作之前的冗余

Is the `if` statement redundant before modulo and before assign operations?

本文关键字:冗余 操作 分配 if 语句 modulo      更新时间:2023-10-16

考虑下一个代码:

unsigned idx;
//.. some work with idx
if( idx >= idx_max )
    idx %= idx_max;

可以简化为仅第二行:

idx %= idx_max;

并将取得相同的结果。


几次我遇到了下一个代码:

unsigned x;
//... some work with x
if( x!=0 )
  x=0;

可以简化为

x=0;

问题:

  • 使用if是否有任何意义?为什么?特别是手臂拇指指令集。
  • 可以省略这些if S吗?
  • 编译器有什么优化?

如果您想了解编译器在做什么,则只需拉起一些组件即可。我推荐此网站(我已经从问题中输入了代码(:https://godbolt.org/g/fwzzob。

第一个示例更有趣。

int div(unsigned int num, unsigned int num2) {
    if( num >= num2 ) return num % num2;
    return num;
}
int div2(unsigned int num, unsigned int num2) {
    return num % num2;
}

生成:

div(unsigned int, unsigned int):          # @div(unsigned int, unsigned int)
        mov     eax, edi
        cmp     eax, esi
        jb      .LBB0_2
        xor     edx, edx
        div     esi
        mov     eax, edx
.LBB0_2:
        ret
div2(unsigned int, unsigned int):         # @div2(unsigned int, unsigned int)
        xor     edx, edx
        mov     eax, edi
        div     esi
        mov     eax, edx
        ret

基本上,出于非常具体且逻辑上的原因,编译器将不是优化分支机构。如果整数部门的成本与比较大致相同,那么该分支将毫无意义。但是Integer Division(通常将模量与通常一起执行(实际上非常昂贵:http://www.agner.org/optimize/instruction_tables.pdf。数字因体系结构和整数大小而差异很大,但通常可能是15到接近100个周期的潜伏期。

通过在执行模量之前先进行分支机构,您实际上可以节省很多工作。但是请注意:编译器也不会在没有分支的情况下将代码转换为组件级别的分支。那是因为分支也有一个缺点:如果模量最终到底是必要的,那么您只是浪费了一点时间。

没有办法对正确的优化做出合理的确定,而无需知道idx < idx_max的相对频率是正确的。因此,编译器(GCC和Clang做同样的事情(选择以相对透明的方式映射代码,将此选择留在开发人员的手中。

,分支机构可能是一个非常合理的选择。

第二个分支应该完全毫无意义,因为比较和分配是可比成本的。也就是说,您可以在链接中看到编译器,如果编译器对变量有参考,他们仍然不会执行此优化。如果该值是局部变量(如您所示的代码(,则编译器将优化分支。

总的来说,第一件代码可能是合理的优化,第二个代码可能只是一个疲倦的程序员。

在许多情况下,编写具有它已经拥有的值的变量可能比读取它较慢,发现已经拥有所需的值并跳过写入。一些系统具有处理器缓存,该缓存将所有写入请求立即发送到内存。尽管今天的设计并不司空见惯,但它们曾经很普遍,因为它们可以提供完整读取/写入缓存可以提供的性能提升的很大一部分,但是以一小部分成本提供。

像上面的代码在某些多CPU情况下也可能相关。最常见的情况是,当在两个或多个CPU内核上同时运行的代码将反复击中该变量。在具有强烈内存模型的多核缓存系统中,想要编写变量的核心必须首先与其他核心进行协商,以获取包含它的高速缓存线的独家所有权,然后必须再次协商以下次放弃此类控制任何其他核心都希望读取或编写它。这样的操作很容易变得非常昂贵,即使每本写入都只是存储已经持有的存储价值,也必须承担成本。但是,如果位置变为零并且再也不会写入,则两个核心都可以同时容纳无限制的阅读访问,而不必为此进行进一步协商。

在几乎所有情况下,多个CPU都可以击中变量,该变量应最少声明为volatile。一个例外,可能在此处适用,是在所有写入main()开始后发生的变量的情况下,将存储相同的值,并且代码在另一个CPU上是否可见任何商店是否可以在另一个CPU上看到任何商店。如果多次进行操作会很浪费但否则会无害,并且该变量的目的是说是否需要完成,那么许多实施可能能够在没有volatile预选赛的情况下生成更好的代码试图通过无条件的写作来提高效率。

顺便说一句,如果通过指针访问对象,将会有另一个上述代码的可能原因:如果函数被设计为接受一个 const对象,其中特定字段为零,或一个非const对象应该将该字段设置为零,如上所述的代码可能需要确保在这两种情况下确保定义的行为。

在代码的第一个块:这是基于钱德勒·卡鲁斯(Chandler Carruthinfo(,但是,不一定认为它将是此形式的有效微观优化(如果而不是三元(或任何给定的编译器。

modulo是一个相当昂贵的操作,如果代码经常执行并且有强大的统计倾斜或条件的另一侧,则CPU的分支预测(给定现代CPU(将显着降低分支指令。

对我来说,使用如果有的话似乎是一个坏主意。

你是对的。无论是idx >= idx_max,它都将在idx %= idx_max之后的IDX_MAX下。如果idx < idx_max,它将不变,无论是否遵循IF。

您可能会认为在Modulo周围分支可能会节省时间,但真正的罪魁祸首是,当遵循分支机构时,将现代CPU管道供电时必须重置他们的管道,这花费了很多时间。与整数模型相比,不必遵循分支机构,它的成本大致与整数部门一样多。

编辑:事实证明,正如这里其他人所建议的那样,模量非常慢。这是一个检查这个完全相同的问题的人:CPPCON 2015:Chandler Carruth"调整C :基准,CPU,以及编译器!哦,我的!"(在此问题的另一个答案中链接的另一个如此问题中提出(。

这个家伙写了编译器,并认为没有分支机构会更快。但是他的基准证明了他错了。即使只花了20%的时间将分支放置,它也会更快地测试。

另一个原因不具有IF:要维护的一条较少的代码行,以及其他人使它的含义令人困惑。上面链接中的那个人实际上创建了一个"更快的模量"宏。恕我直言,此或直列功能是进行关键绩效应用程序的一种方式,因为如果没有分支机构,您的代码将变得更加易于理解,但会尽快执行。

最后,上面视频中的那个人正计划使编译器作家已知这种优化。因此,如果不是在代码中,则可能会为您添加IF。因此,只有mod就会出现。