如果Thens比乘法和赋值更快吗?

时间:2010-10-26 13:35:23

标签: c optimization ada branch-prediction

我有一个简单的问题,假设我有以下代码,例如,它以类似的方式重复10次。

if blah then
    number = number + 2^n
end if

评估会更快:

number = number + blah*2^n?

这也带来了问题,你可以将布尔值乘以整数(虽然我不确定从2 ^ n返回的类型,是整数还是unsigned..etc)? (我在Ada工作,但是我们可以试着概括一下吗?)

编辑:对不起我应该澄清我正在考虑n的力量,我把c放在那里因为我对将来学习感兴趣如果我在c中碰到这个问题并且我认为那里在这些主板上有更多的c程序员然后Ada(我假设你知道这意味着什么),但是我目前的问题是Ada语言,但问题应该是相当语言独立的(我希望)。

10 个答案:

答案 0 :(得分:10)

这个问题没有一般的答案,这在很大程度上取决于你的编译器和CPU。现代CPU有条件移动指令,所以一切皆有可能。

这里要知道的唯一方法是检查生成的汇编程序(通常-S作为编译器选项)并进行测量。

答案 1 :(得分:5)

在阿达......

原始配方:

if Blah then
  Number := Number + (2 ** N);
end if;

替代通用公式,假设Blah的类型为布尔值,而数字和N的类型合适:

Number := Number + (Boolean'pos(Blah) * (2 ** N));

(对于用户定义的整数或浮点类型的NNumber,可能需要合适的定义和类型转换,这里的关键点是Boolean'pos()构造,Ada保证将为预定义的布尔类型提供0或1。)

至于这是否更快,我同意@Cthutu:

  

我会保留条件。   你不应该担心低级别   此时优化细节。   写下描述你的代码   算法最好,相信你的   编译器。

答案 2 :(得分:5)

如果我们谈论C而且blah不在你的控制之内,那么就这样做:

if(blah) number += (1<<n);

C中确实没有布尔值,并且不需要,false为零且true不为零,因此您不能假设零不是1,这是您的解决方案所需要的,也不能假设blah中的任何特定位都被设置,例如:

number += (blah&1)<<n;

不一定有效,因为0x2或0x4或任何非零,清零位被认为是真的。通常你会发现0xFFF ... FFFF(减1或全部)用作true,但你不能依赖典型的。

现在,如果您完全控制了blah中的值,并将其严格保持为0表示false而1表示true,那么您可以执行您所询问的内容:

number += blah<<n;

避免分支,额外缓存行填充等的可能性

回到通用案例,采用这种通用解决方案:

unsigned int fun ( int blah, unsigned int n, unsigned int number )
{
    if(blah) number += (1<<n);
    return(number);
}

编译两个最受欢迎/最常用的平台:

    testl   %edi, %edi
    movl    %edx, %eax
    je  .L2
    movl    $1, %edx
    movl    %esi, %ecx
    sall    %cl, %edx
    addl    %edx, %eax
.L2:

以上使用条件分支。

下面的一个使用条件执行,没有分支,没有管道刷新,是确定性的。

  cmp   r0,#0
  movne r3,#1
  addne r2,r2,r3,asl r1
  mov   r0,r2
  bx    lr

可以通过在函数调用中重新排列参数来保存mov r0,r2指令,但这是学术性的,你通常不会在这个上烧掉函数调用。

编辑:

正如所建议:

unsigned int fun ( int blah, unsigned int n, unsigned int number )
{
    number += ((blah!=0)&1)<<n;
    return(number);
}
  subs  r0, r0, #0
  movne r0, #1
  add   r0, r2, r0, asl r1
  bx    lr

当然更便宜,而且代码看起来不错,但我不会假设blah!= 0的结果,即0或编译器定义为true的任何内容总是设置为lsbit。它不必为编译器设置该位以生成工作代码。也许标准规定了真实的具体价值。通过重新排列函数参数,if(blah)数字+ = ...也将导致三个单时钟指令而没有假设。

EDIT2:

看看我理解为C99标准:

  

==(等于)和!=(不等于   运营商类似于   关系运算符除了他们的   优先级较低。每一个   运算符如果指定则产生1   relation为true,如果为false,则为0。

这解释了为什么上面的编辑工作以及为什么你得到了movne r0,#1而不是其他随机数。

海报在询问关于C的问题,但也注意到ADA是当前的语言,从语言独立的角度来看,你不应该假设像上面的C特征这样的“特征”并使用if(blah)数字=数字+(1

海报假设基本上也是正确的,如果你可以把它变成0或1形式,那么在数学中使用它会更快,因为没有分支。如果没有它比分支更昂贵,那么将它变成那种形式就是诀窍。

答案 3 :(得分:4)

我会保留条件。此时您不应该担心低级优化细节。编写最能描述算法的代码并信任编译器。在某些CPU上,乘法较慢(例如,在每条指令上都有条件的ARM处理器)。你也可以使用?:表达式,它可以在某些编译器下更好地优化。例如:

number += (blah ? 2^n : 0);

如果出于某种原因,这个小计算是分析后应用程序的瓶颈,那么就会担心低级优化。

答案 4 :(得分:4)

在C中,关于blah * 2 ^ n:你有理由相信blah取值0和1吗?该语言仅承诺0&lt; - &gt; FALSE和(其他所有)&lt; - &gt;真正。 C允许您将“布尔”临时值与另一个数字相乘,但结果未定义,除非结果= 0&lt; =&gt; bool是假的,或者数字是零。

在Ada中,关于blah * 2 ^ n:语言没有在Boolean类型上定义乘法运算符。因此,blah不能成为一个布尔并且会成倍增加。

答案 5 :(得分:1)

如果你的语言允许在布尔值和数字之间进行乘法,那么是的,这比条件更快。条件需要分支,这可能使CPU的管道无效。此外,如果分支足够大,它甚至可能导致指令中的高速缓存未命中,尽管在您的小例子中这不太可能。

答案 6 :(得分:1)

通常,特别是在与Ada合作时,您不应该担心像这样的微优化问题。编写代码,以便读者清楚,只有在遇到性能问题时才会担心性能,并将其跟踪到代码的那一部分。

不同的CPU有不同的需求,它们可能非常复杂。例如,在这种情况下,更快的取决于CPU的管道设置,当时缓存中的内容以及分支预测单元的工作方式。编译器的一部分工作就是成为这方面的专家,除了最好的汇编程序员之外,它会做得更好。 Certianly比你(或我)更好。

所以你只需要担心编写好的代码,让编译器担心从中获取高效的机器代码。

答案 7 :(得分:1)

对于所述的问题,C中确实存在可以产生有效代码的简单表达式。

n运算符的2幂可以使用<<运算符计算为1 << n,前提是n小于int中的值位数blah

如果int布尔,即0,其值为1number += blah << n; ,则可以编写代码片段:

blah

如果if (blah)是可以测试其真值为number += !!blah << n; 的任何标量类型,则表达式稍微复杂一些:

number += (blah != 0) << n;

相当于l1 = [(['hello','world'],), (['stack','overflow'],), (['hello', 'alice'],), (['sample', 'text'],)] df1 = spark.createDataFrame(l1) l2 = [(['big','world'],), (['sample','overflow', 'alice', 'text', 'bob'],), (['hello', 'sample'],)] df2 = spark.createDataFrame(l2)

测试仍然存在但是,对于现代体系结构,生成的代码不会有任何跳转,因为可以使用Godbolt's compiler explorer在线验证。

答案 8 :(得分:0)

在任何一种情况下,你都无法避免分支(内部),所以不要试试!

number = number + blah*2^n

除非编译器足够聪明,否则当blah为0时,必须对整个表达式进行求值。如果是,如果blah为0,你将得到一个分支。如果不是,你总是得到一个昂贵的乘。如果blah为false,您还将获得不必要的添加和赋值。

在“if then”语句中,语句只会在blah为真时执行add和赋值。

简而言之,在这种情况下,您的问题的答案是“是”。

答案 9 :(得分:0)

此代码显示它们的表现相似,但乘法通常稍快。

@Test
public void manual_time_trial()
{
    Date beforeIfElse = new Date();
    if_else_test();
    Date afterIfElse = new Date();
    long ifElseDifference = afterIfElse.getTime() - beforeIfElse.getTime();
    System.out.println("If-Else Diff: " + ifElseDifference);

    Date beforeMultiplication = new Date();
    multiplication_test();
    Date afterMultiplication = new Date();
    long multiplicationDifference = afterMultiplication.getTime() - beforeMultiplication.getTime();
    System.out.println("Mult Diff   : " + multiplicationDifference);

}

private static long loopFor = 100000000000L;
private static short x = 200;
private static short y = 195;
private static int z;

private static void if_else_test()
{
    short diff = (short) (y - x);
    for(long i = 0; i < loopFor; i++)
    {
        if (diff < 0)
        {
            z = -diff;
        }
        else
        {
            z = diff;
        }
    }
}

private static void multiplication_test()
{
    for(long i = 0; i < loopFor; i++)
    {
        short diff = (short) (y - x);
        z = diff * diff;
    }
}