c# - 为什么循环 Array.AsSpan() 更快?
问题描述
| Method | Mean | Error | StdDev |
|--------------- |---------:|---------:|---------:|
| ArrayRefIndex | 661.9 us | 12.95 us | 15.42 us |
| ArraySpanIndex | 640.4 us | 4.08 us | 3.82 us |
为什么循环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;
}
}
解决方案
简答
Span 保证了“任意内存的连续区域”,它允许编译器对 CLI 指令进行优化。
长答案
如果您在反汇编中打开您提供的代码(调试 -> Windows -> 反汇编),您将在 ArrayRefIndex() 中找到以下内容
ref Struct16 value = ref myArray[i];
00007FFC3E860DCC movsxd r8,ecx
00007FFC3E860DCF shl r8,4
00007FFC3E860DD3 lea r8,[rax+r8+10h] // <----
“lea”代表加载有效地址。这意味着, ArrayRefIndex 函数较慢,因为它将结构数组视为无序内存。
当我们查看 ArraySpanIndex 时,我们可以看到它没有“lea”指令,而是仅用“add”替换。我没有确认,但这很可能只是为下一个内存位置添加结构长度。无论哪种方式,“lea”指令是两个函数之间的唯一增量,将罪魁祸首缩小到时间差。
ref Struct16 value = ref span[i];
00007FFC3E8613FA movsxd r8,ecx
00007FFC3E8613FD shl r8,4
00007FFC3E861401 add r8,rax // <----
推荐阅读
- c - Getopt 返回 -1 但它有有效数据
- c - 为什么 _exit 会失败?
- c++ - 如何将自定义比较器传递到 C++ 中的映射构造函数内联的优先级队列构造函数?
- css - 如何将盒子阴影转换为线性渐变?
- directx - 我的 HLSL 着色器中的 TEXCOORD0 总是指向 (0,0)
- node.js - 如何在具有 chromedriver 层的 AWS Lambda 上运行量角器测试
- javascript - 路由器链接应基于子路由处于活动状态
- amazon-web-services - 从 lambda 环境变量将 JWKS 传递给 jwks-rsa npm 包
- sql - SQL Server查询以检查日期是否具有相同的数字
- laravel - 参数不存在 Yajra Datatable Laravel