我使用(ElementAt,Count)和(foreach)作为Linq查询的结果迭代IEnumerable。令我惊讶的是,性能差异是25-30倍!那是为什么?
IEnumerable<double> result =
... simple Linq query that joins two tables
... returns about 600 items
double total = 0;
// Method 1: iterate with Count and ElementAt
for( int i = 0; i < result.Count(); i++ )
{
total += result.ElementAt(i);
}
// Method 2: iterate with foreach
foreach( double value in result )
{
total += value;
}
答案 0 :(得分:9)
ElementAt()
方法是O(n),除非IEnumerable
表示的实际具体类优化它。这意味着每次调用它时,都必须循环遍历整个Enumerable才能找到n
处的元素。更不用说因为i < result.Count()
循环的条件部分中有for
,所以每次都要循环遍历整个可枚举数。
第二种方式,你只需要遍历result
一次。
答案 1 :(得分:6)
因为ElementAt
每次调用IEnumerable
时都在迭代IEnumerable
。 ElementAt
未编入索引,因此GetEnumerator()
必须使用total = result.Sum();
实施。
为什么不
{{1}}
答案 2 :(得分:1)
因为每次调用都会遍历列表。简单的解决方案是在迭代之前调用.ToList(),更好的解决方案是停止迭代。
var theList = result.ToList();
for( int i = 0; i < result.Count; i++ )
{
total += result[i];
}
更好的解决方案:
total = result.Sum();
答案 3 :(得分:1)
性能差异是由于IEnumerable
不允许您通过索引获取的事实,所以每次在第一个循环中调用ElementAt
时,它必须遍历每个项目,直到它到达你要求的元素。
答案 4 :(得分:0)
如果我冒险猜测,result.Count()调用是非延迟的,实际上是命中数据库,而foreach则没有。如果你翻转订单,可能会得到相反的结果。你也可以total = result.Sum();
答案 5 :(得分:0)
第一个可能相当于:
double total = 0;
int i = 0;
while(true)
{
int max = /*Database call to obtain COUNT(*) of join*/
if(max > i)
break;
int j = 0;
foreach(double value in result)
{
if(j++ == i)
{
total += value;
break;
}
}
++i
}
或者甚至可以相当于:
double total = 0;
int i = 0;
while(true)
{
int max = 0;
foreach(double value in result)
++max;
if(max > i)
break;
int j = 0;
foreach(double value in result)
{
if(j++ == i)
{
total += value;
break;
}
}
++i
}
或者每次在上面的代码中出现result
时,它甚至可以重新查询。
另一方面,Count()
可以通过一次属性访问获得,ElementAt()
可以是O(1),如果这些是在允许的结构上支持的话这样的优化,确实可以实现这样的优化(例如,它适用于List<T>
)。
答案 6 :(得分:0)
这是关于理解延期执行的全部内容!当多次执行查询时,运行时间会急剧增加。 LINQ可以更快,但你真的需要根据你将如何使用你的查询结果做出选择。
看一下这篇文章http://allthingscs.blogspot.com/2011/03/linq-performance.html。它分析了这个问题。