汇编语言中哪种方法执行得更快:按变量加法还是按立即值加法?为什么?



例如:

; Method 1
.data
val1 DWORD 10000h
.code
add eax,val1

v.s:

; Method 2
.code
add eax,10000h

哪种方法在编译(汇编)后执行得更快?我认为方法2会产生更快的代码,因为CPU在将值添加到eax寄存器之前不必从主存中读取值。我的回答不太清楚,有人能帮忙吗?

10000h将从内存中读取,无论是从它在数据内存中的位置还是从它在指令存储器中的位置。对于较小的常数值,CPU提供了特殊的指令,这些指令不需要为添加的值提供额外的空间,但这取决于特定的体系结构。由于缓存的原因,立即添加可能会更快:当指令被解码时,常量将在缓存中,并且添加将非常快。

小的离题说明:您的示例显示了一个优化C编译器生成比手工编写的程序集更快的代码的情况:优化器可以不添加10000h,而是将上半个字增加一,并保持下半个字不变。

很可能,这将取决于情况,差异甚至可能不明显

无序执行等因素可能会掩盖任何一个版本固有的"缓慢",除非实际上存在瓶颈。

也就是说,如果我们必须选择哪个更快,那么第二种情况可能更快,这是正确的。

如果我们看看Agner Fog针对当前所有x86处理器的表:

核心2:

add/sub    r, r/i    Latency = 1        , 1/Throughput = 0.33
add/sub    r, m      Latency = unknown  , 1/Throughput = 1

尼哈莱姆:

add/sub    r, r/i    Latency = 1        , 1/Throughput = 0.33
add/sub    r, m      Latency = unknown  , 1/Throughput = 1

Sandy Bridge:

add/sub    r, r/i    Latency = 1        , 1/Throughput = 0.33
add/sub    r, m      Latency = unknown  , 1/Throughput = 0.5

K10:

add/sub    r, r/i    Latency = 1        , 1/Throughput = 0.33
add/sub    r, m      Latency = unknown  , 1/Throughput = 0.5

在所有情况下,内存操作数版本的吞吐量都较小。在所有情况下,延迟都是未知的,但几乎可以肯定的是超过1个周期。因此,从所有因素来看,情况都更糟。

内存操作数版本使用与即时版本相同的所有执行端口+它还需要一个内存读取端口。这只会使情况变得更糟。事实上,这就是为什么内存操作数的吞吐量较低的原因——内存端口只能维持1或2次读取/周期,而加法器可以维持完整的3/周期。

此外,这假设数据在L1高速缓存中。如果不是,则内存操作数版本将慢MUCH


进一步,我们可以检查编码指令的大小:

add eax,val1    ->   03 05 14 00 00 00
add eax,10000h  ->   05 00 00 01 00

根据CCD_ 1的地址,第一个的编码可能略有不同。我在这里展示的例子来自我的特定测试用例。

因此,内存访问版本需要一个额外的字节来编码——这意味着代码大小稍大——并且可能会有更多的i-cache未命中。


因此,总之,如果两个版本之间存在性能差异,那么立即的速度可能会更快,因为:

  1. 它具有较低的延迟
  2. 它具有更高的吞吐量
  3. 它的编码更短
  4. 它不需要访问数据缓存,这可能是缓存未命中

添加立即数(神奇的十六进制值)确实更快(至少在我知道的架构上)。

我认为问题是,多少钱。现在我认为这取决于val1是否被缓存的事实。

如果它没有缓存,它会非常慢,因为访问内存比访问缓存慢很多(无论实际级别如何,缓存l1确实是最快的)。

如果它确实被缓存了,在我看来,结果非常接近。

我已经有一段时间没有进行汇编了,但我认为这段代码并不等价。

在方法1中,您将val1的地址添加到eax,在方法2中,您将常量值10000h添加到eax。。。要添加变量的内容,您必须执行

add eax,[val1]

并且这将更慢,因为它将触发存储器读取。而这一准则甚至可能不合法。你不应该做一些类似的事情吗:

mov ecx, val1
add eax, [ecx]

正如我所说,我的英特尔程序集相当生疏:)

最新更新