为什么设置字段比获取字段慢很多倍
本文关键字:字段 获取 设置 为什么 | 更新日期: 2023-09-27 18:31:49
我已经知道设置字段比设置局部变量慢得多,但似乎使用局部变量设置字段比使用字段设置局部变量要慢得多。这是为什么呢?在任一情况下,都使用该字段的地址。
public class Test
{
public int A = 0;
public int B = 4;
public void Method1() // Set local with field
{
int a = A;
for (int i = 0; i < 100; i++)
{
a += B;
}
A = a;
}
public void Method2() // Set field with local
{
int b = B;
for (int i = 0; i < 100; i++)
{
A += b;
}
}
}
10e+6 次迭代的基准测试结果为:
方法1: 28.1321 ms方法2: 162.4528 ms
在我的机器上运行它,我得到了类似的时差,但是查看 10M 迭代的 JITted 代码,很明显为什么会这样:
方法一:
mov r8,rcx
; "A" is loaded into eax
mov eax,dword ptr [r8+8]
xor edx,edx
; "B" is loaded into ecx
mov ecx,dword ptr [r8+0Ch]
nop dword ptr [rax]
loop_start:
; Partially unrolled loop, all additions done in registers
add eax,ecx
add eax,ecx
add eax,ecx
add eax,ecx
add edx,4
cmp edx,989680h
jl loop_start
; Store the sum in eax back to "A"
mov dword ptr [r8+8],eax
ret
和方法B:
; "B" is loaded into edx
mov edx,dword ptr [rcx+0Ch]
xor r8d,r8d
nop word ptr [rax+rax]
loop_start:
; Partially unrolled loop, but each iteration requires reading "A" from memory
; adding "B" to it, and then writing the new "A" back to memory.
mov eax,dword ptr [rcx+8]
add eax,edx
mov dword ptr [rcx+8],eax
mov eax,dword ptr [rcx+8]
add eax,edx
mov dword ptr [rcx+8],eax
mov eax,dword ptr [rcx+8]
add eax,edx
mov dword ptr [rcx+8],eax
mov eax,dword ptr [rcx+8]
add eax,edx
mov dword ptr [rcx+8],eax
add r8d,4
cmp r8d,989680h
jl loop_start
rep ret
从程序集中可以看出,方法 A 将明显更快,因为 A 和 B 的值都放在寄存器中,并且所有添加都发生在那里,没有中间写入内存。另一方面,方法 B 在每次迭代时都会产生负载并存储到内存中的"A"。
在 1 的情况下,a
清楚地存储在寄存器中。其他任何事情都将是一个可怕的编译结果。
在情况 2 中,.NET JIT 可能不愿意/无法将存储转换为A
以注册存储。
我怀疑这是 .NET 内存模型强制的,因为如果其他线程只观察到A
为 0 或总和,它们就永远无法分辨出您的两种方法之间的区别。他们无法反驳优化从未发生过的理论。这使得它在 .NET 抽象机器的语义下是允许的。
看到 .NET JIT 执行少量优化并不奇怪。这是Stack Overflow上performance
标签的追随者所熟知的。
我从经验中知道,JIT 更有可能在寄存器中缓存内存负载。这就是为什么情况 1(显然)每次迭代都无法访问B
的原因。
寄存器计算比内存访问更便宜。如果所讨论的内存位于 CPU L1 缓存中(如此处的情况),则甚至如此。
我以为只有当地人才有资格使用 CPU 缓存?
这不可能是这样,因为 CPU 甚至不知道本地是什么。所有地址看起来都一样。
方法2:字段被读取~100倍并设置~100倍 = 200倍larg_0(这个)+ 100倍LDFLD(加载场)+ 100倍STFLD(设置字段)+ 100倍LDLOC(本地)
方法1:字段读取100倍,但未设置相当于方法1减去100倍ldarg_0(这)