为什么 Array.AsSpan() 循环速度更快?

Jan*_*kke 6 c# arrays performance benchmarking

|         Method |     Mean |    Error |   StdDev |
|--------------- |---------:|---------:|---------:|
|  ArrayRefIndex | 661.9 us | 12.95 us | 15.42 us |
| ArraySpanIndex | 640.4 us |  4.08 us |  3.82 us |
Run Code Online (Sandbox Code Playgroud)

为什么循环array.AsSpan()比直接循环源数组更快?

public struct Struct16
{
    public int A;
    public int B;
    public int C;
    public int D;
}

public class Program
{
    public const int COUNT = 100000;
    
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<Program>();
    }

    [Benchmark]
    public int ArrayRefIndex()
    {
        Struct16[] myArray = new Struct16[COUNT];
        int sum = 0;
        for (int i = 0; i < myArray.Length; i++)
        {
            ref Struct16 value = ref myArray[i];
            sum += value.A = value.A + value.B + value.C + value.D;
        }
        return sum;
    }

    [Benchmark]
    public int ArraySpanIndex()
    {
        Struct16[] myArray = new Struct16[COUNT];
        int sum = 0;
        Span<Struct16> span = myArray.AsSpan();
        for (int i = 0; i < span.Length; i++)
        {
            ref Struct16 value = ref span[i];
            sum += value.A = value.A + value.B + value.C + value.D;
        }
        return sum;
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 6

简答

Span 保证“任意内存的连续区域”,这允许编译器对 CLI 指令进行优化。

长答案

如果您在反汇编中打开提供的代码(调试 -> Windows -> 反汇编),您将在 ArrayRefIndex() 中找到以下内容

ref Struct16 value = ref myArray[i];
00007FFC3E860DCC  movsxd      r8,ecx  
00007FFC3E860DCF  shl         r8,4  
00007FFC3E860DD3  lea         r8,[rax+r8+10h] // <----
Run Code Online (Sandbox Code Playgroud)

“lea”代表加载有效地址。这意味着 ArrayRefIndex 函数速度较慢,因为它将 struct array 视为无序内存

当我们查看 ArraySpanIndex 时,我们可以看到它没有“lea”指令,而是仅替换为“add”。我没有确认,但这可能只是添加下一个内存位置的结构长度。不管怎样,“lea”指令是两个函数之间唯一的增量,将罪魁祸首缩小到了时间差。

ref Struct16 value = ref span[i];
00007FFC3E8613FA  movsxd      r8,ecx  
00007FFC3E8613FD  shl         r8,4  
00007FFC3E861401  add         r8,rax  // <----
Run Code Online (Sandbox Code Playgroud)