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

如何优化Foreach+Linq.Any实现?超千万级数据性能优化求助

高性能实现:筛选对象首次出现实例的优化方案

背景

当前代码用于筛选满足「PlayerUrl、Position、ChemistryAmount」组合条件的首次出现实例,现有实现存在严重性能瓶颈:处理30万对象耗时10分钟,且每次应用运行需执行该逻辑900次,后续还要处理超1000万级别的数据量,急需优化。

原有数据结构

// 相关属性定义
string PlayerUrl { get; }

Position Position { get; }

public enum Position
{
    GK,
    // 其他枚举值
}

int ChemistryAmount { get; }

原有低效实现

public static List<IFieldPlayerStatsForPosition> GetFirstFieldPlayerStatsForPositionInList(List<IFieldPlayerStatsForPosition> auxFieldPlayerStatsForPositions)
{
    List<IFieldPlayerStatsForPosition> fieldPlayerStatsForPositions = new List<IFieldPlayerStatsForPosition>();
    foreach (IFieldPlayerStatsForPosition fieldPlayerStatsForPosition in auxFieldPlayerStatsForPositions)
    {
        if (!fieldPlayerStatsForPositions.Any(cc => fieldPlayerStatsForPosition.FieldPlayerChemistryApplied.FieldPlayer.PlayerUrl == cc.FieldPlayerChemistryApplied.FieldPlayer.PlayerUrl &&
                                                    fieldPlayerStatsForPosition.Position == cc.Position &&
                                                    fieldPlayerStatsForPosition.FieldPlayerChemistryApplied.ChemistryAmount == cc.FieldPlayerChemistryApplied.ChemistryAmount))
        {
            fieldPlayerStatsForPositions.Add(fieldPlayerStatsForPosition);
        }
    }
    return fieldPlayerStatsForPositions;
}

性能瓶颈分析

原有实现的核心问题是时间复杂度为O(n²):每次遍历元素时,都要调用Any()线性扫描已保存的结果列表,当数据量达到百万级时,这种嵌套遍历的开销会呈指数级增长,完全无法支撑千万级数据的处理。

优化方案:哈希表替代线性查找

利用HashSet的O(1)平均查找/插入性能,将整体时间复杂度降到O(n),这是最有效的优化方向。以下提供两种可行实现:

方案1:使用ValueTuple作为哈希键(C# 7.0+)

ValueTuple默认实现了正确的Equals和GetHashCode,无需自定义类型,代码简洁高效:

public static List<IFieldPlayerStatsForPosition> GetFirstFieldPlayerStatsForPositionInList(List<IFieldPlayerStatsForPosition> auxFieldPlayerStatsForPositions)
{
    // 提前设置列表容量,避免多次扩容的内存拷贝开销
    var result = new List<IFieldPlayerStatsForPosition>(auxFieldPlayerStatsForPositions.Count);
    var seenKeys = new HashSet<(string PlayerUrl, Position Position, int ChemistryAmount)>();

    foreach (var item in auxFieldPlayerStatsForPositions)
    {
        // 缓存深层属性,减少重复访问开销
        var chemistryApplied = item.FieldPlayerChemistryApplied;
        var key = (
            chemistryApplied.FieldPlayer.PlayerUrl,
            item.Position,
            chemistryApplied.ChemistryAmount
        );

        // HashSet.Add()返回bool:true表示键不存在,成功添加
        if (seenKeys.Add(key))
        {
            result.Add(item);
        }
    }

    return result;
}

方案2:自定义不可变哈希键类型(兼容旧版C#)

如果项目使用的C#版本不支持ValueTuple,可以自定义一个不可变结构体作为哈希键,严格实现相等性和哈希计算:

// 自定义哈希键结构体,保证不可变性和正确的相等判断
public readonly struct PlayerUniqueKey : IEquatable<PlayerUniqueKey>
{
    public readonly string PlayerUrl;
    public readonly Position Position;
    public readonly int ChemistryAmount;

    public PlayerUniqueKey(string playerUrl, Position position, int chemistryAmount)
    {
        PlayerUrl = playerUrl;
        Position = position;
        ChemistryAmount = chemistryAmount;
    }

    public bool Equals(PlayerUniqueKey other)
    {
        return PlayerUrl == other.PlayerUrl 
               && Position == other.Position 
               && ChemistryAmount == other.ChemistryAmount;
    }

    public override bool Equals(object obj)
    {
        return obj is PlayerUniqueKey other && Equals(other);
    }

    public override int GetHashCode()
    {
        // C# 7.2+ 用内置组合哈希,旧版本可以手动实现
        return HashCode.Combine(PlayerUrl, Position, ChemistryAmount);
    }
}

// 优化后的筛选方法
public static List<IFieldPlayerStatsForPosition> GetFirstFieldPlayerStatsForPositionInList(List<IFieldPlayerStatsForPosition> auxFieldPlayerStatsForPositions)
{
    var result = new List<IFieldPlayerStatsForPosition>(auxFieldPlayerStatsForPositions.Count);
    var seenKeys = new HashSet<PlayerUniqueKey>();

    foreach (var item in auxFieldPlayerStatsForPositions)
    {
        var chemistryApplied = item.FieldPlayerChemistryApplied;
        var key = new PlayerUniqueKey(
            chemistryApplied.FieldPlayer.PlayerUrl,
            item.Position,
            chemistryApplied.ChemistryAmount
        );

        if (seenKeys.Add(key))
        {
            result.Add(item);
        }
    }

    return result;
}

额外优化建议

  • 提前设置List容量:结果列表的最大长度不会超过原列表,直接用原列表的Count作为初始容量,避免多次扩容时的内存分配和元素拷贝。
  • 缓存深层属性:在循环内将item.FieldPlayerChemistryApplied缓存到局部变量,减少重复访问嵌套属性的开销(尤其是属性为计算属性时)。
  • 避免LINQ嵌套调用:LINQ的Any()在底层还是线性遍历,对于大数据量场景,手动使用哈希表的性能优势远大于LINQ语法糖。

内容的提问来源于stack exchange,提问作者stephenspcdmav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:10:29