例如:
; Method 1
.data
val1 DWORD 10000h
.code
add eax,val1
v.s:
; Method 2
.code
add eax,10000h
编译(汇编)后哪种方法执行得更快? 我认为方法2会产生更快的代码,因为在添加到eax寄存器之前,CPU不必从主存储器读取值。我的回答不太清楚,有人可以帮忙吗?
答案 0 :(得分:5)
很可能,情况会有所不同,差异可能甚至不明显。
out-of-order execution等因素可能会隐藏任何类型的固有"缓慢"任何版本,除非确实存在瓶颈。
那就是说,如果我们必须选择哪个更快,那么你是正确的,第二种情况可能会更快。
如果我们查看所有当前x86处理器的Agner Fog's tables:
核心2:
add/sub r, r/i Latency = 1 , 1/Throughput = 0.33
add/sub r, m Latency = unknown , 1/Throughput = 1
<强>的Nehalem:强>
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
第一个的编码可能会略有不同,具体取决于val1
的地址。我在这里展示的例子来自我的特定测试用例。
因此,内存访问版本需要一个额外的字节进行编码 - 这意味着代码大小稍大 - 并且可能会在极端情况下出现更多的i-cache错误。
总而言之,如果版本之间存在性能差异,则立即可能会更快,因为:
答案 1 :(得分:3)
添加一个立即(你的神奇十六进制值)确实更快(至少我知道的架构)。
我认为问题是,多少。现在我认为这取决于val1是否被缓存的事实。
如果它没有被缓存,那么它非常慢,因为访问内存比访问缓存更慢(无论什么级别,缓存l1确实是最快的)。
如果它确实被缓存,结果在我最不同意见中非常接近。
答案 2 :(得分:3)
10000h将从内存中读取,无论是从数据存储器中的位置,还是从指令存储器中的位置读取。对于较小的常量值,CPU提供的特殊指令不需要额外的空间来添加值,但这取决于特定的体系结构。由于缓存,add immediate可能会更快:在解码指令时,常量将在缓存中,并且添加速度非常快。
小偏离主题说明:您的示例显示了优化C编译器生成比手写汇编更快的代码的情况:优化器可以将上半字增加1,而不是添加10000h。低半字也是如此。
答案 3 :(得分:0)
我有一段时间没有完成装配,但我相信这段代码并不等同。
在方法1中,将val1的地址添加到eax,在方法2中,将常量值10000h添加到eax ...要添加变量的内容,您必须执行
add eax,[val1]
这会慢一些,因为它会触发内存读取。这段代码甚至可能不合法。你不应该这样做:
mov ecx, val1
add eax, [ecx]
正如我所说,我的英特尔组装非常生锈:)