简体   繁体   English

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

[英]Are If Thens faster than multiplication and assignment?

I have a quick question, suppose I have the following code and it's repeated in a simliar way 10 times for example. 我有一个简单的问题,假设我有以下代码,并且它以类似的方式重复10次,例如。

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

Would it be faster to evaluate: 评估会更快吗?

number = number + blah*2^n?

Which also brings the question, can you multiply a boolean value times a integer (Although I am not sure the type that is returned from 2^n, is it an integer or unsigned..etc)? 这也带来了问题,你可以将一个布尔值乘以一个整数(虽然我不确定从2 ^ n返回的类型,是整数还是unsigned..etc)? (I'm working in Ada, but let's try to generalize this maybe?) (我在Ada工作,但是我们可以尝试概括一下吗?)

EDIT: Sorry I should clarify I am looking at 2 to the power of n, and I put c in there cause I was interested for my own learning in the future if I ever run into this problem in c and I think there are more c programmers out there on these boards then Ada (I'm assuming and you know what that means), however my current problem is in the Ada language, but the question should be fairly language independent (I hope). 编辑:对不起我应该澄清我正在考虑2的力量n,并且我把c放在那里因为我对将来学习感兴趣如果我遇到c中的这个问题并且我认为还有更多c那些程序员在这些板上然后Ada(我假设你知道这意味着什么),但是我目前的问题是Ada语言,但问题应该是相当语言独立的(我希望)。

There is no general answer to such a question, this depends a lot on your compiler and CPU. 这个问题没有一般的答案,这在很大程度上取决于你的编译器和CPU。 Modern CPU have conditional move instructions, so everything is possible. 现代CPU有条件移动指令,所以一切皆有可能。

The only ways to know here are to inspect the assembler that is produced (usually -S as compiler option) and to measure. 这里要知道的唯一方法是检查生成的汇编程序(通常是-S作为编译器选项)并进行测量。

In Ada... 在阿达...

The original formulation: 原始配方:

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

The alternative general formulation, assuming Blah is of type Boolean and Number and N are of suitable types: 另一种通用公式,假设Blah的类型为Boolean,Number和N属于合适的类型:

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

(For N and Number of user-defined integer or floating point types, suitable definitions and type conversions may be required, the key point here is the Boolean'pos() construct, which Ada guarantees will give you a 0 or 1 for the predefined Boolean type.) (对于NNumber可能需要用户定义的整数或浮点类型,合适的定义和类型转换的,关键的一点这里是Boolean'pos()构建体,其中阿达保证会给你一个0或1的预定义布尔类型。)

As for whether this is faster or not, I concur with @Cthutu: 至于这是否更快,我同意@Cthutu:

I would keep it with the conditional. 我会保留条件。 You shouldn't worry about low-level optimisation details at this point. 此时您不应该担心低级优化细节。 Write the code that describes your algorithm best and trust your compiler. 编写最能描述算法的代码并信任编译器。

if we are talking about C and blah is not within your control, then just do this: 如果我们在谈论C并且blah不在你的控制之内,那么就这样做:

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

There is really not a boolean in C and does not need to be, false is zero and true is not zero, so you cannot assume that not zero is 1 which is what you would need for your solution, nor can you assume that any particular bit in blah is set, for example: 在C中确实没有布尔值,并且不需要,false为零且true不为零,因此您不能假设零不是1,这是您的解决方案所需要的,也不能假设任何特定的设置为blah,例如:

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

Would not necessarily work either because 0x2 or 0x4 or anything non-zero with bit zero clear is considered a true. 不一定有效,因为0x2或0x4或任何非零,清零位被认为是真的。 Typically you will find 0xFFF...FFFF (minus one, or all ones) used as true, but you cannot rely on typical. 通常你会发现0xFFF ... FFFF(减1或全部)用作true,但你不能依赖典型的。

Now, if you are in complete control over the value in blah, and keep it strictly to a 0 for false and 1 for true then you could do what you were asking about: 现在,如果您完全控制了blah中的值,并将其严格保持为0表示false而1表示true,那么您可以执行以下操作:

number += blah<<n;

And avoid the potential for a branch, extra cache line fill, etc. 并避免分支,额外缓存行填充等的可能性。

Back to the generic case though, taking this generic solution: 回到通用案例,采用这种通用解决方案:

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

And compiling for the two most popular/used platforms: 并编译两个最受欢迎/使用的平台:

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

The above uses a conditional branch. 以上使用条件分支。

The one below uses conditional execution, no branch, no pipeline flush, is deterministic. 下面的一个使用条件执行,没有分支,没有管道刷新,是确定性的。

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

Could have saved the mov r0,r2 instruction by re-arranging the arguments in the function call, but that is academic, you wouldnt burn a function call on this normally. 可以通过在函数调用中重新排列参数来保存mov r0,r2指令,但这是学术性的,你通常不会在这个上烧掉一个函数调用。

EDIT: 编辑:

As suggested: 建议:

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

Certainly cheaper, and the code looks good, but I wouldnt make assumptions that the result of blah!=0, which is zero or whatever the compiler has defined as true always has the lsbit set. 当然更便宜,而且代码看起来不错,但我不会假设blah!= 0的结果,即零或编译器定义为true的任何内容总是设置为lsbit。 It doesnt have to have that bit set for the compiler to generate working code. 它不必为编译器设置该位以生成工作代码。 Perhaps the standards dictate the specific value for true. 也许标准规定了真实的具体价值。 by re-arranging the function parameters the if(blah) number +=... will also result in three single clock instructions and not have assumptions. 通过重新排列函数参数,if(blah)数+ = ...也将导致三个单时钟指令而没有假设。

EDIT2: EDIT2:

Looking at what I understand to be the C99 standard: 看看我理解为C99标准:

The == (equal to) and != (not equal to) operators are analogous to the relational operators except for their lower precedence. ==(等于)和!=(不等于)运算符类似于关系运算符,除了它们的优先级较低。 Each of the operators yields 1 if the specified relation is true and 0 if it is false. 如果指定的关系为真,则每个运算符产生1,如果为假,则产生0。

Which explains why the above edit works and why you get the movne r0,#1 and not some other random number. 这解释了为什么上面的编辑工作以及为什么你得到movne r0,#1而不是其他一些随机数。

The poster was asking the question with regards to C but also noted that ADA was the current language, from a language independent perspective you should not assume "features" like the C feature above and use an if(blah) number = number + (1<<n). 海报在询问有关C的问题,但也注意到ADA是当前的语言,从语言独立的角度来看,你不应该假设“功能”,如上面的C功能,并使用if(blah)数字=数字+(1 << N)。 But this was asked with a C tag so the generically (processor independent) fastest result for C is, I think, number += (blah!=0)<<n; 但这是用C标签询问的,所以C的一般(处理器无关)最快的结果是,我认为,数字+ =(blah!= 0)<< n; So Steven Wright's comment had it right and he should get credit for this. 所以史蒂文赖特的评论是正确的,他应该为此获得赞誉。

The posters assumption is also basically correct, if you can get blah into a 0 or 1 form then using it in the math is faster in the sense that there is no branch. 海报假设基本上也是正确的,如果你可以把它变成0或1形式,那么在数学中使用它会更快,因为没有分支。 Getting it into that form without it being more expensive than a branch is the trick. 如果没有它比分支更昂贵就可以进入这种形式。

I would keep it with the conditional. 我会保留条件。 You shouldn't worry about low-level optimisation details at this point. 此时您不应该担心低级优化细节。 Write the code that describes your algorithm best and trust your compiler. 编写最能描述算法的代码并信任编译器。 On some CPUs the multiplication is slower (eg ARM processors that have conditionals on each instruction). 在某些CPU上,乘法较慢(例如,在每条指令上都有条件的ARM处理器)。 You could also use the ?: expression which optimises better under some compilers. 你也可以使用?:表达式,它可以在某些编译器下更好地优化。 For example: 例如:

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

If for some reason this little calculation is the bottleneck of your application after profiling then worry about low-level optimisation. 如果由于某种原因,这个小计算是分析后应用程序的瓶颈,那么担心低级优化。

In C, regarding blah*2^n: Do you have any reason to believe that blah takes the values 0 and 1? 在C中,关于blah * 2 ^ n:你有理由相信blah取值0和1吗? The language only promises that 0 <-> FALSE and (everything else) <-> TRUE. 该语言仅承诺0 < - > FALSE和(其他所有)< - > TRUE。 C allows you to multiply a "boolean" temporary with another number, but the result is not defined except insofar as result=0 <=> the bool was false or the number was zero. C允许您将“布尔”临时值与另一个数字相乘,但结果未定义,除非结果= 0 <=> bool为false或数字为零。

In Ada, regarding blah*2^n: The language does not define a multiplication operator on type Boolean. 在Ada中,关于blah * 2 ^ n:语言没有在Boolean类型上定义乘法运算符。 Thus blah cannot be a bool and be multiplied. 因此,blah不能成为一个bool并且会成倍增加。

If your language allows multiplication between a boolean and a number, then yes, that is faster than a conditional. 如果你的语言允许在布尔值和数字之间进行乘法,那么是的,这比条件更快。 Conditionals require branching, which can invalidate the CPU's pipeline. 条件需要分支,这可能使CPU的管道无效。 Also if the branch is big enough, it can even cause a cache miss in the instructions, though that's unlikely in your small example. 此外,如果分支足够大,它甚至可能导致指令中的高速缓存未命中,尽管在您的小示例中这不太可能。

Generaly, and particularly when working with Ada, you should not worry about micro-optimization issues like this. 通常,尤其是在与Ada合作时,您不应该担心像这样的微优化问题。 Write your code so that it is clear to a reader, and only worry about performance when you have a problem with performance, and have it tracked down to that portion of the code. 编写代码,以便读者清楚,只有在遇到性能问题时才会担心性能,并将其跟踪到代码的那一部分。

Different CPUs have different needs, and they can be insanely complex. 不同的CPU有不同的需求,它们可能非常复杂。 For example, in this case which is faster depends a lot on your CPU's pipeline setup, what's in cache at the time, and how its branch prediction unit works. 例如,在这种情况下,更快的取决于CPU的管道设置,当时缓存中的内容以及分支预测单元的工作方式。 Part of your compiler's job is to be an expert in those things, and it will do a better job than all but the very best assembly programmers. 编译器的一部分工作就是成为这方面的专家,除了最好的汇编程序员之外,它会做得更好。 Certianly better than you (or me). Certianly比你(或我)更好。

So you just worry about writing good code, and let the compiler worry about making efficient machine code out of it. 因此,您只需担心编写好的代码,并让编译器担心从中获取高效的机器代码。

For the problem stated, there is indeed simple expressions in C that may produce efficient code. 对于所述的问题,在C中确实存在可以产生有效代码的简单表达式。

The n th power of 2 can be computed with the << operator as 1 << n , provided n is less than the number of value bits in an int . 所述n理论值的功率2可与被计算<<操作者1 << n ,提供n小于值的比特在数量int

If blah is a boolean , namely an int with a value of 0 or 1 , your code fragment can be written: 如果blah布尔值 ,即值为01int ,则可以编写代码片段:

number += blah << n;

If blah is any scalar type that can be tested for its truth value as if (blah) , the expression is slightly more elaborate: 如果blah是任何可以测试其真值的标量类型,就if (blah) ,表达式稍微复杂一点:

number += !!blah << n;

which is equivalent to number += (blah != 0) << n; 这相当于number += (blah != 0) << n;

The test is still present but, for modern architectures, the generated code will not have any jumps, as can be verified online using Godbolt's compiler explorer . 测试仍然存在但是,对于现代架构,生成的代码不会有任何跳跃,因为可以使用Godbolt的编译器浏览器在线验证。

In either case, you can't avoid a branch (internally), so don't try! 在任何一种情况下,你都无法避免分支(内部),所以不要尝试!

In

number = number + blah*2^n

the full expression will always have to be evaluated, unless the compiler is smart enough to stop when blah is 0. If it is, you'll get a branch if blah is 0. If it's not, you always get an expensive multiply. 除非编译器足够聪明,否则当blah为0时,必须对整个表达式进行求值。如果是,如果blah为0,你将得到一个分支。如果不是,你总是得到一个昂贵的乘法。 In case blah is false, you'll also get the unnecessary add and assignment. 如果blah是假的,你也会得到不必要的添加和赋值。

In the "if then" statement, the statement will only do the add and assignment when blah is true. 在“if then”语句中,语句只会在blah为true时执行add和赋值。

In short, the answer to your question in this case is "yes". 简而言之,在这种情况下你的问题的答案是“是”。

This code shows they perform similarly, but multiplication is usually slightly faster. 此代码显示它们的表现相似,但乘法通常稍快。

@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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM