【发布时间】:2018-04-17 10:14:19
【问题描述】:
我有一个数据列表,该数据列表来自实体框架数据库查询与另一个相同类型的 IEnumerable 以及来自其他来源的内存数据。对于我们的一些客户,这个列表大约有 200000 个条目(大约一半来自 db),这使得分组操作需要非常长的时间(在我们廉价的虚拟 Windows 服务器上长达 30 分钟)。
分组操作将列表减少到大约 10000 个对象(大约 20:1)。
List的数据类基本上就是一大排Strings和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 完成的,基本上相当于:
- 如果允许按自定义逻辑对项目进行分组,这意味着两个对象的属性大约有一半是相等的,并且这些是我们从现在开始关心的唯一属性,除了 ID、Mass 和特殊的 StringProperty即使允许对项目进行分组,这仍然可能有所不同。
- 每个新的分组对象都应具有相关属性(在步骤 1 中相同),加上来自分组项的组合 ID 作为字符串和分组项的所有质量(十进制)属性的总和,以及应根据任何分组项中是否出现特殊字符串来设置特殊 StringProperty。
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;
我该怎么做才能让这个 groupby 跑得更快?
【问题讨论】:
-
您是否有足够的 RAM 来存储 200K 条目以不使用 HDD 作为临时空间?如果您的比较逻辑已经优化,那么除了改进硬件之外,您无能为力。
-
你为什么要为每个元素创建一个新的
ExportComparer?特别是考虑到Equals方法不依赖于CompareData -
为什么不直接使用存储过程,让SQL Server对数据进行分组优化,然后懒加载结果呢?这样,您的应用程序服务器就不会受到重创,SQL Server 将执行其设计的工作,即处理此类繁重的任务。
-
所以澄清一下,将 200k 项在内存中分组需要 30 分钟?
-
与@VidmantasBlazevicius 建议的类似,尝试通过删除
Select成本来消除它并检查执行var grouped = exportData.GroupBy(w => w, new ExportCompare(data)).ToList();的时间。如果它很慢,那么问题出在你的比较器上。我怀疑是这种情况,因为这种具有内存结构的时间通常表示二次时间复杂度算法。
标签: c# asp.net performance linq