C# Linq性能:(ElementAt,Count)vs(foreach)
我使用(ElementAt,Count)和(foreach)遍历IEnumerable作为Linq查询的结果。令我惊讶的是,性能差异是原来的25-30倍!为什么呢C# Linq性能:(ElementAt,Count)vs(foreach),c#,performance,linq,C#,Performance,Linq,我使用(ElementAt,Count)和(foreach)遍历IEnumerable作为Linq查询的结果。令我惊讶的是,性能差异是原来的25-30倍!为什么呢 IEnumerable<double> result = ... simple Linq query that joins two tables ... returns about 600 items double total = 0; // Method 1: iterate with Count
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;
}
IEnumerable结果=
... 连接两个表的简单Linq查询
... 退货约600件
双倍合计=0;
//方法1:使用Count和ElementAt进行迭代
对于(int i=0;i
因为ElementAt
每次调用时都在迭代IEnumerable
IEnumerable
s没有索引,因此必须使用GetEnumerator()实现ElementAt
为什么不呢
total = result.Sum();
因为每次调用都会在列表上迭代。简单的解决方案是在迭代之前调用.ToList(),更好的解决方案是停止迭代
var theList=result.ToList();
for(int i=0;i
更好的解决方案:
total = result.Sum();
性能差异是由于IEnumerable
不允许按索引获取,因此每次在第一个循环中调用ElementAt
时,它必须遍历每个项,直到它到达您请求的元素。方法是O(n),除非IEnumerable
所代表的实际具体类对其进行了优化。这意味着每次调用它时,它都必须遍历整个可枚举项,以找到n
处的元素。更不用说,因为在for
循环的条件部分中有i
,所以每次都必须循环整个枚举才能得到该计数
第二种方法是,只循环一次result
。如果我冒险猜测,result.Count()调用是非延迟的,实际上会命中数据库,而foreach不会。如果你颠倒顺序,你可能会得到相反的结果。也可以只做total=result.Sum()代码>第一个可能相当于:
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
}
或者甚至可以等同于:
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
}
或者,它甚至可以在每次出现在上述代码中时重新查询以获得结果
另一方面,Count()
可以通过一次属性访问获得,ElementAt()
可以是O(1),如果这些都是基于允许这种优化的结构,并且这种优化确实是可用的(例如,它是针对List
)。这是关于理解延迟执行的!当一个查询执行多次时,运行时间会急剧增加。LINQ可以更快,但您确实需要根据如何使用查询结果做出选择
看看这篇文章。它分析了这个问题。差异在哪一个方向(什么更快)?Count()
将迭代整个可枚举项。每个ElementAt(n)
将迭代到第n个元素foreach
将对整个可枚举项迭代一次。我不知道所有的优化,如果枚举也是一个IList
或ICollection
。你应该清楚地说明哪种方法更快。。。原样Count()
。顺便说一句,不需要lambda表达式。只要result.Sum()
就可以了。@Jon Skeet,怀疑是这样,但必须先去检查一下。现在似乎很明显,alaSelect()
total+=p代码>用于演示目的。实际的逻辑是不同的。因为他们说它来自连接,所以我猜result.Count()实际上变成了SQL Count(*)
。但几乎没有什么进步!