架构师_程序员_码农网

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2793|回复: 3

[资料] 【转】.NET 性能优化-快速遍历 List 集合

[复制链接]
发表于 2022-8-28 20:51:16 | 显示全部楼层 |阅读模式
简介

System.Collections.Generic.List<T>是.NET中的泛型集合类,可以存储任何类型的数据,因为它的便利和丰富的API,在我们平时会广泛的使用到它,可以说是使用最多的集合类。

在代码编写中,我们经常需要遍历一个List<T>集合,获取里面的得元素进行一些业务的处理。通常情况下,集合内的元素不是很多,遍历起来非常快。但是对于一些大数据处理,统计,实时计算等动辄数万、十万数据的List<T>集合,如何快速的遍历它呢?这就是今天需要和大家分享的内容。

遍历方式

我们来看看不同遍历方式的性能表现,构建了如下一个性能基准测试,使用不同数量级的集合遍历来看看不同方式的性能表现。代码片段如下所示:

使用foreach语句

foreach是我们遍历集合是最常用的方式了,它是一个语法糖实现了迭代器模式,它也是作为我们本次的基准。

因为 foreach 语句是一个语法糖,所以最终编译器会使用 while 循环调用 GetEnumerator() 和 MoveNext() 来实现功能。编译后的代码如下所示:

QQ截图20220828204300.jpg

其中MoveNext()方法实现中会确保在迭代中不会有其它线程修改集合,如果发生了修改则会抛出InvalidOperationException异常,另外它会有溢出检查,检查当前索引是不是合法的,还需要将对应的元素赋值给enumerator.Current属性,所以其实它的性能并不是最好的,代码片段如下所示:

QQ截图20220828204423.jpg

我们来看看它在不同集合大小的性能怎么样,结果如下所示:

QQ截图20220828204503.jpg

可以看到在Size不同的情况下,耗时程线性增长关系,就算是没有任何处理逻辑的遍历100w的数据,则需要至少1s。

使用List的ForEach方法

另外一个比较常用的方式就是使用List<T>.ForEach()方法,这个方法允许你传入一个Action<T>委托,它会在遍历元素时调用Action<T>委托。

它是List<T>内部实现的方法,所以能直接访问私有数组,另外能避免掉溢出检查;按照理论上来说它应该会很快速;但是在我们的场景中只有一个空方法,可能表现并不会有完全内联调用的foreach方法好。下面是ForEach方法的源码,可以看到它没有了溢出检查,不过还保留了并发的版本号检查。

QQ截图20220828204558.jpg

另外由于需要给ForEach方法传递委托,所以在调用代码中,每一次都会检查闭包生成类中的委托对象是否为空,如果不为空则new Action<T>(),如下所示:

QQ截图20220828204625.jpg

我们来看看它与foreach关键字相比性能上有什么差别吧。下图是基准测试的结果:

QQ截图20220828204650.jpg

从测试结果来看,要比直接使用foreach关键字慢40%,看来如非必要,直接使用foreach是比较好的选择,那么还有没有什么更快的方式呢?

for循环遍历

回到了我们最古老的方式,就是使用for关键字来遍历集合。它应该是目前来说性能最好的遍历方式,因为它不需要像之前的那几种方式一样有一些多余的代码(不过索引器同样有检查,防止溢出),另外很显然它不会检查版本号,所以在多线程环境下集合被改变,使用for不会有异常抛出。测试代码如下所示:

来看看它的结果吧。

QQ截图20220828204746.jpg

这看来就是我们所期待的方式了,直接使用for循环要比foreach快60%,原本需要1秒才能遍历完的集合,现在只需要400毫秒。那么还有没有更快的方式呢?

使用CollectionsMarshal

在.NET5以后,dotnet社区为了让集合操作性能更好,从而实现了CollectionsMarshal类;这个类里面实现了对于集合类型的原生数组的访问方式(如果你看过我的【.NET性能优化-你应该为集合类型设置初始大小】文章,就知道很多数据结构的底层实现都是数组)。所以它能跳过各种检测,直接访问原始的数组,应该是最快速的。代码如下所示:

可以看到编译器生成的代码是非常高效的。

QQ截图20220828204906.jpg

直接访问底层数组是非常危险的,你一定要清楚自己每一行代码在做什么,并且有足够的测试。基准测试结果如下所示:

QQ截图20220828204929.jpg

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

原文链接:https://mp.weixin.qq.com/s/MatmDDoTrQ1VSyvwfuRgoQ





上一篇:RabbitMQ AMQP 消息架构详解
下一篇:网线水晶头 T568A 和 T568B 标准和区别
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2022-9-4 22:15:52 | 显示全部楼层
学习学习
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2022-9-8 10:33:05 | 显示全部楼层
学习 学习
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
发表于 2023-6-27 22:39:13 | 显示全部楼层
你好12306 数据能发我下么私信我
码农网,只发表在实践过程中,遇到的技术难题,不误导他人。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

免责声明:
码农网所发布的一切软件、编程资料或者文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:help@itsvse.com

QQ|手机版|小黑屋|架构师 ( 鲁ICP备14021824号-2 )|网站地图

GMT+8, 2024-4-27 18:10

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表