代码之家  ›  专栏  ›  技术社区  ›  brainimus user417509

为什么在LINQ中使用对象属性比使用原语慢?

  •  1
  • brainimus user417509  · 技术社区  · 14 年前

    为什么此LINQ查询(Id是结构对象中long类型的属性):

    IList<Structure> theStructures = new List<Structure>();
    public int GetChildrenSlow(Structure aStructure){
       IEnumerable<Structure> childrenQuery =
                             from structure in theStructures
                             where structure.ParentStructureId == aStructure.Id
                             select structure;
       int count = childrenQuery.Count();
       //Functionality continues...
    }
    

    跑得比这个慢:

    IList<Structure> theStructures = new List<Structure>();
    public int GetChildrenFast(long aStructureId){
       IEnumerable<Structure> childrenQuery =
                             from structure in theStructures
                             where structure.ParentStructureId == aStructureId
                             select structure;
       int count = childrenQuery.Count();
       //Functionality continues...
    }
    

    我多次(递归地)调用这个函数,使用这个属性比直接使用long要慢得多。如果我把Id取出并存储在一个长变量中 之前 我执行LINQ命令,速度相当于 GetChildrenFast . 为什么在LINQ中使用对象属性比使用原语慢?

    工作示例:

    namespace ConsoleApplication1
    {
       class Structure
       {
          public int Id
          {
             get; set;
          }
    
          public int ParentStructureId
          {
             get; set;
          }
       }
    
       class Program
       {
          private IList<Structure> theStructures = new List<Structure>();
          public Structure FirstStructure
          {
             get; set;
          }
    
          private int FastCountStructureChildren(long aStructureId)
          {
             IEnumerable<Structure> childrenQuery =
                             from structure in theStructures
                             where structure.ParentStructureId == aStructureId
                             select structure;
    
             int count = childrenQuery.Count();
             foreach(Structure childStructure in childrenQuery)
             {
                count += FastCountStructureChildren(childStructure.Id);
             }
             return count;
          }
    
          private int SlowCountStructureChildren(Structure aStructure)
          {
             IEnumerable<Structure> childrenQuery =
                             from structure in theStructures
                             where structure.ParentStructureId == aStructure.Id
                             select structure;
    
             int count = childrenQuery.Count();
             foreach(Structure childStructure in childrenQuery)
             {
                count += SlowCountStructureChildren(childStructure);
             }
             return count;
          }
    
          public void BuildStructure()
          {
             FirstStructure = new Structure{Id = 0, ParentStructureId = -1};
             theStructures.Add(FirstStructure);
             //The loop only goes to 6000 as any more than that causes
             //a StackOverflowException my development machine.
             for(int i=1; i<6000; i++)
             {
                Structure newStructure = new Structure{Id = i,ParentStructureId = i - 1};
                theStructures.Add(newStructure);
             }
          }
    
          static void Main(string[] args)
          {
             Program program = new Program();
             program.BuildStructure();
    
             Stopwatch fastStopwatch = new Stopwatch();
             fastStopwatch.Start();
             program.FastCountStructureChildren(0);
             fastStopwatch.Stop();
    
             Stopwatch slowStopwatch = new Stopwatch();
             slowStopwatch.Start();
             program.SlowCountStructureChildren(program.FirstStructure);
             slowStopwatch.Stop();
    
             Console.WriteLine("Fast time: " + fastStopwatch.Elapsed);
             Console.WriteLine("Slow time: " + slowStopwatch.Elapsed);
             Console.ReadLine();
          }
       }
    }
    
    5 回复  |  直到 14 年前
        1
  •  2
  •   configurator    14 年前

    运行您提供的完整示例

    Fast time: 00:00:01.6187793
    Slow time: 00:00:01.3977344
    

    只有在调试模式下运行时,慢时间才会变慢。这是因为在调试模式下,方法从不内联,并且到处都有nop允许您中断,例如在Id getter中。


    因为您显然关心运行速度,所以我将指出一个无关的低效之处:您运行查询两次:一次用于计数,另一次用于迭代子项。只运行一次(循环中的count增加1)应该会加快速度。


    顺便说一句,我通常解决这个问题的方法是,如果打电话给 GetChildren 方法,提供两个重载。否则,请提供( Structure )重载并在查询之前获取id,如 long id = aStructure.id; .

        2
  •  1
  •   Jon Skeet    14 年前

    好吧,即使属性访问是内联的,我怀疑它仍然需要在每次迭代中做一个空值检查。这是一个额外的条件,例如可能会破坏分支预测。

    玩一个完整的例子会很有趣,但我 犯罪嫌疑人 事实上,您对每个委托调用都执行了一个额外的操作。还有一种可能是,这个“额外的一点点”已经关闭了与委托相关的其他一些内联,从而导致了某种domino性能影响。

        3
  •  0
  •   Jimmy Hoffa    14 年前

        4
  •  0
  •   Amy B    14 年前

    在“功能继续”中,是否再次使用childrenQuery?你知不知道每次都会重新列举这些结构?不要多次枚举大型数据集,每个项目上属性的访问成本不会造成太大影响。

    IList<Structure> theStructures = new List<Structure>(); 
    ILookup<int, Structure> byParentId = null;
    
    public int GetChildren(Structure aStructure){
       if (byParentId = null)
       {
         byParentId = theStructures.ToLookup(x => x.ParentStructureId);
       }
       List<Structure> children = byParentId[aStructure.Id].ToList();
       int count = children.Count; 
       //Functionality continues... 
    } 
    
        5
  •  0
  •   Jacob    14 年前

    public IEnumerable<Structure> FetchChildren()
    {
        for (int i = 0; i < 10; i++)
        {
            aStructure.Id++;
            yield return GetChild(a.Structure.Id);
        }
    }
    
    public int GetChildrenSlow(Structure aStructure){
       IEnumerable<Structure> childrenQuery =
                             from structure in FetchChildren()
                             where structure.ParentStructureId == aStructure.Id
                             select structure;
       int count = childrenQuery.Count();
       //Functionality continues...
    }
    

    如你所见, aStructure.Id

    IList<Structure> theStructures = new List<Structure>();
    public int GetChildrenSlow(Structure aStructure){
       IEnumerable<Structure> childrenQuery =
            theStructures.Where(s => s.ParentStructureId == aStructure.Id++);
       int count = childrenQuery.Count();
       //Functionality continues...
    }
    

    而且总是有多线程也会把事情搞砸。由于变异的可能性,检查属性值所需的代价是必要的。