Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/asp.net/32.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# LINQ groupby与自定义IEqualityComparer+;组合属性-性能问题_C#_Asp.net_Performance_Linq - Fatal编程技术网

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

C# LINQ groupby与自定义IEqualityComparer+;组合属性-性能问题,c#,asp.net,performance,linq,C#,Asp.net,Performance,Linq,我有一个数据列表,它是从一个实体框架数据库查询与另一个相同类型的IEnumerable以及其他来源的内存数据组合而成的。对于我们的一些客户机,这个列表大约有200000个条目(大约来自数据库的一半),这使得分组操作需要非常长的时间(在我们廉价的虚拟Windows服务器上最多30分钟) 分组操作将列表缩小到大约10000个对象(大约20:1) 列表的数据类基本上只是一大行字符串和int以及一些其他基本类型: public class ExportData { public string Fir

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

分组操作将列表缩小到大约10000个对象(大约20:1)

列表的数据类基本上只是一大行字符串和int以及一些其他基本类型:

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完成的,基本上相当于:

  • 如果允许按自定义逻辑对项目进行分组,这意味着两个对象的属性大约有一半是相等的,从现在起,除了ID、Mass和特殊的StringProperty之外,这是我们唯一关心的属性,即使允许对项目进行分组,这些属性仍然可以不同
  • 每个新的分组对象应具有相关属性(与步骤1中相同),加上分组项作为字符串的组合ID和分组项的所有质量(十进制)属性之和,并且应根据分组项中是否出现特殊字符串来设置special StringProperty
  • 列表导出数据//内存中来自数据库的组合数据列表+内存数据

    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;
    
    公共类导出比较程序:IequalityComparer
    {
    私有比较数据;
    公共导出比较程序()
    {
    }
    公共导出比较程序(比较数据比较数据)
    {
    //比较逻辑所需的附加数据
    //从另一个数据库预取
    数据=比较数据;
    }
    公共布尔等于(ExportData x,ExportData y)
    {
    if(ReferenceEquals(x,y))返回true;
    if(ReferenceEquals(x,null)| ReferenceEquals(y,null))返回false;
    (…)//单元的其余部分已经过测试,并且已经优化了很长的比较逻辑
    返回相等;//来自自定义比较的结果
    }
    public int GetHashCode(ExportData obj)
    {
    if(ReferenceEquals(obj,null))返回0;
    int hash=17;
    hash=hash*23+obj.FirstProperty.GetHashCode();
    (…)//对比较逻辑中使用的每个属性重复
    返回散列;
    
    如何使此groupby运行更快?

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

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

    您可能想尝试从您的
    g
    中创建一个
    列表或数组,但这更多是一种猜测,取决于内部发生的情况以及您的组是如何构建的,这可能是好的,也可能是坏的。您需要进行测试和分析


    然而,我强烈怀疑这里还有其他可疑之处,要么是内存耗尽,要么是需要优化您没有显示的代码。30分钟的内存工作是疯狂的。

    很难建议对comparer进行优化,因为它的代码没有显示,但有一个针对
    Select
    的优化子句

    现在,您正在该选择中使用
    Sum
    Count
    Select
    Any
    (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();
    
    exportData.GroupBy(w=>w,newexportcompare(data))。选择(g=>
    {                
    ExportData组=g.密钥;
    十进制质量=0米;
    var id=新列表();
    bool-anyAb=false;
    bool anyCd=false;
    //只有一个回路
    foreach(g中的var项目){
    质量+=物料质量;
    添加(item.Id);
    anyAb=anyAb | | item.StringProperty.Equals(“AB”);
    anyCd=anyCd | | item.StringProperty.Equals(“CD”);
    }
    组。质量=质量;
    如果(id.Count>1){
    group.combinedds=string.Join(“-”,id);
    }
    if(anyAb)
    group.StringProperty=“AB”;
    else if(anyCd)
    group.StringProperty=“CD”;
    其他的
    group.StringProperty=“EF”;
    返回组;
    }).ToList();
    

    现在我们只循环分组一次,这应该比循环5次更有效。

    您是否有足够的RAM来容纳200K个条目而不使用HDD作为临时空间?如果您的比较逻辑已经优化,那么除了改进硬件,您几乎无能为力。为什么要为每个元素制作一个新的
    ExportComparer
    ?特别是考虑到
    Equals
    方法不依赖于
    CompareData
    为什么不使用存储过程,让SQL server对分组数据进行优化,然后延迟加载结果?这样,您的应用程序服务器就不会受到重击,SQL server将完成它设计的任务,即处理如此繁重的数据问。为了澄清这一点,将内存中的200k项分组需要30分钟?与@VidmantasBlazevicius建议的类似,尝试删除
    Select
    成本,并检查执行
    var group=exportData.GroupBy(w=>w,new ExportCompare(data)).ToList()的时间;
    。如果比较慢,那么问题就出在比较器中。我怀疑是这样,因为这种内存结构的时间通常表示二次时间复杂度算法。我关心计数行中是否有2个或更多项,因此我不能真正使用任何项。CustomComparer本身会在比较器中的内存列表上进行一些查找构造函数中给它的eData对象。除此之外,根据输入属性,在大约20种不同的情况下,它只是大量的布尔逻辑。我可以尝试使用equals函数中的相关列表查找来更新这个问题