LINQ groupby与自定义IEqualityComparer +组合属性 - 性能问题

时间:2018-04-17 10:14:19

标签: c# asp.net performance linq

我有一个数据列表,它从实体框架数据库查询与另一个相同类型的IEnumerable和其他来源的内存数据相结合。对于我们的一些客户来说,这个列表大约有200000个条目(大约是数据库的一半),这使得分组操作需要非常长的时间(在我们便宜的虚拟Windows服务器上最长可达30分钟)。

分组操作将列表向下转换为大约10000个对象(大约20:1)。

List的数据类基本上只是一大排字符串和Ints以及其他一些基本类型:

public class ExportData
{
  public string FirstProperty;
  public string StringProperty;
  public string String1;
  ...
  public string String27;
  public int Int1;
  ...
  public int Int15;
  public decimal Mass;
  ...
}

分组是通过自定义IEqualityComparer完成的,基本上等于:

  1. 如果允许按自定义逻辑对项目进行分组,则意味着两个对象的大约一半属性相同,除了ID,Mass和特殊的StringProperty之外,这些属性是我们从此开始关注的唯一属性。即使允许对项目进行分组,这仍然可能会有所不同。
  2. 每个新的分组对象应具有相关属性(在步骤1中相同),加上分组项目中的组合ID作为字符串以及分组项目的所有质量(十进制)属性的总和,以及应根据是否在任何分组项中出现特殊字符串来设置特殊的StringProperty。
  3. List<ExportData> exportData; //来自数据库+内存数据的组合数据的内存列表

    exportData = exportData.GroupBy(w => w, new ExportCompare(data)).Select(g =>
    {
      ExportData group = g.Key;
      group.Mass = g.Sum(s => s.Mass);
    
      if (g.Count() > 1)
      {
        group.CombinedIds = string.Join("-", g.Select(a => a.Id.ToString()));
      }
    
      if (g.Any(s => s.StringProperty.Equals("AB"))) 
      {
        group.StringProperty= "AB";
      }
      else if (g.Any(s => s.StringProperty.Equals("CD"))) 
      {
        group.StringProperty= "CD";
      }
      else
      {
        group.StringProperty= "EF";
      }
    
      return group;
    }).ToList();
    

    自定义比较器的完整性:

    public class ExportComparer : IequalityComparer<ExportData>
    {
      private CompareData data;
    
      public ExportComparer()
      {
      }
      public ExportComparer(CompareData comparedata)
      {
        // Additional data needed for comparison logic
        // prefetched from another database
        data = comparedata;
      }
      public bool Equals(ExportData x, ExportData y)
      {
        if (ReferenceEquals(x, y)) return true;
    
        if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false;
    
        (...) // Rest of the unit-tested and already optimized very long comparison logic
        return equality; // result from the custom comparison
      }
    
      public int GetHashCode(ExportData obj)
      {
        if (ReferenceEquals(obj, null)) return 0;
    
        int hash = 17;
    
        hash = hash * 23 + obj.FirstProperty.GetHashCode();
        (...) // repeated for each property used in the comparison logic
        return hash;
    

    如何让这个群体跑得更快?

2 个答案:

答案 0 :(得分:2)

很难为比较器建议优化,因为它的代码没有显示,但是对Select子句进行了优化。

现在您在选择内部使用SumCountSelectAny(2次)。这意味着每组中的元素被评估5次(从中完全至少3次)。相反,你可以使用一次foreach循环,并自己评估你的条件:

exportData.GroupBy(w => w, new ExportCompare(data)).Select(g =>
{                
    ExportData group = g.Key;
    decimal mass = 0m;
    var ids = new List<int>();
    bool anyAb = false;
    bool anyCd = false;
    // only one loop
    foreach (var item in g) {
        mass += item.Mass;
        ids.Add(item.Id);
        anyAb = anyAb || item.StringProperty.Equals("AB");
        anyCd = anyCd || item.StringProperty.Equals("CD");
    }
    group.Mass = mass;
    if (ids.Count > 1) {
        group.CombinedIds = string.Join("-", ids);
    }
    if (anyAb)
        group.StringProperty = "AB";
    else if (anyCd)
        group.StringProperty = "CD";
    else
        group.StringProperty = "EF";

    return group;
}).ToList();

现在我们只循环分组一次,这样做5次就更有效了。

答案 1 :(得分:0)

g.Count() > 1可以优化为g.Any(),因为您并不真正关心计数,您只关心至少有一个元素。

您的Any / Any对AB或CD的调用可以在一个循环中处理,而不是两个。

你可能想尝试从你的List制作一个g或数组,但更多的是猜测,这取决于内部发生的情况,以及你的小组如何构建,可能会有好处或危害。您需要测试和分析。

但是,我强烈怀疑此处还有其他问题,要么你的RAM耗尽,要么需要优化你没有显示的代码。 30分钟做一些记忆工作是疯了。