C# x64和x86之间在字节数组访问方面的巨大性能差异

C# x64和x86之间在字节数组访问方面的巨大性能差异,c#,performance,x86,64-bit,clr,C#,Performance,X86,64 Bit,Clr,为了更好地理解clr性能和版本问题,我目前正在进行微基准测试。讨论中的微基准是将每个64字节的两个字节数组xoring在一起 我总是先用safe.net做一个参考实现,然后再尝试用safe等来击败.net框架实现 我的参考实施方案是: for (int p = 0; p < 64; p++) a[p] ^= b[p]; 运行速度大约是x64参考实现的4倍。因此,我对编译器的*long^=*long和*int^=*int优化的想法是不正确的 参考实现中的巨大性能差异来自何处?现在我

为了更好地理解clr性能和版本问题,我目前正在进行微基准测试。讨论中的微基准是将每个64字节的两个字节数组xoring在一起

我总是先用safe.net做一个参考实现,然后再尝试用
safe
等来击败.net框架实现

我的参考实施方案是:

for (int p = 0; p < 64; p++)
    a[p] ^= b[p];
运行速度大约是x64参考实现的4倍。因此,我对编译器的
*long^=*long
*int^=*int
优化的想法是不正确的

参考实现中的巨大性能差异来自何处?现在我发布了ASM代码:为什么C编译器不能以这种方式优化x86版本?

x86和x64参考实现的IL代码(它们相同):

我认为
ldloc.3
a

x86的最终ASM代码:

                for (int p = 0; p < 64; p++)
010900DF  xor         edx,edx
010900E1  mov         edi,dword ptr [ebx+4]
                    a[p] ^= b[p];
010900E4  cmp         edx,edi
010900E6  jae         0109010C
010900E8  lea         esi,[ebx+edx+8]
010900EC  mov         eax,dword ptr [ebp-14h]
010900EF  cmp         edx,dword ptr [eax+4]
010900F2  jae         0109010C
010900F4  movzx       eax,byte ptr [eax+edx+8]
010900F9  xor         byte ptr [esi],al
                for (int p = 0; p < 64; p++)
010900FB  inc         edx
010900FC  cmp         edx,40h
010900FF  jl          010900E4
for(int p=0;p<64;p++)
010900DF xor edx,edx
010900E1 mov edi,dword ptr[ebx+4]
a[p]^=b[p];
010900E4 cmp edx,edi
010900E6 jae 0109010C
010900E8 lea esi,[ebx+edx+8]
010900EC mov eax,dword ptr[ebp-14h]
010900EF cmp edx,德沃德ptr[eax+4]
010900F2 jae 0109010C
010900F4 movzx eax,字节ptr[eax+edx+8]
010900F9异或字节ptr[esi],al
对于(int p=0;p<64;p++)
010900FB公司edx
010900FC cmp edx,40小时
010900FF jl 010900E4
x64的最终ASM代码:

                    a[p] ^= b[p];
00007FFF4A8B01C6  mov         eax,3Eh
00007FFF4A8B01CB  cmp         rax,rcx
00007FFF4A8B01CE  jae         00007FFF4A8B0245
00007FFF4A8B01D0  mov         rax,qword ptr [rbx+8]
00007FFF4A8B01D4  mov         r9d,3Eh
00007FFF4A8B01DA  cmp         r9,rax
00007FFF4A8B01DD  jae         00007FFF4A8B0245
00007FFF4A8B01DF  mov         r9d,3Fh
00007FFF4A8B01E5  cmp         r9,rcx
00007FFF4A8B01E8  jae         00007FFF4A8B0245
00007FFF4A8B01EA  mov         ecx,3Fh
00007FFF4A8B01EF  cmp         rcx,rax
00007FFF4A8B01F2  jae         00007FFF4A8B0245
00007FFF4A8B01F4  nop         word ptr [rax+rax]
00007FFF4A8B0200  movzx       ecx,byte ptr [rdi+rdx+10h]
00007FFF4A8B0205  movzx       eax,byte ptr [rbx+rdx+10h]
00007FFF4A8B020A  xor         ecx,eax
00007FFF4A8B020C  mov         byte ptr [rdi+rdx+10h],cl
00007FFF4A8B0210  movzx       ecx,byte ptr [rdi+rdx+11h]
00007FFF4A8B0215  movzx       eax,byte ptr [rbx+rdx+11h]
00007FFF4A8B021A  xor         ecx,eax
00007FFF4A8B021C  mov         byte ptr [rdi+rdx+11h],cl
00007FFF4A8B0220  add         rdx,2
                for (int p = 0; p < 64; p++)
00007FFF4A8B0224  cmp         rdx,40h
00007FFF4A8B0228  jl          00007FFF4A8B0200
a[p]^=b[p];
00007FFF4A8B01C6 mov eax,3Eh
00007FFF4A8B01CB cmp rax,rcx
00007FFF4A8B01CE jae 00007FFF4A8B0245
00007FFF4A8B01D0 mov rax,qword ptr[rbx+8]
00007FFF4A8B01D4 mov r9d,3Eh
00007FFF4A8B01DA cmp r9,rax
00007FFF4A8B01DD jae 00007FFF4A8B0245
00007FFF4A8B01DF mov r9d,3Fh
00007FFF4A8B01E5凸轮轴位置r9,rcx
00007FFF4A8B01E8 jae 00007FFF4A8B0245
00007FFF4A8B01EA mov ecx,3Fh
00007FFF4A8B01EF cmp rcx,rax
00007FFF4A8B01F2 jae 00007FFF4A8B0245
00007FFF4A8B01F4 nop字ptr[rax+rax]
00007FFF4A8B0200 movzx ecx,字节ptr[rdi+rdx+10h]
00007FFF4A8B0205 movzx eax,字节ptr[rbx+rdx+10h]
00007FFF4A8B020A异或ecx,eax
00007FFF4A8B020C mov字节ptr[rdi+rdx+10h],cl
00007FFF4A8B0210 movzx ecx,字节ptr[rdi+rdx+11h]
00007FFF4A8B015 movzx eax,字节ptr[rbx+rdx+11h]
00007FFF4A8B01A异或ecx,eax
00007FFF4A8B01C mov字节ptr[rdi+rdx+11h],cl
00007FFF4A8B0220添加rdx,2
对于(int p=0;p<64;p++)
00007FFF4A8B0224 cmp rdx,40h
00007FFF4A8B0228 jl 00007FFF4A8B0200

您犯了一个典型的错误,试图对未优化的代码进行性能分析。下面是一个完整的最小可编译示例:

using System;

namespace SO30558357
{
    class Program
    {
        static void XorArray(byte[] a, byte[] b)
        {
            for (int p = 0; p< 64; p++)
                a[p] ^= b[p];
        }

        static void Main(string[] args)
        {
            byte[] a = new byte[64];
            byte[] b = new byte[64];
            Random r = new Random();

            r.NextBytes(a);
            r.NextBytes(b);

            XorArray(a, b);
            Console.ReadLine();  // when the program stops here
                                 // use Debug -> Attach to process
        }
    }
}
对于x64:

00007FFD4A3000FB  mov         rax,qword ptr [rsi+8]  
00007FFD4A3000FF  mov         rax,qword ptr [rbp+8]  
00007FFD4A300103  nop         word ptr [rax+rax]  
00007FFD4A300110  movzx       ecx,byte ptr [rsi+rdx+10h]  
00007FFD4A300115  movzx       eax,byte ptr [rdx+rbp+10h]  
00007FFD4A30011A  xor         ecx,eax  
00007FFD4A30011C  mov         byte ptr [rsi+rdx+10h],cl  
00007FFD4A300120  movzx       ecx,byte ptr [rsi+rdx+11h]  
00007FFD4A300125  movzx       eax,byte ptr [rdx+rbp+11h]  
00007FFD4A30012A  xor         ecx,eax  
00007FFD4A30012C  mov         byte ptr [rsi+rdx+11h],cl  
00007FFD4A300130  movzx       ecx,byte ptr [rsi+rdx+12h]  
00007FFD4A300135  movzx       eax,byte ptr [rdx+rbp+12h]  
00007FFD4A30013A  xor         ecx,eax  
00007FFD4A30013C  mov         byte ptr [rsi+rdx+12h],cl  
00007FFD4A300140  movzx       ecx,byte ptr [rsi+rdx+13h]  
00007FFD4A300145  movzx       eax,byte ptr [rdx+rbp+13h]  
00007FFD4A30014A  xor         ecx,eax  
00007FFD4A30014C  mov         byte ptr [rsi+rdx+13h],cl  
00007FFD4A300150  add         rdx,4  
00007FFD4A300154  cmp         rdx,40h  
00007FFD4A300158  jl          00007FFD4A300110
一句话:x64优化器工作得更好。当它仍然使用字节大小的传输时,它将循环展开了4倍,并将函数调用内联

因为在x86版本中,循环控制逻辑对应大约一半的代码,所以展开可以产生几乎两倍的性能

内联允许编译器执行上下文相关优化,知道数组的大小并消除运行时边界检查

如果我们手工内联,x86编译器现在会生成:

00A000B1  xor         edi,edi  
00A000B3  mov         eax,dword ptr [ebp-10h]  
00A000B6  mov         ebx,dword ptr [eax+4]  
                a[p] ^= b[p];
00A000B9  mov         eax,dword ptr [ebp-10h]  
00A000BC  cmp         edi,ebx  
00A000BE  jae         00A000F5  
00A000C0  lea         esi,[eax+edi+8]  
00A000C4  movzx       eax,byte ptr [esi]  
00A000C7  mov         edx,dword ptr [ebp-14h]  
00A000CA  cmp         edi,dword ptr [edx+4]  
00A000CD  jae         00A000F5  
00A000CF  movzx       edx,byte ptr [edx+edi+8]  
00A000D4  xor         eax,edx  
00A000D6  mov         byte ptr [esi],al  
            for (int p = 0; p< 64; p++)
00A000D8  inc         edi  
00A000D9  cmp         edi,40h  
00A000DC  jl          00A000B9 
00A000B1异或edi,edi
00A000B3 mov eax,dword ptr[ebp-10h]
00A000B6 mov ebx,dword ptr[eax+4]
a[p]^=b[p];
00A000B9 mov eax,dword ptr[ebp-10h]
00A000BC cmp edi,ebx
00A000BE jae 00A000F5
00A000C0 lea esi,[eax+edi+8]
00A000C4 movzx eax,字节ptr[esi]
00A000C7 mov edx,dword ptr[ebp-14h]
00A000CA cmp edi,dword ptr[edx+4]
00A000CD在00A000F5
00A000CF movzx edx,字节ptr[edx+edi+8]
00A000D4异或eax,edx
00A000D6 mov字节ptr[esi],al
对于(int p=0;p<64;p++)
00A000D8公司edi
00A000D9 cmp edi,40小时
00A000DC jl 00A000B9
没有太大帮助,循环仍然没有展开,运行时边界检查仍然存在


值得注意的是,x86编译器找到了一个寄存器(
EBX
)来缓存一个数组的长度,但寄存器用完了,每次迭代都被迫从内存访问另一个数组长度。这应该是一个“便宜”的一级缓存访问,但仍然比寄存器访问慢,而且比没有边界检查慢得多。

发布两个版本的反汇编列表。一般来说,JIT并不聪明。应该是原始的、按字面意思编译的代码。此外,还应使用.Length作为上限,以至少消除一次阵列访问检查。即使在您编写的版本中,RyuJIT也能够消除这两种检查。这是我能找到的关于RyuJIT的唯一好处。我添加了从C#编译器生成的IL。它们是相同的。那么为您的代码生成的IL和编译器生成的IL呢?那两个也一样吗?我认为这就是这里应该比较的。区别可能存在于单个指令中,比如数组边界检查或类似的东西。我们需要x86代码,IL是直译的,没有意义。它是便携式的,所以它总是相同的。右键单击,显示反汇编。@Matthias进入调试设置并取消选中“抑制JIT优化”。然后您可以看到正确的拆卸。
006F00D8  push        ebp  
006F00D9  mov         ebp,esp  
006F00DB  push        edi  
006F00DC  push        esi  
006F00DD  push        ebx  
006F00DE  push        eax  
006F00DF  mov         dword ptr [ebp-10h],edx  
006F00E2  xor         edi,edi  
006F00E4  mov         ebx,dword ptr [ecx+4]  
006F00E7  cmp         edi,ebx  
006F00E9  jae         006F010F  
006F00EB  lea         esi,[ecx+edi+8]  
006F00EF  movzx       eax,byte ptr [esi]  
006F00F2  mov         edx,dword ptr [ebp-10h]  
006F00F5  cmp         edi,dword ptr [edx+4]  
006F00F8  jae         006F010F  
006F00FA  movzx       edx,byte ptr [edx+edi+8]  
006F00FF  xor         eax,edx  
006F0101  mov         byte ptr [esi],al  
006F0103  inc         edi  
006F0104  cmp         edi,40h  
006F0107  jl          006F00E7  
006F0109  pop         ecx  
006F010A  pop         ebx  
006F010B  pop         esi  
006F010C  pop         edi  
006F010D  pop         ebp  
006F010E  ret
00007FFD4A3000FB  mov         rax,qword ptr [rsi+8]  
00007FFD4A3000FF  mov         rax,qword ptr [rbp+8]  
00007FFD4A300103  nop         word ptr [rax+rax]  
00007FFD4A300110  movzx       ecx,byte ptr [rsi+rdx+10h]  
00007FFD4A300115  movzx       eax,byte ptr [rdx+rbp+10h]  
00007FFD4A30011A  xor         ecx,eax  
00007FFD4A30011C  mov         byte ptr [rsi+rdx+10h],cl  
00007FFD4A300120  movzx       ecx,byte ptr [rsi+rdx+11h]  
00007FFD4A300125  movzx       eax,byte ptr [rdx+rbp+11h]  
00007FFD4A30012A  xor         ecx,eax  
00007FFD4A30012C  mov         byte ptr [rsi+rdx+11h],cl  
00007FFD4A300130  movzx       ecx,byte ptr [rsi+rdx+12h]  
00007FFD4A300135  movzx       eax,byte ptr [rdx+rbp+12h]  
00007FFD4A30013A  xor         ecx,eax  
00007FFD4A30013C  mov         byte ptr [rsi+rdx+12h],cl  
00007FFD4A300140  movzx       ecx,byte ptr [rsi+rdx+13h]  
00007FFD4A300145  movzx       eax,byte ptr [rdx+rbp+13h]  
00007FFD4A30014A  xor         ecx,eax  
00007FFD4A30014C  mov         byte ptr [rsi+rdx+13h],cl  
00007FFD4A300150  add         rdx,4  
00007FFD4A300154  cmp         rdx,40h  
00007FFD4A300158  jl          00007FFD4A300110
00A000B1  xor         edi,edi  
00A000B3  mov         eax,dword ptr [ebp-10h]  
00A000B6  mov         ebx,dword ptr [eax+4]  
                a[p] ^= b[p];
00A000B9  mov         eax,dword ptr [ebp-10h]  
00A000BC  cmp         edi,ebx  
00A000BE  jae         00A000F5  
00A000C0  lea         esi,[eax+edi+8]  
00A000C4  movzx       eax,byte ptr [esi]  
00A000C7  mov         edx,dword ptr [ebp-14h]  
00A000CA  cmp         edi,dword ptr [edx+4]  
00A000CD  jae         00A000F5  
00A000CF  movzx       edx,byte ptr [edx+edi+8]  
00A000D4  xor         eax,edx  
00A000D6  mov         byte ptr [esi],al  
            for (int p = 0; p< 64; p++)
00A000D8  inc         edi  
00A000D9  cmp         edi,40h  
00A000DC  jl          00A000B9