代码之家  ›  专栏  ›  技术社区  ›  Matthew

为什么.NET数组类的复制速度这么慢?

  •  2
  • Matthew  · 技术社区  · 16 年前

    我很失望地发现array.clone、array.copy to和array.copy都可以通过为(;;)循环手动编码的简单三行代码来完成:

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

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

    在每次操作之前使用array.clone将时间延长到100秒是非常糟糕的。

    使用array.copyto和array.copy只需大约45秒。

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

    (不要主观地争论这是否会对现实世界产生影响,因为在现实世界中,为(;;)循环编写3行代码和查找数组类的文档一样简单。)

    我只是想知道为什么表演会有这么大的不同。在很好的旧C中,最简单的库函数memcpy()执行的操作与上面的(;;)循环的手动操作差不多,我想知道.NET中是否还有其他数组复制函数实现得如此简单,而没有任何抽象妨碍了ARray.clone、array.copyto和array.copy。

    6 回复  |  直到 9 年前
        1
  •  4
  •   weiqure    16 年前

    包括分配我得到以下结果:
    回路:104ms
    克隆:77ms
    抄送:64ms

    代码如下:

    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);
    
        2
  •  2
  •   RBarryYoung    16 年前

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

        3
  •  1
  •   Community CDub    8 年前

    你的测试可能有点起皱。快速查看 Reflector 显示数组。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个滴答,数组为312500个滴答。虽然差别不大,但速度还是比 weiqure 注意。

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

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

        4
  •  0
  •   GregC Benjamin Baumann    16 年前

    可能和拳击有关吗?

        5
  •  0
  •   Jordan Johnson    15 年前

    你没有提到被复制的数组有多大。也许jit没有像对泛型类型那样,为每种类型的数组专门化array.copy和array.clone。因此,这些方法首先要做的是检查数组以确定:它是引用类型还是值类型,如果是值类型,则每个条目有多大?

    如果我是对的,复制一个小数组1000万次将导致这些检查不必要地重复1000万次。另一方面,复制一百万元素数组可能比手工编码的循环快,因为array.copy可能对大型数组采用优化。值得注意的是,在你的手工编码循环中

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

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

    但我只是在猜测。我不知道怎么知道数组是什么样的,拷贝就是这样。

        6
  •  0
  •   James Ko    9 年前

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

    换句话说, 数组克隆() 必须这样做:

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

    而copy和copy等方法只是将元素复制到现有数组中。因此,预计array.clone需要花费很多时间,比其他的要长得多,因为内存不可用。