(x < 0) 和 (x == -1) 哪个更快?(涉及 IT 技术)

38

变量x是一个整数,可能的取值为:-1、0、1、2、3。哪个表达式在CPU时钟周期中更快:

1. (x < 0)
2. (x == -1)

语言:C/C++,但我认为所有其他语言都将是相同的。

P.S. 我个人认为答案是(x < 0)

对于高手们更广泛的问题:如果x-12^30怎么办?


8
回答这样低级别的问题,CPU架构只是一项最基本的信息,你不觉得吗?但即使如此,在这些条件下需要不同数量周期的CPU也相当原始。 - joelr
20
为什么这是一个不好的问题?对此进行全面回答可以使所有人更好地理解处理器的工作原理等相关知识。那不是一件好事吗? - Alex Gartrell
7
最后一点:针对这样的问题,无法一概而论。最好的方法是尝试两种方法,使用您的产品编译器和一个代表性的测试系统,并比较结果。我很惊讶这种问题经常出现,当几分钟的基准测试可以提供答案时。 - Mark Ransom
4
我认为他只是想更好地了解。 优化这个很愚蠢。 我自己也很感兴趣,因为我不知道。 +1(赞同) - the_drow
7
听起来你似乎从未处理过嵌入式代码。 - Ed S.
显示剩余9条评论
12个回答

80

这完全取决于你所编译的ISA以及编译器优化器的质量。不要过早地优化:首先进行性能分析,找出瓶颈

尽管如此,在x86中,你会发现在大多数情况下两者的速度都是一样快的。无论哪种情况,你都会有一个比较指令(cmp)和一个条件跳转指令(jCC)。然而,对于(x < 0),在某些情况下编译器可以省略cmp指令,从而使你的代码加速一个完整周期

具体来说,如果值x存储在寄存器中,并且最近是一次算术运算(例如addsub,但还有许多其他可能性)的结果,该运算设置了EFLAGS寄存器中的符号标志SF,那么就不需要cmp指令,编译器可以只发出一个js指令。没有简单的jCC指令可以在输入为-1时跳转。


21
我不相信这是任何程序中的“瓶颈”,如果你看到时间上的差异,更可能是你的代码通过将== -1条件设置为-2而“跳过”了它,因此没有终止循环(假设该表达式是循环的一部分)。 - lothar
5
不要忘记,cmp指令可能会被替换为or指令,这不会减少周期数,但可能会改变内存对齐方式。这可能有帮助,也可能是适得其反,这就是为什么性能分析如此重要的原因。 - Mark Ransom
17
请不要小看这个问题——我曾经遇到循环非常紧密的情况,这种优化可以产生一定的影响。通常只有几个百分点,但有时每一点都有帮助! - Mark Ransom
1
而且甚至不仅仅取决于ISA本身,还取决于体系结构的实际实现... - fortran
不需要引用,流水线在CPU供应商之间是不同的,因此在两个不同的CPU上,相同的x86指令需要不同的CPU周期计数。 - CoffeDeveloper
显示剩余2条评论

11

尝试一下并观察结果!每个测试分别做100万次或者甚至10亿次,并计时。我敢打赌你的结果没有统计学意义,但谁知道呢——也许在你的平台和编译器上,你可能会得到一个结果。

这是一个很棒的实验,让你相信过早优化可能不值得你花费时间——并且很可能是“所有邪恶的根源——至少在编程中是如此"。


5
这是一些建议,但不是真正的答案。 - Paul Draper

9

这两种操作都可以在单个CPU步骤中完成,因此它们在性能上应该是相同的。


11
啊!虽然这在绝大多数芯片上都是真的,但如果不知道他所使用的平台,你就无法做出明确的陈述。并非所有的世界都是x86架构。 - dmckee --- ex-moderator kitten
4
如果他询问的是特定的、非正常的架构,我认为他会指明。如果他是一般性地提问,我试图为大多数现代架构给出简单的答案。 - Gavin H
当然,我没有考虑任何特定的架构。通常是x86。 - Nikolay Vyahhi

9

x < 0会更快。如果没有其他原因,它可以防止将常量-1作为操作数获取。 大多数架构都有针对与零进行比较的特殊指令,这也有所帮助。


2
你如何在不了解架构和/或编译器的情况下判断这一点? - Johan Kotlinski
1
你在谈论哪种架构?我相信大多数x86指令集都可以与立即值进行比较,无需获取操作数。这是英特尔指令集参考链接:http://www.intel.com/Assets/PDF/manual/253666.pdf - jabbie
当然,几乎任何架构都可以与即时值进行比较。但即使在那里,指令的大小也会变大(因此需要从内存中获取另一个取值)。这并不是个大问题,除非每一分性能都至关重要,而这似乎是上下文所要求的。我猜测提问者正在编写设备驱动程序或类似的东西。 - Phone Guy
关于第一个问题 - 我看了很长一段时间的架构。 在前六个或更多之后,模式开始出现。我还碰巧知道关于x86指令集语义的东西比大多数人现在关注的要多。 例如,每次在x86上执行任何值操作时,条件位都会被设置。 因此,在做计算、将值加载到寄存器中等操作之后,您可以使用JB指令测试负数。编译器通常会尝试利用这一点,尽管某些愚蠢的编译器不会这样做。 - Phone Guy

7

这可能取决于比较操作之前或之后的操作。例如,如果在进行比较之前刚刚给x赋值,那么检查符号标志可能比与特定值进行比较更快。或者CPU的分支预测性能可能会受到所选择比较的影响。

但是,正如其他人所说,这取决于CPU架构、内存架构、编译器和许多其他因素,因此没有通用答案。


3

如果没有上下文,你甚至无法回答这个问题。如果你试图进行微不足道的微基准测试,优化器完全有可能会将你的代码飘到虚空中:

// Get time
int x = -1;
for (int i = 0; i < ONE_JILLION; i++) {
    int dummy = (x < 0); // Poof!  Dummy is ignored.
}
// Compute time difference - in the presence of good optimization
// expect this time difference to be close to useless.

它将被编译器优化为零条指令。但我理解了你的想法,谢谢。 - Nikolay Vyahhi
是的 - 这就是我想要用欢快的方式表达的意思。如果第一次没有表达清楚,那是我的错。 - Bob Cross
你可以通过允许x和dummy逃逸(即,在另一个翻译单元中将它们的指针传递给函数)并引入编译器特定的内存屏障指令,例如gcc的__sync_synchronize()来在一定程度上避免这种情况。这将强制编译器发出代码来评估(x<0)并设置dummy - 但也会强制进行内存访问。 - bdonlan
2
最终,你最终会创建一个复杂的结构来尝试测量不存在或没有100%上下文可测量的差异。例如,OP使用“C ++”和“C”标记了这个问题-两者之间存在显着差异,更不用说在所有不同平台上的各种编译器之间了。 - Bob Cross
在这样一小段代码中,添加测量代码可能会因为缓存、优化等原因改变结果。 - Makis
@Makis,是的,这正是我想说的。 - Bob Cross

3
无论如何,重要的考虑因素是哪个能够准确地指导程序流程,哪个只是碰巧产生相同的结果?
如果x实际上是索引或枚举中的值,那么-1总是您想要的吗?还是任何负值都可以?目前,-1是唯一的负数,但这可能会改变。

1

Nikolay,你写道:

实际上,在高负载程序中是瓶颈运算符。在这1-2个字符串中,性能比可读性更有价值...

所有的瓶颈通常都很小,即使在完美的设计和完美的算法中也是如此(虽然不存在这样的算法)。我进行高负载DNA处理,并且非常了解我的领域和算法。

如果是这样,为什么不这样做:

  1. 获取计时器,将其设置为0;
  2. 使用(x < 0)编译您的高负载程序;
  3. 启动程序和计时器;
  4. 在程序结束时查看计时器并记住结果1。
  5. 同1;
  6. 使用(x == -1)编译您的高负载程序;
  7. 同3;
  8. 在程序结束时查看计时器并记住结果2。
  9. 比较结果1和结果2。

您将得到答案。


1

同样,这两个操作通常在一个时钟周期内完成。


1
这将是一个指令获取和执行周期,但不是一个时钟周期。 - raimue

1

这取决于架构,但 x == -1 更容易出错。x < 0 是更好的选择。


1
不,这不是正确的方法。 为了检测错误,请使用单元测试而不是花哨的代码。 为了减少错误:给常量命名。通常直截了当地表达更好。如果目标是与-1进行比较,只需编写(x == -1),否则下一个维护此代码的开发人员将不得不弄清楚我们为什么要与0进行比较(“哦,好吧,实际上是为了测试-1”),然后弄清楚(该死的...)是什么。 - Jem
好的,我们现在讨论的是一个理想情况。正如你所说,没有人应该使用“魔法数字”,而应该使用常量。你可以这样比较 ( x <= VALUE )。通常你会用计数器变量来做这个,这样就能减少错误的发生。在现实世界中,单元测试并不总是能够完成(时间或其他限制)。显然,如果这是一个特殊情况,你只需要检查“-1”的值,( x == VALUE ) 就是正确的方法。 - Arturito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接