我需要优化以下需要20秒才能运行的循环:
foreach (IGrouping<DateTime, DateTime> item in groups)
{
var countMatchId = initialGroups
.Where(grp => CalculateArg(grp.a.Arg) == item.Key && grp.b.Arg == someId)
.Sum(y => y.c.Value);
var countAll = initialGroups
.Where(grp => CalculateArg(grp.a.Arg) == item.Key)
.Sum(y => y.c.Value);
}
...其中CalculateArg是一个相对昂贵的功能。我认为,CalculateArg必须是罪魁祸首因此只能在一个查询中使用,所以我想出了这个:
foreach (IGrouping<DateTime, DateTime> item in groups)
{
var result = initialGroups
.Where(grp => CalculateArg(grp.a.Arg) == item.Key);
var countMatchId = result
.Where(x => x.c.Arg == someId).Sum(y => y.c.Value);
var countAll = result
.Sum(y => y.c.Value);
这个结果的问题是它只能节省大约200毫秒,所以没有优化任何东西。我仍然需要countMatchId .Where()迭代所有元素,而 .Sum()也会迭代它们。然后另一个用于countAll的 .Sum()迭代所有元素。
我怎样才能进一步优化?我确信有一些明显的东西让我失踪。
答案 0 :(得分:4)
var result = initialGroups
.Where(grp => CalculateArg(grp.a.Arg) == item.Key);
这不是缓存的。
foreach (var x in result) {}
foreach (var x in result) {}
foreach (var x in result) {}
foreach (var x in result) {}
将重新计算所有内容4次。
这样做:
var result = initialGroups
.Where(grp => CalculateArg(grp.a.Arg) == item.Key)
.ToArray();
答案 1 :(得分:0)
我想这可能会部分改善它:
foreach (IGrouping<DateTime, DateTime> item in groups)
{
var common = initialGroups
.GroupBy(grp => {
var c = CalculateArg(grp.a.Arg);
return (c == item.Key && grp.b.Arg == someId) ? 1 :
c == item.Key ? 2 : 3;
})
.OrderBy(g=>g.Key)
.Select(g=>g.Sum(c=>c.Value)).ToList();
var countMatchId = common[0];
var countAll = common[0] + common[1];
}
答案 2 :(得分:0)
现在我们需要在这个问题中考虑几件事。首先,您的数据来自哪里?它来自dbcontext创建的实体吗?如果是,则需要考虑使用Context访问和操作数据,而不是使用对象的导航属性。那是什么意思?考虑以下两个类,
public class User{
public int ID { get;set; }
public virtual ICollection<Animal> Animals {get;set;}
}
public class Animal{
public int ID { get; set; }
public string Name {get;set;}
[ForeignKey("Owner")]
public int? Owner_ID {get;set;}
public virtual User Owner {get;set;}
}
现在,不要使用下面的代码访问用户的动物,
User user = Context.User.Single(t=> t.ID == 1);
List<Animal> animals = user.Animals.ToList();
直接使用dbcontext进行访问效率要高得多。 (如果您的列表具有100k实体并尝试使用ToList方法将其放入内存,则应考虑性能因素。
List<Animal> animals = Context.Animals.Where(t => t.Owner_ID == 1).ToList();
此外,如果您没有使用任何ORM框架,请尝试将所有计算对象放入内存并将其全部缓存。这将大大提高性能,因为访问已经在内存中的对象比可查询列表中的对象容易得多。在您的情况下,组对象可能是一个可查询对象,这就是为什么您的表现不是那么好。
答案 3 :(得分:0)
如果item
中有很多groups
s,您可能会因更改算法而受益。
而不是迭代,尝试计算一次&amp; GroupJoin将结果合在一起,ala
var calculated = initialGroups
.Select(group => new { Group = group, Arg = CalculateArg(group.a.Arg) })
.ToList();
var sumCollection = groups
.GroupJoin(calculated,
item => item.Key,
group => group.Arg,
(group, calculatedCollection) =>
new {
Group = group,
SumAll = calculatedCollection.Sum(y => y.Group.c.Value),
SumMatchId = calculatedCollection
.Where(y => y.Group.b.Arg == someId)
.Sum(y => y.Group.c.Value)
});
foreach (var item in sumCollection)
{
item.SumAll // you get the idea
item.SumMatchId //
}
答案 4 :(得分:0)
我找到了解决问题的方法:根据对问题的有用评论,我用秒表对foreach的几乎所有行进行了分析,发现确实, CalculateArg()函数是罪魁祸首 - 称每次迭代增加500毫秒;在40个项目集合中,这意味着总共20000毫秒= 20秒。
我所做的是将计算移到foreach之外,意味着 groups (使用SelectMany创建的匿名对象),现在还包括每个元素的CalculateArg()结果。它将代码带到:
foreach (IGrouping<DateTime, DateTime> item in groups)
{
var result = initialGroups
.Where(grp => grp.calculatedArg == item.Key);
}