On .NET 5.0.6 (x64) - for copying byte array to byte array - Array.Copy seems to be the winner even for short arrays. Interestingly enough Enumumerable.Concat is also relatively fast on longer arrays because it optimizes for ICollection<T> if enumerable implements it (it is not the case for .NET Framework though).
Benchmark results and source code:
| 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));
}
}