例如:
; 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未命中。
因此,总之,如果两个版本之间存在性能差异,那么立即的速度可能会更快,因为:
- 它具有较低的延迟
- 它具有更高的吞吐量
- 它的编码更短
- 它不需要访问数据缓存,这可能是缓存未命中
添加立即数(神奇的十六进制值)确实更快(至少在我知道的架构上)。
我认为问题是,多少钱。现在我认为这取决于val1是否被缓存的事实。
如果它没有缓存,它会非常慢,因为访问内存比访问缓存慢很多(无论实际级别如何,缓存l1确实是最快的)。
如果它确实被缓存了,在我看来,结果非常接近。
我已经有一段时间没有进行汇编了,但我认为这段代码并不等价。
在方法1中,您将val1的地址添加到eax,在方法2中,您将常量值10000h添加到eax。。。要添加变量的内容,您必须执行
add eax,[val1]
并且这将更慢,因为它将触发存储器读取。而这一准则甚至可能不合法。你不应该做一些类似的事情吗:
mov ecx, val1
add eax, [ecx]
正如我所说,我的英特尔程序集相当生疏:)