0

我需要优化以下需要 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()迭代所有元素。

我该如何进一步优化呢?我敢肯定,我缺少一些明显的东西。

4

5 回答 5

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();
于 2013-08-17T09:17:11.357 回答
0

我找到了解决它的方法:在对问题的有用评论之后,我用秒表分析了 foreach 的几乎每一行,发现确实,CalculateArg()函数是罪魁祸首——称它为每次迭代增加了 500 毫秒; 对于 40 个项目的集合,这意味着总共 20000 毫秒 = 20 秒。

我所做的是将计算移到 foreach 之外,这意味着(使用 SelectMany 制作的匿名对象)现在还包括每个元素的 CalculateArg() 的结果。这将代码带到:

foreach (IGrouping<DateTime, DateTime> item in groups)
{
    var result = initialGroups
                    .Where(grp => grp.calculatedArg == item.Key);
}
于 2013-08-17T16:04:55.887 回答
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];
}
于 2013-08-17T09:15:47.807 回答
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 框架,请尝试将所有计算对象放入内存并全部缓存。这将大大提高性能,因为访问已经在内存中的对象比 Queryable 列表中的对象要容易得多。在您的案例对象可能是一个可查询的对象,这就是为什么您的性能不是那么好。

于 2013-08-17T09:35:24.170 回答
0

如果你有很多items ,groups你可能会从改变算法中受益。

代替迭代,尝试计算一次并将结果组合在一起,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 // 
}
于 2013-08-17T10:25:32.710 回答