Array.Copy与Buffer.BlockCopy的区别

154

Array.CopyBuffer.BlockCopy 都可以完成相同的任务,但是 BlockCopy 旨在快速复制以字节为基础的原始数组,而 Copy 则是通用实现。我的问题是,在什么情况下应该使用 BlockCopy?当复制基元类型数组时,是否应该始终使用它,或者只有在编写性能代码时才使用它?使用 Buffer.BlockCopy 是否存在任何固有风险,可能比 Array.Copy 更加危险?


5
别忘了使用 Marshal.Copy :-) 。对于引用类型、复杂值类型以及类型不变的情况,请使用 Array.Copy;对于值类型、字节数组和字节转换之间的“转换”,请使用 Buffer.BlockCopy。例如,如果你知道该怎么做,与 StructLayout 结合使用就非常强大。就性能而言,调用非托管的 memcpy/cpblk 是最快的,可以参考 http://code4k.blogspot.nl/2010/10/high-performance-memcpy-gotchas-in-c.html 。 - atlaste
1
我对 byte[] 进行了一些基准测试。在发布版本中没有任何区别。有时候 Array.Copy,有时候 Buffer.BlockCopy(稍微)更快。 - Bitterblue
下面刚发布了新的全面答案。请注意,在缓冲区大小较小的情况下,显式循环复制通常是最好的选择。 - Special Sauce
我认为它们并不总是做相同的事情 - 例如,您不能使用Array.Copy将Ints数组复制到Bytes数组。 - mcmillab
Jon Skeet使用Buffer.BlockCopy。这使得它更快。不需要进行基准测试。 :-) - Edward Brey
显示剩余2条评论
8个回答

176

前言

虽然我来晚了,但是这篇文章已经有32k的浏览量,所以值得认真对待。迄今为止,大部分微基准测试代码都存在一个或多个严重的技术缺陷,包括未将内存分配移出测试循环(这会引入严重的GC副作用)、未测试变量与确定性执行流、JIT预热以及未跟踪测试内部的可变性。此外,大多数答案都没有测试不同缓冲区大小和不同原始类型(针对32位或64位系统)的影响。为了更全面地回答这个问题,我使用了我开发的自定义微基准测试框架,尽可能地减少了大部分常见的“坑”。测试在.NET 4.0 Release模式下在32位机器和64位机器上运行。结果是在20次测试运行中平均计算的,每次运行每种方法都有100万次试验。测试的原始类型包括byte(1字节)、int(4字节)和double(8字节)。测试了三种方法:Array.Copy()Buffer.BlockCopy()和在循环中进行简单的索引赋值。数据太多了,无法在这里发布,所以我将总结重要的点。

结论

  • 如果您的缓冲区长度大约为75-100或更少,则显式循环复制例程通常比Array.Copy()Buffer.BlockCopy()更快(对于在32位和64位机器上测试的所有3种原始类型,速度提高了约5%)。此外,与两种替代方法相比,显式循环复制例程的性能变化更小。这种良好的性能几乎肯定是由于CPU L1 / L2 / L3内存缓存利用引用局部性而产生的,同时没有方法调用开销。
  • 对于32位机器上的缓冲区::所有测试的缓冲区大小都高达100k时,显式循环复制例程优于两种替代方法。该改进比其他方法好3-5%。这是因为在传递本机32位宽度后,Array.Copy()Buffer.BlockCopy()的性能完全降低。因此,我认为同样的效果也将适用于缓冲区。
  • 对于超过〜100的缓冲区大小,显式循环复制很快就比另外2种方法慢得多(只有刚才提到的一个例外)。差异在byte[]中最为明显,其中显式循环复制在大缓冲区大小时可能变得慢7倍或更多。
  • 总的来说,在所有测试的3种原始类型和所有缓冲区大小上,Array.Copy()Buffer.BlockCopy()的性能几乎相同。平均而言,Array.Copy()似乎需要更少的时间(但典型情况下只比Buffer.BlockCopy()好0.2%-0.5%),尽管Buffer.BlockCopy()有时会胜过它。由于未知原因,Buffer.BlockCopy()的内部测试变异性明显高于Array.Copy()。尽管我尝试了多种缓解措施并且没有可操作的理论,但这种影响无法消除。
  • 由于Array.Copy()是一种“更智能”,更通用和更安全的方法,除了速度略微更快,平均变化更小之外,它还应该优先于Buffer.BlockCopy()在几乎所有常见情况下使用。唯一的用例是当源和目标数组值类型不同时(如Ken Smith的答案所指出的那样),Buffer.BlockCopy()将明显更好。虽然这种情况不常见,但由于不断的“安全”值类型转换,Array.Copy()在这里的表现可能非常差,而Buffer.BlockCopy()则直接进行强制转换。
  • 来自StackOverflow之外的其他证据表明,Array.Copy()Buffer.BlockCopy()更快地复制相同类型的数组,可以在此处找到。

1
另外,有个发现是在数组长度约为100时,.NET的Array.Clear()开始胜过显式循环赋值清除数组(将其设置为false0null)。这与我上面所说的类似结果一致。这些独立的基准测试是在此处发现的:http://manski.net/2012/12/net-array-clear-vs-arrayx-0-performance/ - Special Sauce
当您提到缓冲区大小时,是指字节还是元素个数? - dmarra
在我的上面的回答中,“缓冲区长度”和“缓冲区大小”通常都是指元素数量。 - Special Sauce
1
@TodCunningham,你提到了“源偏移5个字节”。Array.CopyBuffer.BlockCopy在能够操作机器字和/或页面边界时最强大。在64位机器上,这将是4字节边界。此外,正如本帖子所示,方法调用的开销加上边界检查以及缺乏局部性使得Array.Copy对于小数组(<100个或更少的项)来说是一个相当糟糕的选择。一个(展开的)循环关闭边界检查会快得多,就像你也发现的那样。 - Abel
有趣的回答。不过,由于它是在2015年写的,我添加了一个新的答案,其中包含.NET 5的结果。 - user1121956
显示剩余3条评论

72

由于Buffer.BlockCopy的参数基于字节而不是索引,如果你使用Buffer.BlockCopy,你更有可能搞砸你的代码,相比之下,使用Array.Copy会更加安全,因此我只会在代码的性能关键部分使用Buffer.BlockCopy


9
完全同意。使用Buffer.BlockCopy会有太多出错的空间。保持简单,不要试图在了解程序优化的情况下挤出任何性能(进行分析)。 - Stephen
6
如果你正在处理一个 byte[],使用 BlockCopy 时有什么需要注意的地方吗? - thecoop
4
如果你正在处理一个 byte[],那么使用 BlockCopy 应该是没问题的,除非 "byte" 的定义稍后被更改为其他类型,否则这会对代码的其他部分产生相当负面的影响。 :) 另一个潜在的陷阱是,BlockCopy 只是直接复制字节,因此它不考虑字节序,但这只会在非 Windows 机器上发挥作用,并且只有在你一开始就把代码搞砸了的情况下才会出现问题。此外,如果你正在使用 Mono,可能会有一些奇怪的差异。 - MusiGenesis
6
在我的测试中,Array.Copy() 的性能与 Buffer.BlockCopy() 非常相似。在处理由640个元素组成的字节数组时(这是我最感兴趣的类型),Buffer.BlockCopy 性能始终比 Array.Copy() 快不到10%。但你应该使用自己的数据进行测试,因为结果可能会因数据、数据类型、数组大小等因素而异。值得一提的是,这两种方法的速度大约是使用 Array.Clone() 的3倍,比使用 for 循环复制快大约20倍。 - Ken Smith
4
@KevinMiller:嗯,UInt16每个元素占据两个字节。如果您将此数组与数组中的元素数量一起传递给BlockCopy,当然只会复制一半的数组。为使其正常工作,您需要将元素数量乘以每个元素的大小(2)作为长度参数传递。请参考https://msdn.microsoft.com/zh-cn/library/system.buffer.blockcopy(v=vs.110).aspx并在示例中搜索`INT_SIZE`。 - MusiGenesis
显示剩余6条评论

69
另一个使用 Buffer.BlockCopy() 的合理场景是,当你得到一组基元类型的数组(比如 shorts),并需要将其转换为字节数组(比如用于网络传输)时。我在处理 Silverlight AudioSink 中的音频时经常使用这种方法。它提供了一个 short[] 数组作为样本,但在构建提交给 Socket.SendAsync() 的数据包时,需要将其转换为 byte[] 数组。你可以使用 BitConverter 并逐个迭代数组,但直接使用以下代码速度更快(在我的测试中大约快了 20 倍):
Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).  

同样的技巧反过来也适用:

Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);

这几乎是在安全的C#中实现类似C和C++中常见的(void *)类型内存管理的最接近方式。


7
好的,我会尽力进行翻译。请问需要把 "endianness" 翻译成什么? - Phillip
是的,我认为你可能会遇到这个问题,这取决于你的情况。我的场景通常是要在同一台机器上在字节数组和短数组之间来回切换,或者我知道我正在将数据发送到具有相同字节顺序并且我控制远程端的机器。但是,如果您使用的协议要求远程机器按网络顺序而不是主机顺序发送数据,那么这种方法会给您带来问题。 - Ken Smith
Ken在他的博客上也有一篇关于BlockCopy的文章:http://blog.wouldbetheologian.com/2011/11/my-favorite-c-micro-optimization-1.html - Drew Noakes
6
请注意,自从.Net Core 2.1以后,你可以不用复制即可完成这个操作。MemoryMarshal.AsBytes<T>MemoryMarshal.Cast<TFrom, TTo>可以让你将一个原始数据序列解释为另一个原始数据序列。 - Timo
3
我有一个短整型数组放在我的底抽屉里。 - Engineer

17

根据我的测试,性能不是优先选择Buffer.BlockCopy而非Array.Copy的原因。从我的测试结果来看,实际上Array.Copy比Buffer.BlockCopy更快。

var buffer = File.ReadAllBytes(...);

var length = buffer.Length;
var copy = new byte[length];

var stopwatch = new Stopwatch();

TimeSpan blockCopyTotal = TimeSpan.Zero, arrayCopyTotal = TimeSpan.Zero;

const int times = 20;

for (int i = 0; i < times; ++i)
{
    stopwatch.Start();
    Buffer.BlockCopy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    blockCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();

    stopwatch.Start();
    Array.Copy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    arrayCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();
}

Console.WriteLine("bufferLength: {0}", length);
Console.WriteLine("BlockCopy: {0}", blockCopyTotal);
Console.WriteLine("ArrayCopy: {0}", arrayCopyTotal);
Console.WriteLine("BlockCopy (average): {0}", TimeSpan.FromMilliseconds(blockCopyTotal.TotalMilliseconds / times));
Console.WriteLine("ArrayCopy (average): {0}", TimeSpan.FromMilliseconds(arrayCopyTotal.TotalMilliseconds / times));

示例输出:

bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000

1
抱歉这个回答有点像评论,但是它太长了不能作为评论。由于大家似乎一致认为Buffer.BlockCopy在性能方面更好,我想让大家知道我无法通过测试来证实这种共识。 - Kevin
10
我认为你的测试方法存在问题。大部分时间差异是由应用程序启动、缓存自身、运行JIT等产生的。尝试使用较小的缓冲区进行测试,但重复进行数千次;然后在循环内部再次执行整个测试,并仅关注最后一次运行。我的测试结果显示,对于640字节的数组,Buffer.BlockCopy() 的运行速度可能比Array.Copy() 快5%左右。虽然不是很快,但略有提高。 - Ken Smith
2
对于一个特定的问题,我进行了相同的测量,我发现在 Array.Copy() 和 Buffer.BlockCopy() 之间没有性能差异。如果有什么区别的话,BlockCopy 在某些情况下引入了不安全因素,实际上导致我的应用程序崩溃。 - gatopeich
1
就像添加Array.Copy支持源位置的长整型一样,这样在将其分解为“大”字节数组时,它不会抛出越界异常。 - Alxwest
2
根据我刚刚进行的测试(https://bitbucket.org/breki74/tutis/commits/cffbddfdec985a66c1e71bf11044dd665c2fa690),我认为在处理字节数组时,这两种方法之间没有实际性能差异。 - Igor Brejc

7

ArrayCopy比BlockCopy更加智能。如果源数组和目标数组相同,它会自动处理如何复制元素。

如果我们使用0,1,2,3,4填充一个int数组,并应用以下操作:

Array.Copy(array, 0, array, 1, array.Length - 1);

我们得到了预期的结果:0,0,1,2,3。

但是如果使用BlockCopy,我们得到的是:0,0,2,3,4。如果我在这之后赋值array[0]=-1,那么就会得到预期的结果:-1,0,2,3,4,但是如果数组长度是偶数,比如6,我们就会得到危险的结果:-1,256,2,3,4,5。不要使用BlockCopy来复制除一字节数组以外的其他类型的数组。

还有一种情况只能使用Array.Copy:如果数组大小超过2^31。Array.Copy有一个带有long类型参数的重载方法,而BlockCopy没有。


2
使用BlockCopy进行测试的结果并不出乎意料。这是因为BlockCopy尝试一次性复制数据块而不是一个字节一个字节地复制。在32位系统上,它每次复制4个字节,在64位系统上,它每次复制8个字节。 - Pharap
1
因此,预期未定义的行为。 - binki

4
在.NET 5.0.6 (x64)上,将字节数组复制到字节数组的最佳方法是使用Array.Copy,即使是对于短数组。有趣的是,对于较长的数组,Enumumerable.Concat 也相对较快,因为它会针对实现了ICollection<T>的可枚举对象进行优化(尽管在.NET Framework中不是这种情况)。
基准测试结果和源代码:
Method ArrayLength NumberOfArrays Mean Error StdDev
EnumerableConcat 50 1 63.54 ns 1.863 ns 5.435 ns
ForLoop 50 1 95.01 ns 2.008 ns 4.694 ns
ForeachLoop 50 1 91.80 ns 1.953 ns 4.527 ns
ArrayCopy 50 1 26.66 ns 1.043 ns 3.075 ns
BufferBlockCopy 50 1 27.65 ns 0.716 ns 2.076 ns
EnumerableConcat 50 2 265.30 ns 9.362 ns 26.558 ns
ForLoop 50 2 188.80 ns 5.084 ns 13.659 ns
ForeachLoop 50 2 180.16 ns 4.953 ns 14.448 ns
ArrayCopy 50 2 42.47 ns 0.970 ns 2.623 ns
BufferBlockCopy 50 2 47.28 ns 1.038 ns 2.024 ns
EnumerableConcat 50 3 327.81 ns 9.332 ns 27.368 ns
ForLoop 50 3 285.21 ns 6.028 ns 17.680 ns
ForeachLoop 50 3 260.04 ns 5.308 ns 14.795 ns
ArrayCopy 50 3 62.97 ns 1.505 ns 4.366 ns
BufferBlockCopy 50 3 73.45 ns 3.265 ns 9.626 ns
EnumerableConcat 100 1 69.27 ns 1.762 ns 5.167 ns
ForLoop 100 1 189.44 ns 3.907 ns 11.398 ns
ForeachLoop 100 1 163.03 ns 3.311 ns 5.057 ns
ArrayCopy 100 1 33.23 ns 1.225 ns 3.574 ns
BufferBlockCopy 100 1 35.55 ns 1.004 ns 2.865 ns
EnumerableConcat 100 2 291.20 ns 10.245 ns 30.207 ns
ForLoop 100 2 363.01 ns 7.160 ns 9.310 ns
ForeachLoop 100 2 357.98 ns 7.228 ns 7.734 ns
ArrayCopy 100 2 56.59 ns 1.702 ns 5.019 ns
BufferBlockCopy 100 2 61.82 ns 1.747 ns 5.095 ns
EnumerableConcat 100 3 354.19 ns 9.679 ns 27.925 ns
ForLoop 100 3 544.59 ns 16.346 ns 48.198 ns
ForeachLoop 100 3 522.59 ns 12.927 ns 37.914 ns
ArrayCopy 100 3 80.66 ns 3.154 ns 9.300 ns
BufferBlockCopy 100 3 87.21 ns 2.414 ns 7.081 ns
EnumerableConcat 1000 1 181.98 ns 4.073 ns 11.882 ns
ForLoop 1000 1 1,643.59 ns 32.135 ns 50.030 ns
ForeachLoop 1000 1 1,444.37 ns 28.705 ns 70.951 ns
ArrayCopy 1000 1 143.55 ns 3.874 ns 11.301 ns
BufferBlockCopy 1000 1 146.69 ns 3.349 ns 9.662 ns
EnumerableConcat 1000 2 525.41 ns 10.621 ns 29.254 ns
ForLoop 1000 2 3,264.64 ns 47.449 ns 39.622 ns
ForeachLoop 1000 2 2,818.58 ns 56.489 ns 126.345 ns
ArrayCopy 1000 2 283.73 ns 5.613 ns 15.175 ns
BufferBlockCopy 1000 2 292.29 ns 5.827 ns 15.654 ns
EnumerableConcat 1000 3 712.58 ns 15.274 ns 44.068 ns
ForLoop 1000 3 5,005.50 ns 99.791 ns 214.810 ns
ForeachLoop 1000 3 4,272.26 ns 89.589 ns 261.335 ns
ArrayCopy 1000 3 422.30 ns 8.542 ns 22.502 ns
BufferBlockCopy 1000 3 433.49 ns 8.808 ns 20.587 ns
EnumerableConcat 10000 1 1,221.27 ns 28.138 ns 82.964 ns
ForLoop 10000 1 16,464.04 ns 441.552 ns 1,294.995 ns
ForeachLoop 10000 1 13,916.99 ns 273.792 ns 676.746 ns
ArrayCopy 10000 1 1,150.18 ns 26.901 ns 79.318 ns
BufferBlockCopy 10000 1 1,154.10 ns 23.094 ns 60.025 ns
EnumerableConcat 10000 2 2,798.41 ns 54.615 ns 141.952 ns
ForLoop 10000 2 32,570.61 ns 646.828 ns 1,473.154 ns
ForeachLoop 10000 2 27,707.12 ns 545.888 ns 1,051.741 ns
ArrayCopy 10000 2 2,379.49 ns 72.264 ns 213.073 ns
BufferBlockCopy 10000 2 2,374.17 ns 59.035 ns 173.140 ns
EnumerableConcat 10000 3 3,885.27 ns 77.809 ns 196.633 ns
ForLoop 10000 3 49,833.15 ns 984.022 ns 2,097.031 ns
ForeachLoop 10000 3 41,174.21 ns 819.971 ns 1,392.373 ns
ArrayCopy 10000 3 3,738.32 ns 74.331 ns 91.285 ns
BufferBlockCopy 10000 3 3,839.79 ns 78.865 ns 231.298 ns
public class ArrayConcatBenchmark
{
    [Params(50, 100, 1000, 10000)]
    public int ArrayLength;

    [Params(1, 2, 3)]
    public int NumberOfArrays;

    private byte[][] data;

    [GlobalSetup]
    public void GlobalSetup()
    {
        data = new byte[NumberOfArrays][];
        var random = new Random(42);
        for (int i = 0; i < NumberOfArrays; i++)
        {
            data[i] = new byte[ArrayLength];
            random.NextBytes(data[i]);
        }
    }

    [Benchmark]
    public byte[] EnumerableConcat()
    {
        IEnumerable<byte> enumerable = data[0];

        for (int n = 1; n < NumberOfArrays; n++)
        {
            enumerable = enumerable.Concat(data[n]);
        }

        return enumerable.ToArray();
    }

    [Benchmark]
    public byte[] ForLoop()
    {
        var result = new byte[ArrayLength * NumberOfArrays];

        for (int n = 0; n < NumberOfArrays; n++)
        {
            for (int i = 0; i < ArrayLength; i++)
            {
                result[i + n * ArrayLength] = data[n][i];
            }
        }

        return result;
    }

    [Benchmark]
    public byte[] ForeachLoop()
    {
        var result = new byte[ArrayLength * NumberOfArrays];

        for (int n = 0; n < NumberOfArrays; n++)
        {
            int i = 0;

            foreach (var item in data[n])
            {
                result[i + n * ArrayLength] = item;
                i++;
            }
        }

        return result;
    }

    [Benchmark]
    public byte[] ArrayCopy()
    {
        var result = new byte[ArrayLength * NumberOfArrays];

        for (int n = 0; n < NumberOfArrays; n++)
        {
            Array.Copy(data[n], 0, result, n * ArrayLength, ArrayLength);
        }

        return result;
    }

    [Benchmark]
    public byte[] BufferBlockCopy()
    {
        var result = new byte[ArrayLength * NumberOfArrays];

        for (int n = 0; n < NumberOfArrays; n++)
        {
            Buffer.BlockCopy(data[n], 0, result, n * ArrayLength, ArrayLength);
        }

        return result;
    }

    public static void Main(string[] args)
    {
        //Console.WriteLine("Are all results the same: " + AreAllResultsTheSame());
        BenchmarkRunner.Run<ArrayConcatBenchmark>();
    }

    private static bool AreAllResultsTheSame()
    {
        var ac = new ArrayConcatBenchmark()
        {
            NumberOfArrays = 2,
            ArrayLength = 100,
        };

        ac.GlobalSetup();

        var firstResult = ac.EnumerableConcat();
        var otherResults = new[]
        {
            ac.ForLoop(),
            ac.ForeachLoop(),
            ac.ArrayCopy(),
            ac.BufferBlockCopy(),
        };

        return otherResults.All(x => firstResult.SequenceEqual(x));
    }
}

似乎 ArrayCopy 的性能比 BufferBlockCopy 更好(除了在长度为1000且有2个数组的情况下)。这是在 .NET 5 中测试的,您知道这种性能是否适用于其他 .NET 框架吗?我特别关注 .NET Standard。 - Péter Szilvási
我没有在其他 .NET 版本上测试过 - 测试代码是为您提供在您的环境中测试的 :) - user1121956

2
为了参与这个争论,如果作者不小心编写基准测试,他们很容易被误导。我编写了一个非常简单的测试来说明这一点。在我的下面的测试中,如果我交换开始Buffer.BlockCopy或Array.Copy测试的顺序,第一个的几乎总是最慢的(虽然它很接近)。这意味着由于许多原因,我不会进一步解释,仅仅运行多次测试尤其是连续运行测试将不能给出准确的结果。
我将测试保持为每个包含1000000个连续双倍数的数组进行1000000次尝试。然后我忽略前900000个周期并平均余下的周期。在这种情况下,Buffer更优秀。
private static void BenchmarkArrayCopies()
        {
            long[] bufferRes = new long[1000000];
            long[] arrayCopyRes = new long[1000000];
            long[] manualCopyRes = new long[1000000];

            double[] src = Enumerable.Range(0, 1000000).Select(x => (double)x).ToArray();

            for (int i = 0; i < 1000000; i++)
            {
                bufferRes[i] = ArrayCopyTests.ArrayBufferBlockCopy(src).Ticks;
            }

            for (int i = 0; i < 1000000; i++)
            {
                arrayCopyRes[i] = ArrayCopyTests.ArrayCopy(src).Ticks;
            }

            for (int i = 0; i < 1000000; i++)
            {
                manualCopyRes[i] = ArrayCopyTests.ArrayManualCopy(src).Ticks;
            }

            Console.WriteLine("Loop Copy: {0}", manualCopyRes.Average());
            Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Average());
            Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Average());

            //more accurate results - average last 1000

            Console.WriteLine();
            Console.WriteLine("----More accurate comparisons----");

            Console.WriteLine("Loop Copy: {0}", manualCopyRes.Where((l, i) => i > 900000).ToList().Average());
            Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Where((l, i) => i > 900000).ToList().Average());
            Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Where((l, i) => i > 900000).ToList().Average());
            Console.ReadLine();
        }

public class ArrayCopyTests
    {
        private const int byteSize = sizeof(double);

        public static TimeSpan ArrayBufferBlockCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            Buffer.BlockCopy(original, 0 * byteSize, copy, 0 * byteSize, original.Length * byteSize);
            watch.Stop();
            return watch.Elapsed;
        }

        public static TimeSpan ArrayCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            Array.Copy(original, 0, copy, 0, original.Length);
            watch.Stop();
            return watch.Elapsed;
        }

        public static TimeSpan ArrayManualCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            for (int i = 0; i < original.Length; i++)
            {
                copy[i] = original[i];
            }
            watch.Stop();
            return watch.Elapsed;
        }
    }

https://github.com/chivandikwa/Random-Benchmarks


7
请提供控制台输出结果,因为您的回答中没有时间结果。 - ToolmakerSteve

1

我只想加入我的测试案例,再次证明BlockCopy与Array.Copy相比在'PERFORMANCE'效能方面没有优势。它们在我的机器上在发布模式下似乎具有相同的性能(都需要约66ms才能复制5000万个整数)。在调试模式下,BlockCopy仅略快一些。

    private static T[] CopyArray<T>(T[] a) where T:struct 
    {
        T[] res = new T[a.Length];
        int size = Marshal.SizeOf(typeof(T));
        DateTime time1 = DateTime.Now;
        Buffer.BlockCopy(a,0,res,0, size*a.Length);
        Console.WriteLine("Using Buffer blockcopy: {0}", (DateTime.Now - time1).Milliseconds);
        return res;
    }

    static void Main(string[] args)
    {
        int simulation_number = 50000000;
        int[] testarray1 = new int[simulation_number];

        int begin = 0;
        Random r = new Random();
        while (begin != simulation_number)
        {
            testarray1[begin++] = r.Next(0, 10000);
        }

        var copiedarray = CopyArray(testarray1);

        var testarray2 = new int[testarray1.Length];
        DateTime time2 = DateTime.Now;
        Array.Copy(testarray1, testarray2, testarray1.Length);
        Console.WriteLine("Using Array.Copy(): {0}", (DateTime.Now - time2).Milliseconds);
    }

3
无意冒犯,但你的测试结果并不是很有用 ;) 首先,“比快20ms”在不知道总时间的情况下并没有告诉你什么。你还以非常不同的方式进行了这两个测试。BlockCopy案例有一个额外的方法调用和目标数组的分配,而Array.Copy案例中却没有。由于多线程波动(可能的任务切换、核心切换),每次执行测试时都很容易得到不同的结果。 - Bunny83
@Bunny83 感谢您的评论。我稍微修改了计时器位置,现在应该会得到更公平的比较结果。而且我有点惊讶的是,blockcopy 并没有比 array.copy 更快。 - dragonfly02

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接