【问题标题】:conditional vs operator?条件与运算符?
【发布时间】:2014-01-11 15:25:17
【问题描述】:

在 GCC(4.8.2 版)手册中,声明如下:

-ftree-loop-if-convert-stores:
也尝试 if-convert 包含内存的条件跳转 写道。这种转换对于多线程来说可能是不安全的 程序,因为它将条件内存写入转换为 无条件内存写入。例如,

   for (i = 0; i < N; i++)
      if (cond)
        A[i] = expr;

转化为

   for (i = 0; i < N; i++)
       A[i] = cond ? expr : A[i];

可能产生数据竞争。

不过,我想知道使用 operator?if 语句相比是否会提高性能。

  • 在第一段代码中,A[i] 设置为expr 满足条件。如果不满足,则跳过语句内的代码。
  • 在第二个中,A[i] 似乎不管条件都写了;该条件仅影响其设置的值。

通过使用operator?,我们也在做检查;但是,在不满足条件的情况下,我们会增加一些开销。我错过了什么吗?

【问题讨论】:

  • 我认为第二种形式更快,因为它避免了branch prediction 失败的任何机会。详情请见this previous question
  • 这是一个灰色地带,因为它混合了供应商的多线程模型、代码生成和 C。您需要了解所有这三个模型才能理解这一点。
  • 只写可读的代码。不要尝试让优化器成为第二个客人。在完成这项任务方面,这比你或我要好得多
  • 非常令人担忧:测试同一输入数组上的两个结构,我得到1000000 loops: #1 = 4.428852 sec, #2 = 5.061400 sec。 (当然不使用标志。)这需要检查生成的汇编程序...
  • (在我的测试中)唉:第一个转换为testb $1, (%rdx); je LBB1_5; movb %dil, (%rsi),第二个转换为相同的代码,但也有额外的写入,中间有额外的跳转。这并没有说明什么新鲜事。

标签: c++ c gcc if-statement conditional-operator


【解决方案1】:

所谓的条件跳转被转换为条件移动指令,即cmove 系列指令。它们提高了速度,因为它们不会像跳转那样停止处理器流水线。

使用跳转指令,您预先不知道要加载哪些指令,因此使用预测并在管道中加载分支。如果预测正确,一切都很好,下一条指令已经在管道上执行。但是,在跳转评估之后,如果预测错误,那么流水线中已经存在的所有后续指令都是无用的,因此必须释放流水线,并加载正确的指令。现代处理器包含 16-30 级管道,并且分支错误预测会严重降低性能。条件移动绕过了这一点,因为它们不会在程序流中插入分支。

但是 cmove 总是写吗?

来自 Intel x86 指令集参考:

CMOVcc 指令检查 EFLAGS 寄存器 [..] 中一个或多个状态标志的状态,并在标志处于指定状态(或条件)时执行移动操作。 [..] 如果条件不满足,移动 不执行,继续执行 CMOVcc 指令之后的指令。

编辑

在进一步研究 gcc 手册后,我感到困惑,因为据我所知,编译器不会优化将 C 代码转换为另一个 C 代码,而是使用控制流图等内部数据结构,所以我真的不知道是什么他们的意思是他们的榜样。我想它们是指生成的新流的 C 等效项。我不确定这个优化是否是关于生成cmoves

编辑 2

由于cmove 使用寄存器而不是内存进行操作,因此

if (cond)
  A[i] = expr

无法生成cmove

不过这个

 A[i] = cond ? expr : A[i];

可以。

假设我们在bx 中有expr 值。

load A[i] into ax
cmp // cond
cmove ax, bx
store ax into &A[i]

因此,为了使用cmove,您必须读取 A[i] 值并将其写回 if cond if false,这与 if 语句不等效,而是与三元运算符等效。

【讨论】:

  • 但是cmove 总是吗?此选项中的警告似乎是 A[i] 总是被写入。
  • 请记住,除非A[i]volatile,否则无论是否执行移动都没有实际意义。对于非易失性,在没有数据竞争或其他 UB 的情况下可观察到的行为是相同的。所以当代码生成器看到转换后的代码带有?时,它可以在此基础上决定是否使用cmove指令。
  • 我不明白,不熟悉这种优化,代码生成器和/或矢量化过程在看到if (cond) A[i] = expr; 时是否不能只使用 cmove。这让我想知道这个优化标志是否真的像你所说的那样与 cmove 有任何特别的关系,或者它是否有助于“从最内层循环中删除控制流以提高矢量化传递的能力处理这些循环”(这是-ftree-loop-if-convert 的声明意图)。
  • @harold:bolov 的最新编辑表明它确实很重要。使用if 构造,内存可能不会被写入,而使用cmov总是需要。
  • @harold:但 警告 是该标志会将可选写入转换为“始终”写入。这与 bolov 建议的汇编代码完全一致。
猜你喜欢
  • 2021-05-07
  • 2023-03-03
  • 1970-01-01
  • 2020-04-22
  • 2010-11-29
  • 2017-12-03
  • 2013-03-22
  • 1970-01-01
相关资源
最近更新 更多