这两种算法之间是否存在改变IEnumerable的性能差异?

时间:2010-12-10 19:12:22

标签: c# linq performance ienumerable

这两个问题对于改组IEnumerable提供了类似的算法:

以下两种方法是并排的:

public static IEnumerable<T> Shuffle1<T> (this IEnumerable<T> source)
{
    Random random = new Random ();
    T [] copy = source.ToArray ();

    for (int i = copy.Length - 1; i >= 0; i--) {
        int index = random.Next (i + 1);
        yield return copy [index];
        copy [index] = copy [i];
    }
}


public static IEnumerable<T> Shuffle2<T> (this IEnumerable<T> source)
{
    Random random = new Random ();
    List<T> copy = source.ToList ();

    while (copy.Count > 0) {
        int index = random.Next (copy.Count);
        yield return copy [index];
        copy.RemoveAt (index);
    }
}

它们基本相同,除了一个使用List,一个使用数组。从概念上讲,第二个似乎对我来说更清楚。但使用阵列是否可以获得显着的性能优势?即使Big-O时间相同,如果它快几倍,也会产生明显的差异。

3 个答案:

答案 0 :(得分:7)

由于RemoveAt,第二个版本可能会慢一些。列表实际上是在向元素添加元素时增长的数组,因此,中间的插入和删除速度很慢(实际上,MSDN声明RemoveAt具有O(n)复杂度)。

无论如何,最好的方法是简单地使用分析器来比较两种方法。

答案 1 :(得分:2)

第一个算法是O(n),因为它有一个循环,在每次迭代时执行O(1)交换。第二个算法是O(n ^ 2),因为它在每次迭代时执行O(n)RemoveAt操作。另外,列表上的索引器比数组上的索引慢,因为前者是方法调用,而后者是IL指令。

所以在第一个中,第一个可能会更快。也就是说,如果你在表演之后,为什么还要费心取得结果呢?它已经转换为一个数组,所以只是在适当的位置进行随机播放并直接返回数组(如果你担心人们改变它,则将其包裹在ReadOnlyCollection<T>中),这可能会更快。

另外,两种方法都有错误,当多个线程使用Random时,{{1}}的行为是未定义的,所以它们应该使用thread-safe random number generator

答案 2 :(得分:2)

第一个不编译,虽然很明显你试图重新枚举可枚举,然后实现Fisher-Yates;这可能是正确的方法,任何以前曾经洗过阵列的人都不应该不清楚。使用RemoveAt的第二个因其他评论者所说的原因而不好。

编辑:你的顶级实现现在看起来是正确的,这是一个很好的方法。