是IEnumerable.比带break的for循环快

本文关键字:for 循环 break IEnumerable 比带 | 更新日期: 2023-09-27 17:59:17

我们在打开表单的代码中遇到了一些缓慢的问题,这可能是由于带有breakfor循环需要很长时间才能执行。我把它换成了IEnumerable.Any(),看到表单很快就打开了。我现在正试图弄清楚是单独进行此更改提高了性能,还是更有效地访问了ProductIDs属性。这种实施应该更快吗?如果是,为什么?

最初实施:

public bool ContainsProduct(int productID) {
    bool containsProduct = false;
    for (int i = 0; i < this.ProductIDs.Length; i++) {
        if (productID == this.ProductIDs[i]) {
            containsProduct = true;
            break;
        }
    }
    return containsProduct;
}

新实现:

public bool ContainsProduct(int productID) {
    return this.ProductIDs.Any(t => productID == t);
}

是IEnumerable.比带break的for循环快

称之为有根据的猜测:

this.ProductIDs.Length

这可能就是缓慢的地方。如果在每次迭代中(例如)都从数据库中检索ProductIDs的列表以获得Length,那么它确实会非常慢。您可以通过分析应用程序来确认这一点。

如果不是这种情况(比如ProductIDs在内存中,Length在缓存中),那么两者的运行时间应该几乎相同。

第一个实现是稍微快(枚举比for循环稍微慢)。第二个是可读性更强


更新

Oded的答案可能是正确的,并且在发现它方面做得很好。第一个答案在这里较慢,因为它涉及数据库往返。否则,它会像我说的那样稍微快一点


更新2-证明

下面是一个简单的代码,说明为什么第一个更快:

    public static void Main()
    {
        int[] values = Enumerable.Range(0, 1000000).ToArray();
        int dummy = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 0; i < values.Length; i++)
        {
            dummy *= i;
        }
        stopwatch.Stop();
        Console.WriteLine("Loop took {0}", stopwatch.ElapsedTicks);
        dummy = 0;
        stopwatch.Reset();
        stopwatch.Start();
        foreach (var value in values)
        {
            dummy *= value;         
        }
        stopwatch.Stop();
        Console.WriteLine("Iteration took {0}", stopwatch.ElapsedTicks);
        Console.Read();
    }

输出如下:

环路占用12198

迭代耗时20922

所以循环是迭代/枚举的两倍

我认为它们或多或少是相同的。我通常参考Jon Skeet的"重新实现LINQ到对象"博客系列来了解扩展方法的工作原理。这是Any()All() 的帖子

以下是后中Any()实现的核心部分

public static bool Any<TSource>( 
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate) 
{ 
   ...
    foreach (TSource item in source) 
    { 
        if (predicate(item)) 
        { 
            return true; 
        } 
    } 
    return false; 
} 

本文假设ProductIDsList<T>或数组。所以我说的是林克对物体。

Linq通常比传统的基于循环的代码更慢,但更短/更可读。根据你所做的事情,2-3的因素是典型的。

你能重构你的代码使this.ProductIDs成为HashSet<T>吗?或者至少对数组进行排序,以便使用二进制搜索。你的问题是你在执行线性搜索,如果有很多产品,这是很慢的。

我认为下面的实现会比相应的linq实现快一点,但非常小尽管

public bool ContainsProduct(int productID) {
    var length = this.ProductIDs.Length;
    for (int i = 0; i < length; i++) {
        if (productID == this.ProductIDs[i]) {
            return true;
        }
    }
    return false;
}

区别通常在于内存使用率和速度。

但通常情况下,当您知道将使用数组的所有元素时,您应该使用for循环。在其他情况下,您应该尝试使用while或do while。

我认为这个解决方案使用最少的资源

int i = this.ProductIDs.Length - 1;
while(i >= 0) {
 if(this.ProductIDs[i--] == productId) {
   return true;
 }
}
return false;