什么时候对数组的跨度进行迭代变得比对数组本身进行迭代更快

问题描述 投票:0回答:1

在 Nick Chapsas 的最近视频最后,他评论说 Find

 上的 
List<T>
 方法“未优化”,因为代码不是在数组的 Span 上迭代,而是在数组本身上迭代:

public T? Find(Predicate<T> match) {
    if (match == null) {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }

    for (int i = 0; i < _size; i++) {
        if (match(_items[i])) {
            return _items[i];
        }
    }
    return default;
}

我认为在 Spans 上进行迭代可以像在数组上一样快(但不是更快),来自 Stephen Toub 关于 Spans 的 (已更新?)博客文章的这段:

汇编代码部分如此相似,因为消除了 边界检查。但同样重要的是 JIT 对跨度的认可 索引器作为内在函数,意味着 JIT 生成特殊代码 对于索引器,而不是将其实际的 IL 代码转换为 组装。

这一切都是为了说明运行时可以申请跨度 对数组进行相同类型的优化,使跨度成为 访问数据的有效机制。

什么时候变得不言而喻了,对数组的 Span 进行迭代会与对数组本身进行迭代一样快,甚至可能更快?哪个 .NET 版本引入了这些不能用于数组的 JIT Span 优化?

c# arrays
1个回答
1
投票

跨跨度迭代有两个优点:

  1. 它避免了间接进入数组的字段(而不是局部字段)
  2. 跨度的长度以可预测的方式已知

如果不是

_size
also是本地人,也许可以修复代码;即给定:

for(int i = 0 ; i < _arr.Length ; i++)
{
    DoSomething(_arr[i]);
}

简单地将

_arr
提升到本地允许 JIT 省略边界检查,因为数组无法重新分配

var arr = _arr;
for(int i = 0 ; i < arr.Length ; i++)
{
    DoSomething(arr[i]);
}

另请注意,

foreach (var x in _arr)
foreach (var x in arr)
都可以,并允许忽略边界检查。然而,讨厌的
_size
意味着边界检查不能被省略,如果
_size
_arr.Length
(超大缓冲区)不匹配,我们就不能使用简单的
foreach
。 Span 间接解决了这个问题,因为 span 是有界的;创建长度为
_size
的跨度意味着 一旦构建了跨度(在构造函数中进行了边界验证),JIT 就可以信任它,并省略边界检查。

© www.soinside.com 2019 - 2024. All rights reserved.