You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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()的解决方案

  1. 提前JIT预热
    在程序启动阶段(如Main方法开头),提前调用一次空的List.ForEach()触发JIT编译,避免首次业务调用时的编译开销:
new List<int>().ForEach(_ => {});
  1. 启用分层PGO优化
    设置环境变量DOTNET_TieredPGO=1,让.NET运行时根据实际执行情况优化List.ForEach()的编译代码,缩小首次运行与后续运行的性能差距。可通过项目属性或部署环境配置该变量。

  2. 自定义扩展方法覆盖默认实现
    实现一个与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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 13:50:23