1 <10的比较是否比1 <1000000便宜?


65

我只用了大约10亿作为z-indexCSS中a的数量,并在考虑必须进行的比较。在非常大的数字与非常小的数字之间进行比较时,ALU级别的性能是否有所不同?

例如,这两个摘要中的一个摘要会比另一个摘要贵吗?

snippet 1

for (int i = 0; i < 10000000; i++){
    if (i < 10000000000000) {
        //do nothing
    }
}

snippet 2

for (int i = 0; i < 10000000; i++){
    if (i < 1000) {
        //do nothing
    }
}

9
您是否知道分支预测的工作原理
t

12
OP并没有询问分支将花费多少时间。显然,该示例旨在确保两个片段中的时间完全相同。问题是,CMP如果i较大,则单个机器指令是否会变慢。
Kilian Foth,2015年

18
由于这是在CSS中完成的,因此将字符串转换为整数很可能会在执行时间上占主导地位。

58
如果您需要在CSS文件中使用1000000000作为z-index,则您做错了什么。
Bergi 2015年

6
对于CSS,将文本转换为整数的开销将取决于要转换的位数(其中1000000之类的6位数可能是1之类的1位数的大约6倍);并且此开销可能比整数比较的开销大几个数量级。
布伦丹2015年

Answers:


82

我研究过的每个处理器都通过从另一个操作数中减去一个操作数来进行比较,丢弃结果并留下处理器的标志(零,负等)。因为减法是作为单个操作完成的,所以操作数的内容无关紧要。

肯定地回答该问题的最佳方法是将代码编译为汇编代码,并参阅目标处理器的文档以获取生成的指令。对于当前的Intel CPU,这将是《Intel 64和IA-32体系结构软件开发人员手册》

所述的描述CMP(“比较”)指令是在体积2A,3-126页,或PDF的618页,描述其操作为:

temp ← SRC1 − SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)

这意味着如果需要,第二个操作数将进行符号扩展,然后从第一个操作数中减去,并将结果放置在处理器的临时区域中。然后,以与SUB(“减”)指令相同的方式设置状态标志(PDF的1492页)。

CMPSUB文档中没有提到操作数的值与延迟有关,因此您使用的任何值都是安全的。


5
如果数字对于32位算术变得太大怎么办?那会不会被分解为较慢的计算呢?
Falco

3
@Falco不在具有64位ALU的CPU上使用(这几天几乎所有这些都在嵌入式空间中。)
reirab 2015年

8
@Falco:是的,但是由于该问题询问了ALU性能,因此含义是这些值适合CPU的字长或它可能具有的任何SIMD指令的能力。在更大的数量上进行操作必须在CPU外部使用多条指令来实现。这在30年前非常普遍,当时您只有8位或16位寄存器可以使用。
Blrfl 2015年

6
@Falco那怎么需要调试?这不是错误;在本身不支持64位操作的CPU上执行64位操作的速度稍慢一些。建议不要使用2 ^ 31-1以上的数字似乎有点荒谬。
reirab 2015年

2
@Falco话虽如此,浏览器中的渲染引擎是否甚至使用整数来表示z索引?我熟悉的大多数渲染引擎对所有内容都使用单精度浮点数(直到最后的光栅化阶段为止),但是我还没有真正研究过浏览器渲染引擎。
reirab 2015年

25

在非常大的数字与非常小的数字之间进行比较时,ALU级别的性能是否有所不同?

这是非常不可能的,除非从一个小数变为一个大数会更改您的数字类型,例如从更改int为a long。即使这样,两者之间的差异也可能并不明显。如果您的编程语言在后台悄悄地切换到任意精度算术,则您更有可能看到不同。

但是,您的特定编译器可能正在执行一些您不知道的聪明的优化。您发现的方法就是衡量。 对代码运行探查器;查看哪些比较耗时最长。或者只是启动和停止计时器。


应该提到的是,问题中建议的数字在典型的32位整数类型中具有不同的数字类型...
Falco

19

许多处理器具有“小”指令,可以对某些立即指定的操作数执行算术运算,包括比较。这些特殊值以外的操作数必须使用更大的指令格式,或者在某些情况下,必须使用“从内存中加载值”指令。例如,在ARM Cortex-M3指令集中,至少有五种方法可以将值与常数进行比较:

    cmp r0,#1      ; One-word instruction, limited to values 0-255

    cmp r0,#1000   ; Two-word instruction, limited to values 0-255 times a power of 2

    cmn r0,#1000   ; Equivalent to comparing value with -1000
                   ; Two-word instruction, limited to values 0-255 times a power of 2

    mov r1,#30000  ; Two words; can handle any value 0-65535
    cmp r0,r1      ; Could use cmn to compare to values -1 to -65535

    ldr r1,[constant1000000] ; One or two words, based upon how nearby the constant is
    cmp r0,r1
    ...

constant1000000:
    dd  1000000

第一种形式是最小的。第二种形式和第三种形式执行的速度可能快也可能不会快,这取决于从中获取代码的内存的速度。第四种形式几乎肯定会比前三种形式慢,而第五种形式甚至更慢,但后者可以与任何32位值一起使用。

在较早的x86处理器上,短格式比较指令的执行速度比长格式比较指令的执行速度快,但是许多较新的处理器会在首次获取长格式和短格式时将其转换为相同的表示形式,并将该统一表示形式存储在缓存中。因此,尽管嵌入式控制器(如在许多移动平台上发现的控制器)会有速度差异,但许多基于x86的计算机却没有。

还要注意,在许多情况下,在循环中大量使用常量的情况下,编译器只需要在循环开始之前一次将常量加载到寄存器中,就可以解决时序差异。另一方面,在某些情况下,即使是小循环,也不会总是发生。如果循环很小但执行力很强,则涉及短立即值的比较和涉及较长立即值的比较之间有时可能会表现出较大的性能。


在MIPS上,您只能有16位立即数,因此与1的比较肯定会更短,并且(可能)比1000000更快。Sparc和PowerPC可能相同。而且我想我从一些消息来源
获悉

@LưuVĩnhPhúc:可以在循环之前加载寄存器。那时,在两种情况下,实际的比较将是相同数量的指令。
cHao 2015年

由于Loop只是op的一个示例,问题是例如z-index,如果您有1000个对象,每个对象都有其自己的z-index,则将它们设置为100000000 ... 1000000999或10000 ... 10999,您在渲染之前将它们循环进行排序,有很多比较和很多加载指令。在那里可能会有所作为!
Falco 2015年

@Falco:在那种情况下,立即数甚至都不会考虑在内;加载和与寄存器比较似乎是不可避免的。
cHao 2015年

@cHao:如果将Z索引相互比较,则它们将在寄存器中。如果一个人对某些范围的索引的处理不同,则可能需要立即进行比较。通常,常量会在循环开始之前加载,但是如果例如某个循环需要从内存中读取成对的值,然后将每个对的第一个值与五个范围为100000的不同(非均匀间隔)的常量进行比较,到100499,以及另一个带有其他五个此类常数的值,则减去100250(保留在寄存器中)然后与-250到250进行比较可能要快得多...
supercat

5

这个问题的简短答案是,,假设将两个数字存储在相同的数据类型中(例如,两个32位整数或两个64位长整数),则基于这些数字的大小比较两个数字没有时间差异。

此外,在不超过ALU字长的情况下,将两个整数相互比较将不可能花费超过1个时钟周期,因为这是微不足道的运算,等同于减法。我认为我曾经处理过的每个体系结构都有单周期整数比较。

我能想到的唯一的情况是,两个数字的比较不是单周期操作:

  • 指令在获取操作数时实际上存在内存延迟,但这与比较本身的工作方式无关(通常在RISC架构上是不可能的,尽管在x86 / x64之类的CISC设计中通常是可能的)。
  • 浮点比较可能是多周期的,具体取决于体系结构。
  • 有问题的数字不适合ALU的字长,因此,必须将比较分解为多个指令。

4

@RobertHarvey的回答很好;认为这个答案是对他的补充。


您还应该考虑分支预测

在计算机体系结构中,分支预测器是一种数字电路,它试图猜测在确定之前知道分支(例如,if-then-else结构)将走的路。分支预测器的目的是改善指令管道中的流程。在许多现代流水线微处理器体系结构(例如x86)中,分支预测器在实现高效能方面发挥着至关重要的作用。

基本上,在您的示例中,如果if循环内的语句始终返回相同的答案,则系统可以通过正确猜测它将分支的方式来对其进行优化。在您的示例中,由于if第一种情况的语句始终返回相同的结果,因此它的运行速度将比第二种情况快一些。

关于该主题的优秀堆栈溢出问题


分支预测会影响分支时间,但不会影响比较时间本身。
reirab

3

这取决于实现方式,但这是非常非常不可能的

我承认我还没有阅读各种浏览器引擎的实现细节,并且CSS没有为数字指定任何特定的存储类型。但我相信可以肯定地说,所有主流浏览器都使用64位双精度浮点数(“ doubles”,是从C / C ++借用的术语)来满足CSS中大部分的数字需求,因为这是JavaScript用于数字的方式,因此使用相同的类型会使集成更容易。

从计算机的角度来看,所有双打都承载相同数量的数据:64位,无论值是1或-3.14还是1000000或1e100。对这些数字进行操作所花费的时间并不取决于这些数字的实际值,因为它始终处理相同数量的数据。这样处理时要权衡取舍,因为双精度不能准确表示所有数字(甚至是其范围内的所有数字),但对于大多数情况它们可以足够接近,而且CSS所做的事情并不是数字上的-要求足够的精度。将其与与JavaScript的直接兼容性的优点相结合,您将获得双打的强大理由。

有人可能会使用可变长度的数字编码来实现CSS。如果有人使用可变长度编码,那么与小数进行比较将比与大数进行比较便宜,因为大数具有更多的数据需要处理。这些编码可能比二进制编码更精确,但它们也要慢得多,尤其是对于CSS而言,精确度的提高可能不足以值得提高性能。得知任何浏览器都以这种方式进行操作,我会感到非常惊讶。

从理论上讲,现在我上面所说的一切都有一个例外:与零进行比较通常比与其他数字进行比较要快。这不是因为零短(如果这是原因,那么1应该和速度一样快,但事实并非如此)。这是因为零会让您作弊。这是所有位均关闭的唯一数字,因此,如果您知道其中一个值是零,则您甚至不必将另一个值视为一个数字:如果其中任何位都不等于零,然后您只需要看一点就可以看到它是否大于或小于零。


0

如果每次运行时都对这段代码进行解释,则存在差异,因为与10000000000000相比,标记和解释需要更长的时间1000。但是,在这种情况下,这显然是解释器的第一个优化:一次标记化并解释标记。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.