1

我对以下代码片段的流程有一个简单的疑问。我在高级和汇编指令级别上比较了这个代码块。我发现这?:比分支要好得多。

const int THUMB_IMAGE = 0;
const int ICON_IMAGE = 1;
const int UNKNOWN_IMAGE = 2;

void foo( int nFlag ){
    int CopyFlag = nFlag;
    if( CopyFlag == THUMB_IMAGE )
        CopyFLag = ICON_IMAGE; // Setting THUMB and ICON images to same level
    // Executing rest of the code
}

void foo1( int nFlag ){
    int CopyFlag = ( nFlag == THUMB_IMAGE ) ?
                     ICON_IMAGE : nFlag; // Setting THUMB and ICON images to same level
    // Executing rest of the code
}

int main(void){
   foo( THUMB_IMAGE );
   foo1( THUMB_IMAGE );
   return 0;
}

在上面的代码片段中,有两个函数,foo()foo1(). 这两个函数将两种图像类型设置为ICON_IMAGE

问题是如何分配和if()执行?

哪些条件语句是高度优化的if()或三元运算符?:

在汇编代码级别,翻译if(), CMP(分支)和MOV指令是必要的。对于?:操作员,我认为有一些特殊的指令,但完全避免了分支指令。

谁能告诉哪个是最好的方法?

foo()中,无论 if 条件如何,都会完成第一个赋值操作。它可能并非一直都需要。

但是在 中foo1(),这是在?:操作符部分完成的,避免了不需要的赋值操作。我想知道foo()或中的代码foo1()是否经过优化?

4

9 回答 9

2

如果不进行优化,您会copyFlag以编写 . 的方式设置两次,而if使用?. 等效于您的?:使用if将是:

int copyFlag;
if ( nFlag == thumbImage ) 
    copyFlag = iconImage;
else
    copyFlag = nFlag;

通过优化,我希望所有三个变体都生成或多或少相同的代码。

一般来说,你不应该担心这种事情。最重要的是,我不会担心汇编程序。在这两种情况下都可以使用相同的优化。(可以在没有任何分支指令的情况下编写它;这是否是最佳解决方案由编译器决定,而不是您。)优化器会处理它。您应该尽可能以最清晰的方式编写代码——在这种情况下,这意味着三元运算符。

于 2013-02-28T08:45:33.860 回答
1

if在一个体面的编译器上,和之间的效率绝对没有区别?:

这两种做同一件事的方式之间的唯一区别是?:包含微妙的提升规则。第二个和第三个表达式相互平衡,就好像它们是同一操作的操作数一样。如果你不走运,这可能会导致错误,但它的效率可能不会?:低于if.

例如,如果你写int x = 1;thenx ? 1 : 1.0f;那么结果类型是float,这可能是出乎意料的。

于 2013-02-28T08:48:30.173 回答
1

对于x86系列,关闭优化的编译器将转换为x86if... else...中带有跳转指令的分支,并转换为x86...?...:...中的条件移动指令。

如果您像在代码中一样直接编写条件,那么好的编译器会将分支优化为条件移动。

就性能而言,如果易于预测,分支会更好,如果难以预测,则条件移动会更好。原因是如果预测不正确,分支指令会在现代处理器中导致显着的分支预测损失,而无论条件是否成立,条件移动指令的延迟都会稍慢一些。

在此处查看我的答案以获取详细说明:

于 2013-02-28T08:49:04.190 回答
1

我 100% 确信任何现代编译器都会将这两者优化为相同的东西。当然,在这个特定的示例中,编译器可能会删除 and 的所有代码foofoo1因为这些代码在此示例中没有任何作用,除非您关闭优化 - 这不是一个公平的比较。您需要一个更复杂的示例,其中编译器无法确定输入并需要代码的结果。

现在,如果我对这段代码所做的事情是正确的——即复制某种图像——那么我希望函数开头的 if 语句在这个函数中花费的总时间非常少。

与性能一样,首先分析您的代码并识别“热点”,然后集中精力使热点运行得更快。我很确信这不是一个热点。因此,您正在尝试优化不会对您的代码产生重大影响的东西。

此外,不要相信互联网上的某个人会告诉您哪个版本的代码更快 - 在不同的配置下运行它,然后做出决定。最好在具有不同硬件(在这种特殊情况下是不同制造商的处理器)的多台机器上 - 除非它是一个只需要在您当前的计算机上快速运行的爱好项目。

于 2013-02-28T09:15:41.390 回答
0

仅当您关闭优化时,才可能存在性能差异。现代编译器应该平等地优化这两个版本。

选择其中一种解决方案的原因可能是:

  1. 可读性。但是,个人偏好可能会有所不同。
  2. 调试版本性能。但是,差异应该很小。
  3. 易于调试。不可能在三元运算符的某个分支处设置断点(除非您进入汇编语言级别,否则有时可以)。
  4. 覆盖分析。如果您编写测试并收集覆盖分析信息,if则语句可以让您了解两个分支是否都经过测试。三元运算符没有。在程序运行期间根本无法评估其中一个表达式,但代码覆盖率工具会显示所有内容都被覆盖。
于 2013-02-28T11:13:22.697 回答
0

所有其他答案,表明一个体面的编译器不会关心,并且除非您看到此函数在分析器中弹出作为性能瓶颈,否则您可能不应该担心,是完全正确的。

但是...如果您在某些架构上分支很昂贵,并且不存在条件移动,或者您的编译器真的很糟糕...您可以将原始版本与以下内容进行比较:

void foo( int nFlag ){
    int CopyFlag = ((nFlag & 2) << 1) + 1;
    // Rest of code...

显然,这需要有很多警告(它很丑陋、hacky、不可维护等)......不过它会并行化!

然而,我真正得到的是,与其担心两种相似语言结构的确切代码生成,你应该(在确定你甚至有性能问题之后)看看算法本身,以及它是否可以更改或更换,以完全避免您遇到的问题。

于 2013-02-28T10:10:56.780 回答
0

理论上不应该有任何区别,理论上无所不知的优化器会将其优化为相同的生成代码。

在实践中,我知道 amd64 上至少某些版本的 gcc 将生成一个分支if并使用该cmov指令,:?而不管优化级别和标志如何。听起来像是一件小事,但在代码库中,我在一个与此类似的构造上测试了它,该构造用于一种锁定更改,在宏基准测试中if减少:?了 10% 的 cpu 使用率。

当然,您的里程可能会因编译器、编译器版本、标志和其他您无法控制的因素而有所不同。所以测试你的编译器,看看它是否值得优化。

于 2013-02-28T09:01:35.450 回答
0

使用优化编译器,您应该看不到任何差异。只需使用更具可读性的内容。

于 2013-02-28T09:14:11.637 回答
0

这取决于编译器。一些编译器会优化这两个语句并从中生成相同的汇编代码。

顺便说一句,为此使用最快的解决方案的优势非常小,不值得麻烦。如果您调用此代码来设置图像的类型,那么您很可能会在某处使用该图像(即使只是更改图像类型而不显示它们),这比在一个额外的汇编指令中花费更多的 CPU周期if?:声明。主要使用三元运算符编写代码?:可能会使开发和维护更加困难,而且不值得付出努力。

编辑:性能的差异来自您在foowhen nFlagis执行两项任务的事实THUMB_IMAGE。以下foo2在性能上应该更接近foo1.

void foo2( int nFlag ){
   int CopyFlag;
   if( nFlag == THUMB_IMAGE )
       CopyFlag = ICON_IMAGE; // Setting THUMB and ICON images to same level
   else
       CopyFlag = nFlag;
   // Executing rest of the code
}

性能差异可能来自编译器不是为了优化赋值和if将同一变量赋值到语句中的if-else语句(例如 Sun/Oracle Java 编译器这样做),或者编译器可能已被指示不要优化代码(通过编译标志/参数)。

于 2013-02-28T08:41:30.040 回答