.NET 7中List.ForEach()是否存在性能问题?求解决方案
.NET 7升级后List.ForEach()首次运行性能暴跌问题排查与解决方案
问题背景
将个人项目从.NET 6升级至.NET 7后,处理百万级数据的耗时从约18秒剧增至4分30秒。定位后发现,List.ForEach()方法在.NET 7首次运行时性能极差(测试中耗时从.NET 6的1452ms飙升至7997ms),后续运行性能有所回升但仍与.NET 6存在差异。项目依赖.NET 7的其他性能提升,希望保留该版本,需确认问题是否由.NET 7导致,并寻求无需批量修改List.ForEach()调用的解决方案。
测试代码
为定位问题编写的性能测试函数如下:
private void SpeedTest() { int nbdata = 6000000; List<int> list = new(nbdata); var rnd = RandomNumberGenerator.Create(); Random rand = new(12345); for (int i = 0; i < nbdata; i++) { var rnddata = new byte[sizeof(int)]; rnd.GetBytes(rnddata); list.Add(BitConverter.ToInt32(rnddata)); } int[] arr = list.ToArray(); //Begin test int chk = 0; Stopwatch watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { int len = list.Count; for (int i = 0; i < len; i++) { chk += list[i]; } } watch.Stop(); SpeedText.Text += string.Format("List/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { for (int i = 0; i < list.Count; i++) { chk += list[i]; } } watch.Stop(); SpeedText.Text += string.Format("List/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { int len = arr.Length; for (int i = 0; i < len; i++) { chk += arr[i]; } } watch.Stop(); SpeedText.Text += string.Format("Array/for Count out: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { for (int i = 0; i < arr.Length; i++) { chk += arr[i]; } } watch.Stop(); SpeedText.Text += string.Format("Array/for Count in: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { int k = list.Count; for (int j = 0; j < k; j++) { chk += list[j]; } } watch.Stop(); SpeedText.Text += string.Format("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { foreach (int i in list) { chk += i; } } watch.Stop(); SpeedText.Text += string.Format("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { list.ForEach(i => chk += i); } watch.Stop(); SpeedText.Text += string.Format("List/foreach function: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { int k = arr.Length; for (int j = 0; j < k; j++) { chk += arr[j]; } } watch.Stop(); SpeedText.Text += string.Format("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; chk = 0; watch = Stopwatch.StartNew(); for (int rpt = 0; rpt < 100; rpt++) { foreach (int i in arr) { chk += i; } } watch.Stop(); SpeedText.Text += string.Format("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk) + Environment.NewLine; }
测试结果
.NET 6测试结果
List/for Count out: 1442ms (398007896) List/for Count in: 1446ms (398007896) Array/for Count out: 1256ms (398007896) Array/for Count in: 1254ms (398007896) List/for: 1435ms (398007896) List/foreach: 1258ms (398007896) List/foreach function: 1452ms (398007896) <= Array/for: 1255ms (398007896) Array/foreach: 1254ms (398007896)
.NET 7测试结果
List/for Count out: 1483ms (272044760) List/for Count in: 1489ms (272044760) Array/for Count out: 1255ms (272044760) Array/for Count in: 1263ms (272044760) List/for: 1482ms (272044760) List/foreach: 1873ms (272044760) List/foreach function: 7997ms (272044760) <= Array/for: 1254ms (272044760) Array/foreach: 1255ms (272044760)
核心问题代码
性能暴跌的核心触发代码为:
list.ForEach(i => chk += i);
问题根源与解决方案
问题根源
该性能差异确实由.NET 7的运行时实现变更导致。.NET 7对List.ForEach()的内部逻辑做了调整,首次调用时的委托绑定、JIT编译开销被显著放大,尤其是处理大规模集合时。后续运行性能回升是因为JIT编译完成后,委托执行路径被缓存。
无需批量修改List.ForEach()的解决方案
- 提前JIT预热
在程序启动阶段(如Main方法开头),提前调用一次空的List.ForEach()触发JIT编译,避免首次业务调用时的编译开销:
new List<int>().ForEach(_ => {});
启用分层PGO优化
设置环境变量DOTNET_TieredPGO=1,让.NET运行时根据实际执行情况优化List.ForEach()的编译代码,缩小首次运行与后续运行的性能差距。可通过项目属性或部署环境配置该变量。自定义扩展方法覆盖默认实现
实现一个与List.ForEach()签名完全一致的扩展方法,绕过.NET 7的默认实现,逻辑与.NET 6的实现对齐,避免额外开销:
public static class ListExtensions { public static void ForEach<T>(this List<T> list, Action<T> action) { if (list == null) throw new ArgumentNullException(nameof(list)); if (action == null) throw new ArgumentNullException(nameof(action)); for (int i = 0; i < list.Count; i++) { action(list[i]); } } }
添加该扩展方法后,原有list.ForEach()的调用会自动使用自定义实现,无需修改业务代码。
内容的提问来源于stack exchange,提问作者Florent H.
相关产品推荐
相关产品推荐

