在 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 优化?
跨跨度迭代有两个优点:
如果不是
_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 就可以信任它,并省略边界检查。