1 回答
TA贡献1804条经验 获得超7个赞
由于没有人想做一些拆卸的东西,我回答我自己的问题。
原因似乎是 JIT 生成的本机代码,而不是注释中提到的数组边界检查或缓存问题。
RyuJIT 为该ClampSimple方法生成了一个非常有效的代码:
vucomiss xmm1,xmm0
jbe M01_L00
vmovaps xmm0,xmm1
ret
M01_L00:
vucomiss xmm0,xmm2
jbe M01_L01
vmovaps xmm0,xmm2
ret
M01_L01:
ret
它使用 CPU 的本机ucomiss操作来比较floats,并使用快速movaps操作float在 CPU 的寄存器之间移动这些s。
扩展方法较慢,因为它有几个对 的函数调用System.Single.CompareTo(System.Single),这是第一个分支:
lea rcx,[rsp+30h]
vmovss dword ptr [rsp+38h],xmm1
call mscorlib_ni+0xda98f0
test eax,eax
jge M01_L00
vmovss xmm0,dword ptr [rsp+38h]
add rsp,28h
ret
让我们看看 Mono 为该ClampSimple方法生成的本机代码:
cvtss2sd xmm0,xmm0
movss xmm1,dword ptr [rsp+8]
cvtss2sd xmm1,xmm1
comisd xmm1,xmm0
jbe M01_L00
movss xmm0,dword ptr [rsp+8]
cvtss2sd xmm0,xmm0
cvtsd2ss xmm0,xmm0
jmp M01_L01
M01_L00:
movss xmm0,dword ptr [rsp]
cvtss2sd xmm0,xmm0
movss xmm1,dword ptr [rsp+10h]
cvtss2sd xmm1,xmm1
comisd xmm1,xmm0
jp M01_L02
jae M01_L02
movss xmm0,dword ptr [rsp+10h]
cvtss2sd xmm0,xmm0
cvtsd2ss xmm0,xmm0
jmp M01_L01
M01_L02:
movss xmm0,dword ptr [rsp]
cvtss2sd xmm0,xmm0
cvtsd2ss xmm0,xmm0
M01_L01:
add rsp,18h
ret
Mono 的代码转换floats为doubles 并使用comisd. 此外,在准备返回值时,还有奇怪的“转换翻转” float➞ double➞ float。而且在内存和寄存器之间还有更多的移动。这解释了为什么 Mono 的简单方法代码比 RyuJIT 的代码慢。
该Extension方法代码与 RyuJIT 的代码非常相似,但再次具有奇怪的转换翻转float➞ double➞ float:
movss xmm0,dword ptr [rbp-10h]
cvtss2sd xmm0,xmm0
movsd xmm1,xmm0
cvtsd2ss xmm1,xmm1
lea rbp,[rbp]
mov r11,2061520h
call r11
test eax,eax
jge M0_L0
movss xmm0,dword ptr [rbp-10h]
cvtss2sd xmm0,xmm0
cvtsd2ss xmm0,xmm0
ret
似乎 RyuJIT 可以生成更高效的代码来处理floats。Mono 将floats 视为doubles 并每次转换值,这也会导致 CPU 寄存器和内存之间的额外值传输。
请注意,所有这些仅对 Windows x64 有效。我不知道这个基准测试在 Linux 或 Mac 上的表现如何。
- 1 回答
- 0 关注
- 278 浏览
添加回答
举报