数组边界检查for循环中的优化

aus*_*ush 4 c# arrays for-loop compiler-optimization bounds-check-elimination

        var ar = new int[500000000];

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

        var length = ar.Length;
        for (var i = 0; i < length; i++)
        {
            if (ar[i] == 0);
        }

        sw.Stop();
Run Code Online (Sandbox Code Playgroud)

sw.ElapsedMilliseconds:~2930ms

        var ar = new int[500000000];

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

        for (var i = 0; i < ar.Length; i++)
        {
            if (ar[i] == 0);
        }

        sw.Stop();
Run Code Online (Sandbox Code Playgroud)

sw.ElapsedMilliseconds:~3520ms

Win8x64,VS12,.NET4.5,发布版本,"优化代码".

据我所知,第二种方法应该更快,因为数组边界检查优化.我错过了什么吗?

Mar*_*ell 6

我也在调试器之外使用Win8 x64,.NET 4.5,Release版本(这是一个重要的版本); 我明白了:

0: 813ms vs 421ms
1: 439ms vs 420ms
2: 440ms vs 420ms
3: 431ms vs 429ms
4: 433ms vs 427ms
5: 424ms vs 437ms
6: 427ms vs 434ms
7: 430ms vs 432ms
8: 432ms vs 435ms
9: 430ms vs 430ms
10: 427ms vs 418ms
11: 422ms vs 421ms
12: 434ms vs 420ms
13: 439ms vs 425ms
14: 426ms vs 429ms
15: 426ms vs 426ms
16: 417ms vs 432ms
17: 442ms vs 425ms
18: 420ms vs 429ms
19: 420ms vs 422ms
Run Code Online (Sandbox Code Playgroud)

第一个支付JIT /"融合"成本,但总体而言大致相同(每列中的一些看起来更快,但总体来说并不多说).

using System;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        var ar = new int[500000000];

        for (int j = 0; j < 20; j++)
        {
            var sw = Stopwatch.StartNew();
            var length = ar.Length;
            for (var i = 0; i < length; i++)
            {
                if (ar[i] == 0) ;
            }

            sw.Stop();
            long hoisted = sw.ElapsedMilliseconds;

            sw = Stopwatch.StartNew();
            for (var i = 0; i < ar.Length; i++)
            {
                if (ar[i] == 0) ;
            }
            sw.Stop();
            long direct = sw.ElapsedMilliseconds;

            Console.WriteLine("{0}: {1}ms vs {2}ms", j, hoisted, direct);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


har*_*old 5

我对此进行了更多调查,发现很难制作一个实际显示边界检查消除优化效果的基准.

首先是旧基准的一些问题:

  • 反汇编表明JIT编译器也能够优化第一个版本.这对我来说是一个惊喜,但拆卸不是谎言.当然,这完全违背了这个基准的目的.修复:将长度作为函数参数.
  • 数组太大,这意味着缓存未命中,这会给我们的信号增加很多噪音.修复:使用短数组但多次循环.

但现在真正的问题是:它正在做一些过于聪明的事情.内循环中没有数组边界测试,即使循环的长度来自函数参数.生成的代码不同,但内部循环基本相同.不完全(不同的寄存器等)但它遵循相同的模式:

_loop: mov eax, [somewhere + index]
       add index, 4
       cmp index, end
       jl _loop
Run Code Online (Sandbox Code Playgroud)

执行时间没有显着差异,因为最重要的生成代码部分没有显着差异.