优化慢LINQ查询

时间:2013-08-17 09:09:16

标签: c# performance linq

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

我怎样才能进一步优化?我确信有一些明显的东西让我失踪。

5 个答案:

答案 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);
}