C#: ToArray性能

17 浏览
0 Comments

C#: ToArray性能

背景:

我承认我没有尝试对此进行基准测试,但我很好奇...

Enumerable.ToArray(以及它的兄弟方法Enumerable.ToList)的CPU/内存特性如何?

由于IEnumerable无法预先告知其元素数量,我(可能天真地)假设ToArray会“猜测”一个初始数组大小,如果第一次猜测的大小太小,则重新调整/重新分配数组大小,然后如果第二次猜测的大小仍然太小,则再次调整大小等... 这将导致性能不如线性增长。

我可以想象更好的方法,其中涉及(混合)列表,但这仍然需要多次分配(虽然不需要重新分配)和相当多的复制,尽管在开销方面可能是线性的。

问题:

背后是否有任何“魔法”避免了这种重复调整大小的需求,使得ToArray的空间和时间复杂度呈线性关系?

更一般地说,是否有关于BCL性能特性的“官方”文档?

0
0 Comments

在C#中,使用.ToArray()方法将集合转换为数组可能会导致性能问题。这个问题的出现的原因是,ToArray()方法在内部使用了一种称为“倍增算法”的技术。这意味着在转换过程中,数组的大小将被多次倍增,这可能会导致额外的内存分配和复制操作,从而影响性能。

需要注意的是,对于大多数类型来说,转换为数组只需要存储引用即可,不需要分配足够的内存来复制整个对象(除非你使用了大量的结构体)。因此,尽量避免在不必要的情况下使用.ToArray()或.ToList()方法。大多数情况下,你可以一直使用IEnumerable直到最后一刻,即在执行foreach循环或将其赋值给数据源时才进行转换操作。

为了解决这个性能问题,我们可以采用以下方法:

- 尽量延迟使用.ToArray()或.ToList()方法,直到确实需要将集合转换为数组或列表。

- 如果可能的话,尽量使用IEnumerable进行集合操作,而不是先将其转换为数组或列表。

- 如果必须使用.ToArray()或.ToList()方法,可以考虑在转换操作之前先估计集合的大小,并分配足够的内存空间。这样可以避免多次倍增操作,提高性能。

下面是一个示例代码,演示了如何使用估计大小的方法来优化.ToArray()方法的性能:

var collection = GetCollection(); // 获取集合
int estimatedSize = collection.Count(); // 估计集合的大小
var array = new T[estimatedSize]; // 分配足够大小的数组
int index = 0;
foreach (var item in collection)
{
    array[index++] = item;
}
// 现在可以使用转换后的数组进行后续操作

通过以上方法,我们可以避免不必要的内存分配和复制操作,提高.ToArray()方法的性能。总之,尽量避免不必要的集合转换操作,可以在性能上获得一定的优化。

0
0 Comments

C#: ToArray性能问题的原因是Buffer类在遍历元素来计算实际数量和元素数组时,没有计算数量,而是在枚举元素时分配和复制元素到越来越大的数组中。解决方法是在枚举两次元素,第一次获取数量,然后进行一次分配和复制。这取决于枚举是否耗费大量时间。

0
0 Comments

问题的出现原因:

问题的出现是因为在执行C#中的ToArray方法时,如果被操作的IEnumerable不实现ICollection接口,就会执行上述的代码来创建并调整数组大小。在这段代码中,当数组填满时,会将数组大小加倍,这会导致性能下降。

解决方法:

为了避免性能下降,可以尽量避免调用ToArray方法。在需要使用查询结果时,直接对查询进行操作通常是更好的选择。这样可以避免数组的创建和调整大小的操作,提高性能。

以下是整理后的文章:

在C#中,当我们需要将IEnumerable类型的数据转换为数组时,通常会使用ToArray方法。然而,这种方法的性能可能不如我们期望的那样高效。

ToArray方法的实现并没有什么神奇之处,如果需要,它会根据需要进行数组大小的调整。需要注意的是,并不总是需要调整数组大小的。如果被操作的IEnumerable类型同时也实现了ICollection接口,那么ToArray方法会使用Count属性来预先分配数组的大小,使得算法在时间和空间上都是线性的。然而,如果不满足这个条件,就会执行下面这段(粗略的)代码:

foreach (TElement current in source)
{
    if (array == null)
    {
        array = new TElement[4];
    }
    else
    {
        if (array.Length == num)
        {
            // Doubling happens *here*
            TElement[] array2 = new TElement[checked(num * 2)];
            Array.Copy(array, 0, array2, 0, num);
            array = array2;
        }
    }
    array[num] = current;
    num++;
}

需要注意的是,当数组填满时会发生大小加倍的操作。

无论如何,一般来说,除非绝对需要,我们应该尽量避免调用ToArray和ToList方法。在需要使用查询结果时,直接对查询进行操作通常是更好的选择。这样可以避免数组的创建和调整大小的操作,提高性能。

0