Array.Copy与Buffer.BlockCopy

Array.Copy和Buffer.BlockCopy都做同样的事情,但BlockCopy是针对快速字节级原始数组复制,而Copy是通用的实现。 我的问题是 – 在什么情况下你应该使用BlockCopy ? 你是否应该在复制基本types数组的时候使用它,或者只在编码时使用它? 在Array.Copy使用Buffer.BlockCopy有什么内在的危险吗?

由于Buffer.BlockCopy的参数是基于字节的而不是基于索引的,所以与使用Array.Copy相比,您更有可能搞砸代码,所以我只会在Buffer.BlockCopy的性能关键部分我的代码。

序幕

我很晚才join这个派对,但是32K的观点值得一提。 到目前为止,公布的答案中的大多数微基准代码都受到一个或多个严重的技术缺陷的困扰,包括不将testing循环中的内存分配移出testing循环(引入严重的GC伪像),不testingvariables与确定性执行stream程,JIT热身,而不是追踪testing内变异。 另外,大多数答案没有testing不同缓冲区大小和不同基本types(相对于32位或64位系统)的影响。 为了更全面地解决这个问题,我把它与我开发的一个定制的微基准框架联系起来,尽可能地减less了大部分常见的“陷阱”。 testing在32位机器和64位机器上以.NET 4.0 Release模式运行。 结果平均超过20个testing运行,其中每个运行每个方法有100万次试验。 testing的原始types是byte (1字节), int (4字节)和double (8字节)。 testing了三种方法: Array.Copy()Buffer.BlockCopy()和简单的按索引分配。 这里的数据太庞大了,所以我总结一下重要的一点。

外卖

  • 如果缓冲区长度大约在75-100或更less,则对于在32位上testing的所有3种基元types,显式循环复制例程通常比Array.Copy()Buffer.BlockCopy()更快(大约5%)和64位机器。 另外,与两种替代scheme相比,显式循环拷贝例程的性能可变性明显较低。 良好的性能几乎可以肯定是由于CPU L1 / L2 / L3内存caching利用了参考的局部性,而没有方法调用开销。
    • 对于32位机器上的 double缓冲区显式循环复制例程比所有缓冲区大小都高于100k的所有缓冲区都要好。 比其他方法提高3-5%。 这是因为Array.Copy()Buffer.BlockCopy()的性能在传递本机32位宽度后会完全降级。 因此,我认为同样的效果也适用于long缓冲区。
  • 对于超过〜100的缓冲区大小,显式循环拷贝很快变得比其他两种方法(具有刚刚提到的一个特定的例外)慢得多。 与byte[]最为明显的区别是,在大缓冲区大小下,显式循环复制可能会变慢7倍或更多。
  • 一般来说,对于所testing的所有3种基本types以及所有缓冲区大小, Array.Copy()Buffer.BlockCopy()几乎完全相同。 平均而言, Array.Copy()似乎有一个非常轻微的边缘,大约需要花费2%或更less的时间(但典型的是0.2% – 0.5%),尽pipeBuffer.BlockCopy()确实偶尔击败了它。 由于未知的原因, Buffer.BlockCopy()具有比Array.Copy()明显更高的内部testing可变性。 尽pipe我尝试了多种缓解措施,但为什么还没有一个可操作的理论,这种效应是无法消除的。
  • 由于Array.Copy()是一个“更智能”,更通用,更安全的方法,除了速度稍快,平均变化less之外,在几乎所有的常见情况下, Buffer.BlockCopy()应该优先于Buffer.BlockCopy()Buffer.BlockCopy()会更好的唯一用例是源和目标数组的值types不同(正如Ken Smith的回答所指出的那样)。 虽然这种情况并不常见, Array.Copy()与直接强制转换Buffer.BlockCopy()相比,由于持续的“安全”值types强制转换, Buffer.BlockCopy()在这里执行得非常糟糕。
  • 来自外部StackOverflow的其他证据表明Array.Copy()Buffer.BlockCopy()对于同types数组复制的速度更快。

另一个使用Buffer.BlockCopy()时有意义的例子是,当你提供了一个基元数组(例如短裤),并且需要将它转换为一个字节数组(比如说,通过networking传输) 。 我在处理来自Silverlight AudioSink的audio时经常使用这个方法。 它将该示例作为short[]数组提供,但在构build提交给Socket.SendAsync()的数据包时,需要将其转换为byte[]数组。 您可以使用BitConverter ,逐个遍历数组,但速度要快很多(在我的testing中大约是20倍),只需要这样做:

 Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)). 

而同样的技巧也是相反的:

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

这与您在C#中使用C和C ++中常见的(void *)内存pipe理类似。

根据我的testing,性能不是偏好Array.Copy的Buffer.BlockCopy的理由。 从我的testingArray.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 

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没有。

要衡量这个论点,如果一个人不小心如何编写这个基准,他们很容易被误导。 我写了一个非常简单的testing来说明这一点。 在下面的testing中,如果我在开始Buffer.BlockCopy之前或Array.Copy之间交换我的testing顺序,那么首先进行的testing几乎总是最慢的(虽然它是最接近的一个)。 这意味着一堆原因,我不会简单地运行testing多次,尤其是一个接一个不会给出准确的结果。

我试图保持这个testing,每1000000次尝试1000000次连续双打。 然而在我然后忽略了第一个900000周期和其余的平均值。 在这种情况下,缓冲区是优越的。

 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

只是想添加我的testing用例,它再次显示BlockCopy对Array.Copy没有“PERFORMANCE”优势。 他们在我的机器上似乎有释放模式下的相同性能(都需要约66ms才能复制5000万个整数)。 在debugging模式下,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); }