【问题标题】:Why does .NET Array class copy so slow?为什么.NET Array 类复制这么慢?
【发布时间】:2010-11-01 16:41:36
【问题描述】:

我很失望地发现 Array.Clone、Array.CopyTo 和 Array.Copy 都可以通过一个简单的 3 行手动编码的 for(;;) 循环来击败:

    for(int i = 0; i < array.Length; i++)
    {
        retval[i] = array[i];
    }

假设对某个预定大小的数组执行几百万次操作的基本情况需要 10 秒。

在每次操作之前使用 Array.Clone 将该时间延长到 100 秒非常糟糕。

使用 Array.CopyTo 和 Array.Copy 只需要大约 45 秒。

上面的循环只需要大约 20 秒。

(忘记这是否对现实世界产生影响的主观论点,因为在现实世界中,编写一个 3 行 for(;;) 循环可以说与查找 Array 的文档一样简单类。)

我只是想知道为什么性能如此不同。在好的旧 C 语言中,最简单的库函数 memcpy() 的执行与上面的手动 for(;;) 循环大致相同,我想知道 .NET 中是否还有其他一些数组复制函数是这样实现的一个很好的简单 for(;;) 没有任何抽象阻碍 Array.Clone、Array.CopyTo 和 Array.Copy。

【问题讨论】:

标签: .net arrays copy performance


【解决方案1】:

包括分配我得到以下结果:
For 循环:104 毫秒
克隆:77ms
复制到:64 毫秒

代码如下:

int[] values = new int[16000000];

int[] copiedValues1;
Stopwatch sw = Stopwatch.StartNew();
copiedValues1 = new int[values.Length];
for (int i = 0; i < values.Length; i++)
{
    copiedValues1[i] = values[i];
}
Console.WriteLine(sw.ElapsedMilliseconds);

int[] copiedValues2;
sw = Stopwatch.StartNew();
copiedValues2 = (int[])values.Clone();
Console.WriteLine(sw.ElapsedMilliseconds);

int[] copiedValues3;
sw = Stopwatch.StartNew();
copiedValues3 = new int[values.Length];
values.CopyTo(copiedValues3,0);
Console.WriteLine(sw.ElapsedMilliseconds);

【讨论】:

  • 实际上,这些时间似乎只有在您构建调试配置时才成立。在 Release 构建中,for 循环所用的时间与 CopyTo 大致相同,而 Clone 则稍微慢一些。
【解决方案2】:

数组方法不是也必须创建和分配输出数组对象吗?

【讨论】:

    【解决方案3】:

    您的测试可能有问题。快速查看Reflector 会发现 Array.Copy 使用外部实现(Array.CopyTo 最终使用相同的调用):

    [MethodImpl(MethodImplOptions.InternalCall),
    ReliabilityContract(Consistency.MayCorruptInstance, Cer.MayFail)]
    internal static extern void Copy(
        Array sourceArray, 
        int sourceIndex, 
        Array destinationArray, 
        int destinationIndex, 
        int length, 
        bool reliable);
    

    这开启了内存复制与逐项复制的可能性。我自己在发布模式下的测试,使用 int[1000000] - 随机填充,将循环计时在 468750 滴答声和 Array.Copy 在 312500 滴答刻。差别不大,但正如weiqure 所说,仍然更快。

    您可能需要调整测试以确保没有其他因素影响结果。

    This post 对 Object 数组进行了类似的观察。

    【讨论】:

    • 请注意,该帖子的作者完全不知道O(n) 的含义。
    【解决方案4】:

    可能和拳击有关?

    【讨论】:

      【解决方案5】:

      您没有提到要复制的数组有多大。也许 JIT 并没有像对泛型类型那样为每种类型的数组专门化 Array.Copy 和 Array.Clone。因此,这些方法要做的第一件事就是检查数组以确定:它是引用类型还是值类型,如果是值类型,每个条目有多大?

      如果我是对的,复制一个小数组一千万次会导致这些检查不必要地重复一千万次。另一方面,复制一百万个元素的数组可能比手动编码循环更快,因为 Array.Copy 可能会针对大型数组进行优化。值得注意的是,在您的手动编码循环中

      for(int i = 0; i < array.Length; i++)
      {
          retval[i] = array[i];
      }
      

      JIT 将优化对 array[i] 的范围检查,因为它识别出 i 始终在范围内;但它可能不会删除 retval[i] 的范围检查,即使它也可以很容易地证明在范围内。由于它可能是用本机代码编写的,因此 Array.Copy 可以避免这些检查。除此之外,Array.Copy 可能会使用循环展开,对于大字节数组,可能会一次复制一个机器字。

      但我只是推测。我不知道如何找出 Array.Copy 的实际作用。

      【讨论】:

        【解决方案6】:

        我想指出 Array.Clone() 与其他的有点不同,因为它负责 1) 分配新数组和 2) 复制元素,而 CopyTo 只是将元素复制到现成的数组.

        换句话说,Array.Clone() 必须这样做:

        object Clone()
        {
            var result = new T[this.Length];
            this.CopyTo(result, 0);
            return result;
        }
        

        而像 Copy 和 CopyTo 这样的方法只是将元素复制到现有数组中。因此,预计 Array.Clone 应该比其他方法花费更长的时间,因为内存不是空闲的。

        【讨论】:

          猜你喜欢
          • 2014-03-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-15
          • 2018-01-20
          • 1970-01-01
          • 2013-05-03
          • 2017-08-28
          • 2023-03-13
          相关资源
          最近更新 更多