为什么本地数组比静态读取/写入更快?

Ron*_*dau 14 c# arrays performance

我正在编写一些基准测试来弄清楚为什么类似的纯算法(在类中没有C++ lib/.net)在C++中比在C#中运行得快得多,即使在考虑预期的特征差异时也是如此.虽然这样做我偶然发现了令我感到困惑的这两项测试,但是有没有人知道为什么一个人比另一个慢得多?第二个(在我的机器上需要51毫秒vs 88)的唯一区别是2个数组在方法中而不是在外部声明.在这两种情况下,数组都是在开始计时之前创建的.

    const int Runs = 100;
    const int Width = 5000;
    const int Height = 5000;
    const int Size = Width * Height;


    static int[] Input = Enumerable.Range(0, Size).ToArray();
    static int[] Output = new int[Size * 2];

    static int SimpleTest()
    {
        // Removing those 2 lines and using the static arrays instead give substantially slower performance, nearly half the speed!
        int[] Input = Enumerable.Range(0, Size).ToArray();
        int[] Output = new int[Size * 2];

        Stopwatch sw = new Stopwatch();
        sw.Start();

        for (int run = 0; run < Runs; run++)
        {
            int InputIndex = 0;
            for (int x = 0; x < Width; x++)
            {
                for (int y = 0; y < Height; y++)
                {
                    int pixel = Input[InputIndex];
                    var OutputIndex = InputIndex * 2;
                    Output[OutputIndex] = pixel;
                    Output[OutputIndex + 1] = pixel;
                    InputIndex++;
                }
            }
        }
        sw.Stop();
        return (int)(sw.ElapsedMilliseconds / Runs);
    }
Run Code Online (Sandbox Code Playgroud)

Ray*_*hen 15

当变量是本地变量时,编译器知道Input并且Output永远不会更改,这会打开许多​​优化.

  • InputOutput变量的值可以保存在寄存器中.
  • Input.Length并且Output.Length可以计算一次并缓存.
  • 编译器可以证明Input[InputIndex]并且Output[OutputIndex]永远不会导致数组索引超出范围,因此可以优化边界检查.
  • 编译器可以观察到结果InputOutput从未使用过,所以它可以优化循环到零!

如果使用静态版本,则编译器无法执行这些优化.编译器必须重新加载InputOutput在每个接入,并且必须在每个数组索引操作执行范围检查,以防万一另一个线程改性InputOutput.

例如,如果另一个线程执行Input = new int[Size],则所有将来的计算必须继续使用此备用Input.如果另一个线程做了Output = new int[1],那么代码必须引发一个IndexOutOfRangeException.

  • 我原来的答案是描述编译器可用的优化.它实际使用的是实验问题.实践中的优化可能因您的编译器选项或您正在使用的编译器版本而异.另请注意,如果连接了调试器,编译器将禁用某些优化. (3认同)
  • 我看到`r12`寄存器中的64位JIT缓存`Input`和`rsi`寄存器中的`Output`.它还硬编码`Input.Length`和`Output.Length`(例如`cmp rax,2faf080h`)的值来优化边界检查. (2认同)
  • @NicFoster您使用Mono进行了测试.我用微软的编译器测试过.顺便说一下,这是[关于数组边界检查优化的博客条目](http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the- clr.aspx)."全球位置的阵列"部分似乎与此相关. (2认同)

Asi*_*sik 5

使用32位JIT,我相信罪魁祸首就像Raymond Chen所提到的那样,当输入和输出是本地时,输入和输出可以保存在寄存器中,但是每次输入和输出都不需要重新加载.生成的程序集:

对于当地人:

007426F0  mov         eax,dword ptr [ebp-18h]  
007426F3  mov         edi,dword ptr [eax+4]  
                        int pixel = Input[InputIndex];
007426F6  mov         eax,dword ptr [ebp-18h]  
007426F9  cmp         edx,edi  
007426FB  jae         0074276E  
007426FD  mov         ecx,dword ptr [eax+edx*4+8] 
Run Code Online (Sandbox Code Playgroud)

对于静力学:

011C2718  mov         dword ptr [ebp-18h],edx  
011C271B  mov         esi,dword ptr ds:[3BB7E90h]  
011C2721  mov         eax,dword ptr [esi+4]  
011C2724  mov         dword ptr [ebp-1Ch],eax  
                        int pixel = Input[InputIndex];
011C2727  mov         eax,dword ptr [ebp-1Ch]  
011C272A  cmp         ecx,eax  
011C272C  jae         011C27A2  
011C272E  mov         edi,dword ptr [esi+ecx*4+8] 
Run Code Online (Sandbox Code Playgroud)

如您所见,mov esi,dword ptr ds:[3BB7E90h]访问数据段.正如您所看到的,边界检查在两种情况下都会发生(cmp-jae),因此不相关,并且实际上没有将循环优化为零.

64位JIT如何避免这个问题超出了我的范围.

以下是两种情况的完整反汇编:

快速版:

               for (int x = 0; x < Width; x++) {
007426EB  mov         dword ptr [ebp-14h],edx  
                    for (int y = 0; y < Height; y++) {
007426EE  xor         ebx,ebx  
007426F0  mov         eax,dword ptr [ebp-18h]  
007426F3  mov         edi,dword ptr [eax+4]  
                        int pixel = Input[InputIndex];
007426F6  mov         eax,dword ptr [ebp-18h]  
007426F9  cmp         edx,edi  
007426FB  jae         0074276E  
007426FD  mov         ecx,dword ptr [eax+edx*4+8]  
                        var OutputIndex = InputIndex * 2;
00742701  mov         esi,edx  
00742703  add         esi,esi  
                        Output[OutputIndex] = pixel;
00742705  mov         eax,dword ptr [ebp-1Ch]  
00742708  cmp         esi,dword ptr [eax+4]  
0074270B  jae         0074276E  
0074270D  mov         dword ptr [eax+esi*4+8],ecx  
                        Output[OutputIndex + 1] = pixel;
00742711  inc         esi  
00742712  mov         eax,dword ptr [ebp-1Ch]  
00742715  cmp         esi,dword ptr [eax+4]  
00742718  jae         0074276E  
0074271A  mov         dword ptr [eax+esi*4+8],ecx  
                        InputIndex++;
0074271E  inc         edx  
                    for (int y = 0; y < Height; y++) {
0074271F  inc         ebx  
                    for (int y = 0; y < Height; y++) {
00742720  cmp         ebx,1388h  
00742726  jl          007426F6  
                for (int x = 0; x < Width; x++) {
00742728  inc         dword ptr [ebp-14h]  
0074272B  cmp         dword ptr [ebp-14h],1388h  
00742732  jl          007426EE 
Run Code Online (Sandbox Code Playgroud)

慢版:

                for (int x = 0; x < Width; x++) {
011C2713  mov         dword ptr [ebp-14h],ecx  
                    for (int y = 0; y < Height; y++) {
011C2716  xor         edx,edx  
011C2718  mov         dword ptr [ebp-18h],edx  
011C271B  mov         esi,dword ptr ds:[3BB7E90h]  
011C2721  mov         eax,dword ptr [esi+4]  
011C2724  mov         dword ptr [ebp-1Ch],eax  
                        int pixel = Input[InputIndex];
011C2727  mov         eax,dword ptr [ebp-1Ch]  
011C272A  cmp         ecx,eax  
011C272C  jae         011C27A2  
011C272E  mov         edi,dword ptr [esi+ecx*4+8]  
                        var OutputIndex = InputIndex * 2;
011C2732  mov         ebx,ecx  
011C2734  add         ebx,ebx  
                        Output[OutputIndex] = pixel;
011C2736  mov         edx,dword ptr ds:[3BB7E94h]  
011C273C  cmp         ebx,dword ptr [edx+4]  
011C273F  jae         011C27A2  
011C2741  mov         dword ptr [edx+ebx*4+8],edi  
                        Output[OutputIndex + 1] = pixel;
011C2745  inc         ebx  
011C2746  cmp         ebx,dword ptr [edx+4]  
011C2749  jae         011C27A2  
011C274B  mov         dword ptr [edx+ebx*4+8],edi  
                        InputIndex++;
011C274F  inc         ecx  
                    for (int y = 0; y < Height; y++) {
011C2750  inc         dword ptr [ebp-18h]  
011C2753  cmp         dword ptr [ebp-18h],1388h  
011C275A  jl          011C2727  
                for (int x = 0; x < Width; x++) {
011C275C  inc         dword ptr [ebp-14h]  
011C275F  cmp         dword ptr [ebp-14h],1388h  
011C2766  jl          011C2716  
Run Code Online (Sandbox Code Playgroud)