.NET性能优化-快速遍历List集合

简介

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

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

遍历方式

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

public class ListBenchmark
{private List<int> _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 List<int>(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()来实现功能。编译后的代码如下所示:ae47a29bdb2cbe1c8e7e85b9f553abed.png其中MoveNext()方法实现中会确保在迭代中不会有其它线程修改集合,如果发生了修改则会抛出InvalidOperationException异常,另外它会有溢出检查,检查当前索引是不是合法的,还需要将对应的元素赋值给enumerator.Current属性,所以其实它的性能并不是最好的,代码片段如下所示:be02197eaa1ae71f615589c3f0db5aa1.png我们来看看它在不同集合大小的性能怎么样,结果如下所示:dd6917c0e88cb0996e7573949c312e8a.png可以看到在Size不同的情况下,耗时程线性增长关系,就算是没有任何处理逻辑的遍历100w的数据,则需要至少1s。

使用List的ForEach方法

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

[Benchmark]  
public void List_Foreach()  
{  _list.ForEach(_ => { });  
}

它是List<T>内部实现的方法,所以能直接访问私有数组,另外能避免掉溢出检查;按照理论上来说它应该会很快速;但是在我们的场景中只有一个空方法,可能表现并不会有完全内联调用的foreach方法好。下面是ForEach方法的源码,可以看到它没有了溢出检查,不过还保留了并发的版本号检查。55b0480024c2dc049a511875cfaedb4b.png另外由于需要给ForEach方法传递委托,所以在调用代码中,每一次都会检查闭包生成类中的委托对象是否为空,如果不为空则new Action<T>(),如下所示:12469bc257e0a8528e89bd40ac040c01.png我们来看看它与foreach关键字相比性能上有什么差别吧。下图是基准测试的结果:a2b5d4f9e6623fd09a44e498ca313610.png从测试结果来看,要比直接使用foreach关键字慢40%,看来如非必要,直接使用foreach是比较好的选择,那么还有没有什么更快的方式呢?

for循环遍历

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

public void For()  
{  for (var i = 0; i < _list.Count; i++)  {  // 如果是空循环的话,会被编译器优化// 我们加一行代码使其不会被编译器优化_ = _list[i];  }  
}

来看看它的结果吧。160b2b8d02c6551c8cd8fe7c822943e7.png这看来就是我们所期待的方式了,直接使用for循环要比foreach60%,原本需要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];  }  
}

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

直接访问底层数组是非常危险的,你一定要清楚自己每一行代码在做什么,并且有足够的测试。基准测试结果如下所示:ea07469888f7f2081248c92d164d4f97.pngWow,使用CollectionsMarshal比使用foreach要快79%,不过应该是JIT优化的原因,使用foreachfor关键字循环Span没有很大的差别。

总结

今天和大家聊了聊如何快速的遍历List集合,在大多数的情况下推荐大家使用foreach关键字,它既有溢出检查也有多线程下版本号的控制,可以让我们更容易的写出正确的代码。

如果在需要高性能和大数据量的场景,那么推荐直接使用forCollectionsMarshal.AsSpan来遍历集合;当然,使用CollectionsMarshal.AsSpan一定要注意使用方式。

附录

本文源码链接:

https://github.com/InCerryGit/BlogCodes/tree/main/Fast-Enumerate-List

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Thread、Runnable、Callable、Future ... 的关系?

Thread、Runnable、Callable、Future、FutureTask&#xff0c;你能详细讲出他们的内部关系么&#xff1f;这也是面试经常问到的问题。 1. Thread 和 Runnable 1.1 Thread 我们先看一下 Thread 最简单的使用姿势&#xff1a; public class MyThread extends Thread {public M…

Brocade 光纤交换机常用命令

Brocade SAN交换机常用命令使用电脑连接Brocade SAN交换机常用命令使用电脑连接管理网口&#xff0c;默认IP地址为&#xff1a;192.168.0.1&#xff0c;掩码&#xff1a;255.255.255.0默认用户名&#xff1a;admin&#xff0c;默认密码password1. switchStatusShow查看交换机的…

EntityFramework6.X 之 Fulent

Fulent Fulent是配置领域模型类的另一个方法&#xff0c;它比DataAnnotations提供更多的配置&#xff0c;提供以下三种方法映射 Mappings To Database Model-Wide Mapping 设置默认架构&#xff0c;设置经典约束 Entity Mapping 映射单个或多个表格或架构&#xff0c;映射…

Visual Studio 2022 正式支持 .NET MAUI 开发

点击上方蓝字关注我们&#xff08;本文阅读时间&#xff1a;5分钟)我们很高兴地宣布 Visual Studio 2022 正式支持 .NET MAUI 开发。现在&#xff0c;您可以使用 .NET 更快地构建跨平台原生客户端应用程序&#xff0c;并将它们从单个代码库发布到 Android、iOS、macOS 和 Windo…

cxGrid 在 GridMode = True 模式下实现标题点击排序以及标题列过滤筛选!!!

最近使用 cxGrid 这个表格控件&#xff0c;发现加载十几万笔数据时加载特别慢&#xff0c;同时用 DBGridEh 加载来对比&#xff0c;发现速度差异很大&#xff0c; 原来是 cxGrid 的 GridView 默认 GridModeFalse &#xff0c;此时加载数据集时会设置每一列的属性&#xff08;例…

Docker学习笔记

简介 Docker 属于 Linux 容器的一种封装&#xff0c;提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。 Docker 将应用程序与该程序的依赖&#xff0c;打包在一个文件里面。运行这个文件&#xff0c;就会生成一个虚拟容器。程序在这个虚拟容器里运行&#xf…

python访问数据库

1. python DB api简介 python DB api python访问数据库的统一接口规范&#xff0c;详细可参考https://www.python.org/dev/peps/pep-0249/python DB api中主要包括三个重要的对象 数据库连接对象 connection&#xff0c;数据库交互对象 cursor和数据库异常类 exceptions2. 使用…

错误:“filesystem“ 不是 “std“ 的成员

分析原因&#xff1a;应该项目是C版本问题 1、项目属性 → 配置属性 → 常规 → C语言标准 2、项目属性 → C/C → 语言 → C语言标准 3、项目属性 → C/C → 建议行 → 其它选项 → 添加&#xff1a;/Zc:__cplusplus

Blazor预研与实战

背景最近一直在搞一件事&#xff0c;就是熟悉Blazor&#xff0c;后期需要将Blazor真正运用到项目内。前期做了一些调研&#xff0c;包括但不限于Blazor知识学习组件库生态预研与现有SPA框架做比对与WebForm做比对自己动手做个演示项目最终的体验非常不错&#xff0c;功能全面。…

MySQL Internals Manual

http://dev.mysql.com/doc/internals/en/

导入工程后编译不过,报错: apply plugin: 'com.github.dcendents.android-maven'

编译时报错&#xff1a; Error:(2, 0) No service of type Factory apply plugin: com.github.dcendents.android-maven 原来是导入工程时修改了根目录下的build.gradle的android gradle版本。 源码gradle版本是1.1.3&#xff0c;导入工程后修改为2.2.2&#xff0c;修改gradle-…

并发编程10大坑,你踩过几个?

目录 前言 1. SimpleDateFormat线程不安全 2. 双重检查锁的漏洞 3. volatile的原子性 4. 死锁 4.1 缩小锁的范围 4.2 保证锁的顺序 5. 没释放锁 6. HashMap导致内存溢出 7. 使用默认线程池 8. Async注解的陷阱 9. 自旋锁浪费cpu资源 10. ThreadLocal用完没清空 前…

CAP理论与MongoDB一致性、可用性的一些思考

大约在五六年前&#xff0c;第一次接触到了当时已经是hot topic的NoSql。不过那个时候学的用的都是mysql&#xff0c;Nosql对于我而言还是新事物&#xff0c;并没有真正使用&#xff0c;只是不明觉厉。但是印象深刻的是这么一张图片&#xff08;后来google到图片来自这里&#…

十年老站吐血迁移实录

因为一些内容审查方面的原因&#xff0c;一个运行了很久的论坛被要求限期迁移出现有机房。管制很严&#xff0c;要求在1-2天内完成。论坛的文件&#xff08;主要是图片及附件&#xff09;100多G,数据库有80G。我评估了一下&#xff0c;导出数据&#xff0c;部署新环境&#xff…

【开源】一个WPF开发的XML记事本

今天推荐一个WPF开源项目&#xff1a;XmlNotepad[1]。此仓库由 WPF开发者[2] 推荐&#xff0c;站长简单翻译分享。介绍XML Notepad[3] 是一个 Windows 程序&#xff0c;它为浏览和编辑 XML 文档提供了一个简单直观的用户界面。有四种安装方式&#xff1a;ClickOnce 安装程序[4]…

log4j2 mybatis 显示 sql 和 结果集

为什么80%的码农都做不了架构师&#xff1f;>>> 首先说明版本&#xff1a; log4j 版本是 第2版beta9&#xff0c;引用jar包 log4j-api-2.0-beta9.jar log4j-core-2.0-beta9.jar mybatis 是 mybatis-3.2.3 log4j2 配置 如下&#xff0c;命名为 log4j2.xml&#xff0…

深入解析volatile关键字

目录 1. 初步认识volatile 2. volatile的特性一&#xff1a;保证可见性 2.1 volatile关键字是如何保证可见性的&#xff1f; 2.2 从JMM来看可见性 2.3 从硬件层面了解可见性的本质 2.3.1 CPU高速缓存 2.3.2 缓存一致性 2.3.3 MESI 协议 2.3.4 缓存一致性小结 3. vola…

Nginx基础配置

一、主配置文件结构main block&#xff1b;#全局块配置全局生效event{#事件驱动相关配置 }http{#http/https协议相关配置段 server { ... }&#xff1a;#每个server用于定义一个虚拟主机&#xff1b; server { ... server_name root alias location [OPERATOR] URL { ... if CO…

上周面试回来后写的Java面试总结,想进BAT必看

上周陪同之前一起工作的同事去面试&#xff08;乔治&#xff0c;小袁&#xff0c;鹏飞&#xff08;面试人&#xff09;&#xff09;&#xff0c;第一站是去深圳&#xff0c;第二站上海&#xff0c;第三站杭州。面试什么公司我在这里就不多说了&#xff0c;你们知道是一线公司就…

从贝叶斯方法谈到贝叶斯网络

从贝叶斯方法谈到贝叶斯网络0 引言其实。介绍贝叶斯定理、贝叶斯方法、贝叶斯判断的资料、书籍不少&#xff0c;比方《数理统计学简史》&#xff0c;以及《统计决策论及贝叶斯分析 James O.Berger著》等等&#xff0c;然介绍贝叶斯网络的中文资料则非常少。中文书籍总共也没几本…