C# LINQ groupby与自定义IEqualityComparer+;组合属性-性能问题
我有一个数据列表,它是从一个实体框架数据库查询与另一个相同类型的IEnumerable以及其他来源的内存数据组合而成的。对于我们的一些客户机,这个列表大约有200000个条目(大约来自数据库的一半),这使得分组操作需要非常长的时间(在我们廉价的虚拟Windows服务器上最多30分钟) 分组操作将列表缩小到大约10000个对象(大约20:1) 列表的数据类基本上只是一大行字符串和int以及一些其他基本类型: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
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完成的,基本上相当于:
列表导出数据代码>//内存中来自数据库的组合数据列表+内存数据
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函数中的相关列表查找来更新这个问题