各类手机网站建设,广州外贸网站开发,钉子 wordpress,如何创建自己公司的网站简介System.Collections.Generic.ListT是.NET中的泛型集合类#xff0c;可以存储任何类型的数据#xff0c;因为它的便利和丰富的API#xff0c;在我们平时会广泛的使用到它#xff0c;可以说是使用最多的集合类。在代码编写中#xff0c;我们经常需要遍历一个Lis…简介System.Collections.Generic.ListT是.NET中的泛型集合类可以存储任何类型的数据因为它的便利和丰富的API在我们平时会广泛的使用到它可以说是使用最多的集合类。在代码编写中我们经常需要遍历一个ListT集合获取里面的得元素进行一些业务的处理。通常情况下集合内的元素不是很多遍历起来非常快。但是对于一些大数据处理统计实时计算等动辄数万、十万数据的ListT集合如何快速的遍历它呢这就是今天需要和大家分享的内容。遍历方式我们来看看不同遍历方式的性能表现构建了如下一个性能基准测试使用不同数量级的集合遍历来看看不同方式的性能表现。代码片段如下所示public class ListBenchmark
{private Listint _list default!;// 分别测试10、1千、1万、10万及100万数据时的表现[Params(10, 1000, 1_0000, 10_0000, 100_0000)]public int Size { get; set; }[GlobalSetup]public void Setup(){// 提前创建好数组_list new Listint(Size);for (var i 0; i Size; i){_list.Add(i);}}
}使用foreach语句foreach是我们遍历集合是最常用的方式了它是一个语法糖实现了迭代器模式它也是作为我们本次的基准。[Benchmark(Baseline true)]
public void Foreach()
{ foreach (var item in _list) { }
}因为foreach语句是一个语法糖所以最终编译器会使用while循环调用GetEnumerator()和MoveNext()来实现功能。编译后的代码如下所示其中MoveNext()方法实现中会确保在迭代中不会有其它线程修改集合如果发生了修改则会抛出InvalidOperationException异常另外它会有溢出检查检查当前索引是不是合法的还需要将对应的元素赋值给enumerator.Current属性所以其实它的性能并不是最好的代码片段如下所示我们来看看它在不同集合大小的性能怎么样结果如下所示可以看到在Size不同的情况下耗时程线性增长关系就算是没有任何处理逻辑的遍历100w的数据则需要至少1s。使用List的ForEach方法另外一个比较常用的方式就是使用ListT.ForEach()方法这个方法允许你传入一个ActionT委托它会在遍历元素时调用ActionT委托。[Benchmark]
public void List_Foreach()
{ _list.ForEach(_ { });
}它是ListT内部实现的方法所以能直接访问私有数组另外能避免掉溢出检查按照理论上来说它应该会很快速但是在我们的场景中只有一个空方法可能表现并不会有完全内联调用的foreach方法好。下面是ForEach方法的源码可以看到它没有了溢出检查不过还保留了并发的版本号检查。另外由于需要给ForEach方法传递委托所以在调用代码中每一次都会检查闭包生成类中的委托对象是否为空如果不为空则new ActionT()如下所示我们来看看它与foreach关键字相比性能上有什么差别吧。下图是基准测试的结果从测试结果来看要比直接使用foreach关键字慢40%看来如非必要直接使用foreach是比较好的选择那么还有没有什么更快的方式呢for循环遍历回到了我们最古老的方式就是使用for关键字来遍历集合。它应该是目前来说性能最好的遍历方式因为它不需要像之前的那几种方式一样有一些多余的代码不过索引器同样有检查防止溢出另外很显然它不会检查版本号所以在多线程环境下集合被改变使用for不会有异常抛出。测试代码如下所示public void For()
{ for (var i 0; i _list.Count; i) { // 如果是空循环的话会被编译器优化// 我们加一行代码使其不会被编译器优化_ _list[i]; }
}来看看它的结果吧。这看来就是我们所期待的方式了直接使用for循环要比foreach快60%原本需要1秒才能遍历完的集合现在只需要400毫秒。那么还有没有更快的方式呢使用CollectionsMarshal在.NET5以后dotnet社区为了让集合操作性能更好从而实现了CollectionsMarshal类这个类里面实现了对于集合类型的原生数组的访问方式如果你看过我的【.NET性能优化-你应该为集合类型设置初始大小】文章就知道很多数据结构的底层实现都是数组。所以它能跳过各种检测直接访问原始的数组应该是最快速的。代码如下所示// 为了测试编译器有没有针对foreach span优化
// 同时测试for span
public void Foreach_Span()
{ foreach (var item in CollectionsMarshal.AsSpan(_list)) { }
} public void For_Span()
{ var span CollectionsMarshal.AsSpan(_list); for (int i 0; i span.Length; i) { _ span[i]; }
}可以看到编译器生成的代码是非常高效的。直接访问底层数组是非常危险的你一定要清楚自己每一行代码在做什么并且有足够的测试。基准测试结果如下所示Wow使用CollectionsMarshal比使用foreach要快79%不过应该是JIT优化的原因使用foreach和for关键字循环Span没有很大的差别。总结今天和大家聊了聊如何快速的遍历List集合在大多数的情况下推荐大家使用foreach关键字它既有溢出检查也有多线程下版本号的控制可以让我们更容易的写出正确的代码。如果在需要高性能和大数据量的场景那么推荐直接使用for和CollectionsMarshal.AsSpan来遍历集合当然使用CollectionsMarshal.AsSpan一定要注意使用方式。附录本文源码链接https://github.com/InCerryGit/BlogCodes/tree/main/Fast-Enumerate-List