Array.Copy和Buffer.BlockCopy都做同样的事情,但是BlockCopy针对快速字节级原始数组复制,而是Copy通用实现.我的问题是 - 在什么情况下你应该使用BlockCopy?您是否应该在复制基本类型数组时随时使用它,或者只有在编写性能时才使用它?使用Buffer.BlockCopy结束有什么固有的危险Array.Copy吗?
Spe*_*uce 115
序幕
我迟到了,但是有32k的观点,值得这样做.到目前为止,已发布的答案中的大多数微基准测试代码都存在一个或多个严重的技术缺陷,包括不将测试循环中的内存分配移出(这引入了严重的GC工件),而不测试变量与确定性执行流程,JIT预热,而不是跟踪测试内的可变性.此外,大多数答案没有测试不同缓冲区大小和不同原始类型(对于32位或64位系统)的影响.为了更全面地解决这个问题,我将它与我开发的自定义微基准测试框架联系起来,尽可能地减少了大多数常见的"陷阱".测试在32位计算机和64位计算机上以.NET 4.0发布模式运行.结果在20次测试运行中取平均值,其中每次运行每次方法有100万次试验.测试的原始类型是byte(1字节),int(4字节)和double(8字节).三种方法进行了测试:Array.Copy(),Buffer.BlockCopy(),和简单的每个指标分配在一个循环.这里的数据太庞大了,所以我将总结一下重点.
小贴士
Array.Copy()或Buffer.BlockCopy()用于在两个32位和64位机器测试的所有3种原始类型.此外,与两个备选方案相比,显式循环复制例程在性能方面具有明显更低的可变性.良好的性能几乎肯定是由于CPU L1/L2/L3内存缓存利用的引用的局部性以及没有方法调用开销.
double缓冲区:显式循环复制例程优于所有测试高达100k的缓冲区大小的两种选择.改善比其他方法好3-5%.这是因为性能和成为在使本地32位宽度完全降解.因此我假设同样的效果也适用于缓冲区.Array.Copy()Buffer.BlockCopy()longbyte[],在大缓冲区大小的情况下,显式循环复制可能会变慢7倍或更慢.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().Array.Copy()比Buffer.BlockCopy()同类型阵列复制更快的其他证据.Ken*_*ith 65
另一个有意义的例子Buffer.BlockCopy()就是当你提供一个基元数组(比如说短路)时,需要把它转换成一个字节数组(比如说,通过网络传输).在处理来自Silverlight AudioSink的音频时,我经常使用此方法.它将示例作为short[]数组提供,但是byte[]在构建提交的数据包时需要将其转换为数组Socket.SendAsync().您可以BitConverter逐个使用并迭代遍历数组,但这样做速度要快得多(在我的测试中大约20倍):
Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).
Run Code Online (Sandbox Code Playgroud)
同样的技巧也是相反的:
Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);
Run Code Online (Sandbox Code Playgroud)
这与您在C#(void *)和C++中常见的内存管理类型相当接近.
Mus*_*sis 57
由于参数Buffer.BlockCopy是基于字节而不是基于索引的Array.Copy,因此与使用时相比,您更有可能搞砸代码,因此我只会Buffer.BlockCopy在代码的性能关键部分使用.
Kev*_*vin 15
根据我的测试,性能不是偏向于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));
Run Code Online (Sandbox Code Playgroud)
示例输出:
bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000
Run Code Online (Sandbox Code Playgroud)
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 没有。
在 .NET 5.0.6 (x64) 上 - 用于将字节数组复制到字节数组 -Array.Copy即使对于短数组来说似乎也是赢家。有趣的Enumumerable.Concat是,在较长的数组上也相对较快,因为它针对ICollection<T>enumerable 实现它进行了优化(但 .NET Framework 的情况并非如此)。
基准测试结果和源代码:
| 方法 | 数组长度 | 数组数量 | 意思是 | 错误 | 标准差 |
|---|---|---|---|---|---|
| 枚举连接 | 50 | 1 | 63.54纳秒 | 1.863纳秒 | 5.435纳秒 |
| For循环 | 50 | 1 | 95.01纳秒 | 2.008纳秒 | 4.694纳秒 |
| Foreach循环 | 50 | 1 | 91.80纳秒 | 1.953纳秒 | 4.527纳秒 |
| 数组复制 | 50 | 1 | 26.66纳秒 | 1.043纳秒 | 3.075纳秒 |
| 缓冲区块复制 | 50 | 1 | 27.65纳秒 | 0.716纳秒 | 2.076纳秒 |
| 枚举连接 | 50 | 2 | 265.30纳秒 | 9.362纳秒 | 26.558纳秒 |
| For循环 | 50 | 2 | 188.80纳秒 | 5.084纳秒 | 13.659纳秒 |
| Foreach循环 | 50 | 2 | 180.16纳秒 | 4.953纳秒 | 14.448纳秒 |
| 数组复制 | 50 | 2 | 42.47纳秒 | 0.970纳秒 | 2.623纳秒 |
| 缓冲区块复制 | 50 | 2 | 47.28纳秒 | 1.038纳秒 | 2.024纳秒 |
| 枚举连接 | 50 | 3 | 327.81纳秒 | 9.332纳秒 | 27.368纳秒 |
| For循环 | 50 | 3 | 285.21纳秒 | 6.028纳秒 | 17.680纳秒 |
| Foreach循环 | 50 | 3 | 260.04纳秒 | 5.308纳秒 | 14.795纳秒 |
| 数组复制 | 50 | 3 | 62.97纳秒 | 1.505纳秒 | 4.366纳秒 |
| 缓冲区块复制 | 50 | 3 | 73.45纳秒 | 3.265纳秒 | 9.626纳秒 |
| 枚举连接 | 100 | 1 | 69.27纳秒 | 1.762纳秒 | 5.167纳秒 |
| For循环 | 100 | 1 | 189.44纳秒 | 3.907纳秒 | 11.398纳秒 |
| Foreach循环 | 100 | 1 | 163.03纳秒 | 3.311纳秒 | 5.057纳秒 |
| 数组复制 | 100 | 1 | 33.23纳秒 | 1.225纳秒 | 3.574纳秒 |
| 缓冲区块复制 | 100 | 1 | 35.55纳秒 | 1.004纳秒 | 2.865纳秒 |
| 枚举连接 | 100 | 2 | 291.20纳秒 | 10.245纳秒 | 30.207纳秒 |
| For循环 | 100 | 2 | 363.01纳秒 | 7.160纳秒 | 9.310纳秒 |
| Foreach循环 | 100 | 2 | 357.98纳秒 | 7.228纳秒 | 7.734纳秒 |
| 数组复制 | 100 | 2 | 56.59纳秒 | 1.702纳秒 | 5.019纳秒 |
| 缓冲区块复制 | 100 | 2 | 61.82纳秒 | 1.747纳秒 | 5.095纳秒 |
| 枚举连接 | 100 | 3 | 354.19纳秒 | 9.679纳秒 | 27.925纳秒 |
| For循环 | 100 | 3 | 544.59纳秒 | 16.346纳秒 | 48.198纳秒 |
| Foreach循环 | 100 | 3 | 522.59纳秒 | 12.927纳秒 | 37.914纳秒 |
| 数组复制 | 100 | 3 | 80.66纳秒 | 3.154纳秒 | 9.300纳秒 |
| 缓冲区块复制 | 100 | 3 | 87.21纳秒 | 2.414纳秒 | 7.081纳秒 |
| 枚举连接 | 1000 | 1 | 181.98纳秒 | 4.073纳秒 | 11.882纳秒 |
| For循环 | 1000 | 1 | 1,643.59 纳秒 | 32.135纳秒 | 50.030纳秒 |
| Foreach循环 | 1000 | 1 | 1,444.37 纳秒 | 28.705纳秒 | 70.951纳秒 |
| 数组复制 | 1000 | 1 | 143.55纳秒 | 3.874纳秒 | 11.301纳秒 |
| 缓冲区块复制 | 1000 | 1 | 146.69纳秒 | 3.349纳秒 | 9.662纳秒 |
| 枚举连接 | 1000 | 2 | 525.41纳秒 | 10.621纳秒 | 29.254纳秒 |
| For循环 | 1000 | 2 | 3,264.64 纳秒 | 47.449纳秒 | 39.622纳秒 |
| Foreach循环 | 1000 | 2 | 2,818.58 纳秒 | 56.489纳秒 | 126.345纳秒 |
| 数组复制 | 1000 | 2 | 283.73纳秒 | 5.613纳秒 | 15.175纳秒 |
| 缓冲区块复制 | 1000 | 2 | 292.29纳秒 | 5.827纳秒 | 15.654纳秒 |
| 枚举连接 | 1000 | 3 | 712.58纳秒 | 15.274纳秒 | 44.068纳秒 |
| For循环 | 1000 | 3 | 5,005.50 纳秒 | 99.791纳秒 | 214.810纳秒 |
| Foreach循环 | 1000 | 3 | 4,272.26 纳秒 | 89.589纳秒 | 261.335纳秒 |
| 数组复制 | 1000 | 3 | 422.30纳秒 | 8.542纳秒 | 22.502纳秒 |
| 缓冲区块复制 | 1000 | 3 | 433.49纳秒 | 8.808纳秒 | 20.587纳秒 |
| 枚举连接 | 10000 | 1 | 1,221.27 纳秒 | 28.138纳秒 | 82.964纳秒 |
| For循环 | 10000 | 1 | 16,464.04 纳秒 | 441.552纳秒 | 1,294.995 纳秒 |
| Foreach循环 | 10000 | 1 | 13,916.99 纳秒 | 273.792纳秒 | 676.746纳秒 |
| 数组复制 | 10000 | 1 | 1,150.18 纳秒 | 26.901纳秒 | 79.318纳秒 |
| 缓冲区块复制 | 10000 | 1 | 1,154.10 纳秒 | 23.094纳秒 | 60.025纳秒 |
| 枚举连接 | 10000 | 2 | 2,798.41 纳秒 | 54.615纳秒 | 141.952纳秒 |
| For循环 | 10000 | 2 | 32,570.61 纳秒 | 646.828纳秒 | 1,473.154 纳秒 |
| Foreach循环 | 10000 | 2 | 27,707.12 纳秒 | 545.888纳秒 | 1,051.741 纳秒 |
| 数组复制 | 10000 | 2 | 2,379.49 纳秒 | 72.264纳秒 | 213.073纳秒 |
| 缓冲区块复制 | 10000 | 2 | 2,374.17 纳秒 | 59.035纳秒 | 173.140纳秒 |
| 枚举连接 | 10000 | 3 | 3,885.27 纳秒 | 77.809纳秒 | 196.633纳秒 |
| For循环 | 10000 | 3 | 49,833.15 纳秒 | 984.022纳秒 | 2,097.031 纳秒 |
| Foreach循环 | 10000 | 3 | 41,174.21 纳秒 | 819.971纳秒 | 1,392.373 纳秒 |
| 数组复制 | 10000 | 3 | 3,738.32 纳秒 | 74.331纳秒 | 91.285纳秒 |
| 缓冲区块复制 | 10000 | 3 | 3,839.79 纳秒 | 78.865纳秒 | 231.298纳秒 |
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));
}
}
Run Code Online (Sandbox Code Playgroud)