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

为何C#中嵌套foreach循环性能远超SelectMany+foreach及GroupBy?

为什么嵌套foreach循环的性能比另外两种LINQ实现更优?

现有模型类

public class BenchMarks{
    public List<Parent> Parents { get; set; }
}

public class Parent {
    public List<Child> Children { get; set; }
}

public class Child {
    public string Name { get; set; }
    public int Value { get; set; }
}

BenchMarks类包含的三种实现方法

public void GetWithTwoForeach(){
    var dict = new Dictionary<string,int>();
    foreach(var parent in Parents){
        foreach(var child in parent.Children){
            if (dict.ContainsKey(child.Name))
                dict[child.Name] += child.Value;
            else
                dict.Add(child.Name, child.Value);
        }
    }
}

public void GetWithOneForeach(){
    var dict = new Dictionary<string,int>();
    foreach(var child in Parents.SelectMany(p=>p.Children)){
        if (dict.ContainsKey(child.Name))
            dict[child.Name] += child.Value;
        else
            dict.Add(child.Name, child.Value);
    }
}

public void GetWithGroupSum(){
    var childGroups = Parents.SelectMany(p=>p.Children).GroupBy(c => c.Name);   
    var dict = childGroups.ToDictionary(cg => cg.Key, cg => cg.Sum(child => child.Value));
}

问题

为何嵌套foreach循环的性能比另外两种方法高出100%以上?(首次基准测试结果显示嵌套foreach性能是另外两种方法的2倍以上)

编辑补充

此前的基准测试受测试数据有序性影响,修改测试数据初始化逻辑后,性能差距有所缩小,但嵌套foreach仍比LINQ实现有明显性能优势。

修改后的测试数据初始化代码

public void Setup()
{
    Parents = new List<Parent>();
    
    var names = new List<string>{"Tigo","Edo","Kumal","Manu","Guido","Anne","Steff","Mimi","Suzan","Jeff"};
                    
    var random = new Random();
    
    for (var i = 0; i < 200; i++)
    {
        int index = random.Next(names.Count);
        Parents.Add(new Parent
        {
            Children = new List<Child>
            {
                new Child
                {
                    Name = names[index],
                    Value = i
                }
            }
        });
    }
}

调整后基准测试显示性能差距缩小,但LINQ实现仍存在可观测的性能开销。

性能差异的原因分析

  • LINQ的额外对象与委托开销
    SelectMany、GroupBy这类LINQ方法会创建迭代器对象(如SelectManyIterator、GroupedEnumerable),这些对象的分配和回收会增加GC压力;同时LINQ中的Lambda表达式本质是委托调用,每次迭代都会多一层调用栈开销,累积后影响性能。而嵌套foreach直接遍历原生集合,没有这些额外开销。

  • GroupBy的双重遍历成本
    GetWithGroupSum方法中,GroupBy需要先遍历所有Child完成分组,之后Sum又要对每个分组内的元素再次遍历求和,相当于对Child元素做了两次完整遍历;而嵌套foreach只遍历一次Child就完成累加,遍历次数更少。

  • JIT优化的差异
    嵌套foreach是直白的循环结构,JIT编译器可以对其进行充分优化,比如循环展开、边界检查消除等;而LINQ的迭代器模式结构复杂,JIT难以做到同等程度的优化,部分优化会被迭代器的封装逻辑阻断。


内容的提问来源于stack exchange,提问作者Daniël Tulp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 04:55:23