.net Array.Copy 与 Buffer.BlockCopy
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1389821/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Array.Copy vs Buffer.BlockCopy
提问by thecoop
Array.Copyand Buffer.BlockCopyboth do the same thing, but BlockCopyis aimed at fast byte-level primitive array copying, whereas Copyis the general-purpose implementation. My question is - under what circumstances should you use BlockCopy? Should you use it at any time when you are copying primitive type arrays, or should you only use it if you're coding for performance? Is there anything inherently dangerous about using Buffer.BlockCopyover Array.Copy?
Array.Copy和Buffer.BlockCopy都做同样的事情,但BlockCopy针对快速字节级原始数组复制,而是Copy通用实现。我的问题是 - 在什么情况下应该使用BlockCopy?您应该在复制原始类型数组时随时使用它,还是应该只在为性能编码时使用它?使用Buffer.BlockCopyover有什么固有的危险Array.Copy吗?
采纳答案by MusiGenesis
Since the parameters to Buffer.BlockCopyare byte-based rather than index-based, you're more likely to screw up your code than if you use Array.Copy, so I would only use Buffer.BlockCopyin a performance-critical section of my code.
由于 to 的参数Buffer.BlockCopy是基于字节而不是基于索引的Array.Copy,因此与使用 相比,您更有可能搞砸代码,因此我只会Buffer.BlockCopy在代码的性能关键部分使用。
回答by Special Sauce
Prelude
序幕
I'm joining the party late, but with 32k views, it's worth getting this right. Most of the microbenchmarking code in the posted answers thus far suffer from one or more severe technical flaws, including not moving memory allocations out of the test loops (which introduces severe GC artifacts), not testing variable vs. deterministic execution flows, JIT warmup, and not tracking intra-test variability. In addition, most answers did not test the effects of varying buffer sizes and varying primitive types (with respect to either 32-bit or 64-bit systems). To address this question more comprehensively, I hooked it up to a custom microbenchmarking framework I developed that reduces most of the common "gotchas" to the extent possible. Tests were run in .NET 4.0 Release mode on both a 32-bit machine and a 64-bit machine. Results were averaged over 20 testing runs, in which each run had 1 million trials per method. Primitive types tested were byte(1 byte), int(4 bytes), and double(8 bytes). Three methods were tested: Array.Copy(), Buffer.BlockCopy(), and simple per-index assignment in a loop. The data is too voluminous to post here, so I will summarize the important points.
我迟到了,但有 32,000 次观看,值得做对。迄今为止,已发布答案中的大多数微基准测试代码都存在一个或多个严重的技术缺陷,包括未将内存分配移出测试循环(这会引入严重的 GC 工件)、未测试变量与确定性执行流、JIT 预热、并且不跟踪测试内的可变性。此外,大多数答案都没有测试不同缓冲区大小和不同原始类型(对于 32 位或 64 位系统)的影响。为了更全面地解决这个问题,我将它与我开发的自定义微基准测试框架联系起来,该框架尽可能减少了大多数常见的“问题”。测试在 .NET 4.0 Release 模式下在 32 位机器和 64 位机器上运行。结果平均超过 20 次测试运行,其中每次运行每种方法有 100 万次试验。测试的原始类型是byte(1 个字节)、int(4 个字节)和double(8 个字节)。测试了三种方法:Array.Copy()、Buffer.BlockCopy()和 循环中的简单每个索引分配。数据量太大,无法在此贴出,所以我将总结要点。
The Takeaways
外卖
- If your buffer length is about 75-100 or less, an explicit loop copy routine is usually faster (by about 5%) than either
Array.Copy()orBuffer.BlockCopy()for all 3 primitive types tested on both 32-bit and 64-bit machines. Additionly, the explicit loop copy routine has noticeably lower variability in performance compared to the two alternatives. The good performance is almost surely due to locality of referenceexploited by CPU L1/L2/L3 memory caching in conjunction with no method call overhead.- For
doublebuffers on 32-bit machines only:The explicit loop copy routine is better than both alternatives for all buffer sizes tested up to 100k. The improvement is 3-5% better than the other methods. This is because the performance ofArray.Copy()andBuffer.BlockCopy()become totally degraded upon passing the native 32-bit width. Thus I assume the same effect would apply tolongbuffers as well.
- For
- For buffer sizes exceeding ~100, explicit loop copying quickly becomes much slower than the other 2 methods (with the one particular exception just noted). The difference is most noticeable with
byte[], where explicit loop copying can become 7x or more slower at large buffer sizes. - In general, for all 3 primitive types tested and across all buffer sizes,
Array.Copy()andBuffer.BlockCopy()performed almost identically. On average,Array.Copy()seems to have a very slight edge of about 2% or less time taken (but 0.2% - 0.5% better is typical), althoughBuffer.BlockCopy()did occasionally beat it. For unknown reasons,Buffer.BlockCopy()has noticeably higher intra-test variability thanArray.Copy(). This effect could not be eliminated despite me trying multiple mitigations and not having an operable theory on why. - Because
Array.Copy()is a "smarter", more general, and much safer method, in addition to being very slightly faster and having less variability on average, it should be preferred toBuffer.BlockCopy()in almost all common cases. The only use case whereBuffer.BlockCopy()will be significantly better is when the source and destination array value types are different (as pointed out in Ken Smith's answer). While this scenario is not common,Array.Copy()can perform very poorly here due to the continual "safe" value type casting, compared to the direct casting ofBuffer.BlockCopy(). - Additional evidence from outside StackOverflow that
Array.Copy()is faster thanBuffer.BlockCopy()for same-type array copying can be found here.
- 如果您的缓冲区长度大约为 75-100 或更少,则显式循环复制例程通常比在 32 位和 64 位机器上测试的所有 3 种原始类型中的任何一个
Array.Copy()或Buffer.BlockCopy()所有 3 个基元类型都快(大约 5%)。此外,与两种替代方案相比,显式循环复制例程在性能上的可变性明显较低。良好的性能几乎肯定是由于CPU L1/L2/L3 内存缓存利用引用的局部性以及没有方法调用开销。- 仅适用于 32 位机器上的
double缓冲区:对于所有缓冲区大小测试高达 100k 的情况,显式循环复制例程优于这两种替代方法。改进比其他方法好 3-5%。这是因为在传递本机 32 位宽度时和的性能完全下降。因此,我假设同样的效果也适用于缓冲区。Array.Copy()Buffer.BlockCopy()long
- 仅适用于 32 位机器上的
- 对于超过 ~100 的缓冲区大小,显式循环复制很快就会变得比其他 2 种方法慢得多(刚刚提到的一个特殊例外)。差异在 中最为明显
byte[],其中显式循环复制在大缓冲区大小下可能会变慢 7 倍或更多。 - 一般来说,对于测试的所有 3 种原始类型以及所有缓冲区大小,
Array.Copy()并且Buffer.BlockCopy()执行几乎相同。平均而言,Array.Copy()似乎有大约 2% 或更少的时间花费的非常轻微的优势(但 0.2% - 0.5% 是典型的),尽管Buffer.BlockCopy()偶尔会击败它。由于未知原因,Buffer.BlockCopy()具有明显高于 的测试内变异性Array.Copy()。尽管我尝试了多种缓解措施并且没有关于原因的可操作理论,但无法消除这种影响。 - 因为它
Array.Copy()是一种“更智能”、更通用且更安全的方法,除了速度稍快且平均可变性更小之外Buffer.BlockCopy(),几乎在所有常见情况下都应该首选它。唯一Buffer.BlockCopy()明显更好的用例是源和目标数组值类型不同时(如 Ken Smith 的回答中所指出的)。虽然这种情况并不常见,Array.Copy()但由于持续的“安全”值类型转换,与Buffer.BlockCopy(). - 可以在此处找到来自 StackOverflow 外部
Array.Copy()比Buffer.BlockCopy()相同类型数组复制更快的其他证据。
回答by Ken Smith
Another example of when it makes sense to use Buffer.BlockCopy()is when you're provided with an array of primitives (say, shorts), and need to convert it to an array of bytes (say, for transmission over a network). I use this method frequently when dealing with audio from the Silverlight AudioSink. It provides the sample as a short[]array, but you need to convert it to a byte[]array when you're building the packet that you submit to Socket.SendAsync(). You could use BitConverter, and iterate through the array one-by-one, but it's a lot faster (about 20x in my testing) just to do this:
另一个有意义的使用示例Buffer.BlockCopy()是当您提供一组原语(例如,shorts),并且需要将其转换为字节数组(例如,用于通过网络传输)时。在处理来自 Silverlight AudioSink 的音频时,我经常使用这种方法。它以short[]数组的形式提供样本,但byte[]在构建提交到的数据包时需要将其转换为数组Socket.SendAsync()。您可以使用BitConverter, 并逐一遍历数组,但是这样做要快得多(在我的测试中大约是 20 倍):
Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).
And the same trick works in reverse as well:
同样的技巧也适用于相反的情况:
Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);
This is about as close as you get in safe C# to the (void *)sort of memory management that's so common in C and C++.
这与您在安全 C# 中获得的接近于(void *)在 C 和 C++ 中非常常见的内存管理类型一样。
回答by Kevin
Based on my testing, performance is nota reason to prefer Buffer.BlockCopy over Array.Copy. From my testing Array.Copy is actually fasterthan Buffer.BlockCopy.
根据我的测试,性能不是比 Array.Copy 更喜欢 Buffer.BlockCopy 的理由。从我的测试来看,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));
Example Output:
示例输出:
bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000
回答by user3523091
ArrayCopy is smarter than BlockCopy. It figures out how to copy elements if the source and destination are the same array.
ArrayCopy 比 BlockCopy 更智能。如果源和目标是同一个数组,它会计算出如何复制元素。
If we populate an int array with 0,1,2,3,4 and apply:
如果我们用 0,1,2,3,4 填充一个 int 数组并应用:
Array.Copy(array, 0, array, 1, array.Length - 1);
we end up with 0,0,1,2,3 as expected.
我们最终得到了预期的 0,0,1,2,3。
Try this with BlockCopy and we get: 0,0,2,3,4. If I assign array[0]=-1after that, it becomes -1,0,2,3,4 as expected, but if the array length is even, like 6, we get -1,256,2,3,4,5. Dangerous stuff. Don't use BlockCopy other than for copying one byte array into another.
用 BlockCopy 试试这个,我们得到:0,0,2,3,4。如果我array[0]=-1在那之后分配,它会按预期变成 -1,0,2,3,4,但如果数组长度是偶数,比如 6,我们会得到 -1,256,2,3,4,5。危险的东西。除了将一个字节数组复制到另一个字节数组之外,不要使用 BlockCopy。
There is another case where you can only use Array.Copy: if the array size is longer than 2^31. Array.Copy has an overload with a longsize parameter. BlockCopy does not have that.
还有另一种情况,您只能使用 Array.Copy:如果数组大小超过 2^31。Array.Copy 有一个带long大小参数的重载。BlockCopy 没有。
回答by Thulani Chivandikwa
To weigh in on this argument, if one is not careful how they author this benchmark they could be easily misled. I wrote a very simple test to illustrate this. In my test below if I swap the order of my tests between starting Buffer.BlockCopy first or Array.Copy the one that goes first is almost always the slowest (although its a close one). This means for a bunch of reasons which I wont go into simply running the tests multiple times esp one after the other will not give accurate results.
为了权衡这个论点,如果人们不小心他们如何编写这个基准,他们很容易被误导。我写了一个非常简单的测试来说明这一点。在下面的测试中,如果我在首先启动 Buffer.BlockCopy 或 Array.Copy 之间交换我的测试顺序,那么首先进行的几乎总是最慢的(尽管它很接近)。这意味着出于多种原因,我不会简单地多次运行测试,尤其是一个接一个地运行测试不会给出准确的结果。
I resorted to maintaining the test as is with 1000000 tries each for an array of 1000000 sequential doubles. However in I then disregard the first 900000 cycles and average the remainder. In that case the Buffer is superior.
对于 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;
}
}
回答by stt106
Just want to add my testing case which shows again BlockCopy has no 'PERFORMANCE' benefit over Array.Copy. They seem to have the same performance under release mode on my machine (both take about 66ms to copy 50 million integers). Under debug mode, BlockCopy is just marginally faster.
只是想添加我的测试用例,它再次显示 BlockCopy 与 Array.Copy 相比没有“性能”优势。它们在我的机器上的发布模式下似乎具有相同的性能(复制 5000 万个整数都需要大约 66 毫秒)。在调试模式下,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);
}

