我有一个数据列表,它从实体框架数据库查询与另一个相同类型的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完成的,基本上等于:
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;
如何让这个群体跑得更快?
答案 0 :(得分:2)
很难为比较器建议优化,因为它的代码没有显示,但是对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();
现在我们只循环分组一次,这样做5次就更有效了。
答案 1 :(得分:0)
g.Count() > 1
可以优化为g.Any()
,因为您并不真正关心计数,您只关心至少有一个元素。
您的Any
/ Any
对AB或CD的调用可以在一个循环中处理,而不是两个。
你可能想尝试从你的List
制作一个g
或数组,但更多的是猜测,这取决于内部发生的情况,以及你的小组如何构建,可能会有好处或危害。您需要测试和分析。
但是,我强烈怀疑此处还有其他问题,要么你的RAM耗尽,要么需要优化你没有显示的代码。 30分钟做一些记忆工作是疯了。