If you are optimising for performance, then you need to measure what you are optimising.
This is a quick mini-benchmark that will give you an idea of what the trade-offs are, you can adjust for the size of data you're expected and it may change the results.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CodeReview184845
{
delegate byte[] ConcatDelegate(params byte[][] inputs);
public static class Program
{
static byte[] ConcatenateBuffers(params byte[][] list)
{
int totalLen = 0;
for (int i = 0; i < list.Length; ++i)
totalLen += list[i].Length;
byte[] ret = new byte[totalLen];
int offset = 0;
foreach (var item in list)
{
Array.ConstrainedCopy(item, 0, ret, offset, item.Length);
offset += item.Length;
}
return ret;
}
static byte[] ArrayCopyConcat(params byte[][] list)
{
int totalLen = 0;
for (int i = 0; i < list.Length; ++i)
totalLen += list[i].Length;
byte[] ret = new byte[totalLen];
int offset = 0;
foreach (var item in list)
{
Array.Copy(item, 0, ret, offset, item.Length);
offset += item.Length;
}
return ret;
}
static byte[] LinqConcat(params byte[][] list)
{
return list.SelectMany(x => x).ToArray();
}
static byte[] BufferConcat(params byte[][] list)
{
var ret = new byte[list.Sum(t => t.Length)];
var offset = 0;
foreach (var item in list)
{
var length = item.Length;
Buffer.BlockCopy(item, 0, ret, offset, length);
offset += length;
}
return ret;
}
static byte[] ConcatWithList(byte[][] list)
{
var data = new List<byte>();
for (var i = 0; i < list.Length; ++i)
{
for (var j = 0; j < list[i].Length; j++)
{
data.Add(list[i][j]); // the list automatically resizes
}
}
return data.ToArray();
}
static Tuple<string, ConcatDelegate> Test(string name, ConcatDelegate d)
{
return new Tuple<string, ConcatDelegate>(name, d);
}
static void Main(string[] args)
{
var inputs = CreateInputs();
var tests = new[]
{
Test("original", ConcatenateBuffers),
Test("list", ConcatWithList),
Test("Array.Copy", ArrayCopyConcat),
Test("Linq", LinqConcat),
Test("Buffer", BufferConcat)
};
var elapsed = tests.ToDictionary(test => test.Item1, test => new List<long>());
while (true)
foreach (var test in tests)
{
Console.Write($"test {test.Item1}\t");
Console.Out.Flush();
byte[] output = {};
var stopwatch = Stopwatch.StartNew();
for (var run = 0; run < 1000; ++run)
output = test.Item2(inputs);
var total = Sum(output);
stopwatch.Stop();
elapsed[test.Item1].Add(stopwatch.ElapsedMilliseconds);
Console.WriteLine($"elapsed {stopwatch.ElapsedMilliseconds} ms (total: {total}) (mean: {Mean(elapsed[test.Item1])} ms) ");
Console.Out.Flush();
}
}
private static long Sum(byte[] output)
{
var result = 0L;
foreach (var value in output)
result += value;
return result;
}
private static long Mean(List<long> times)
{
return times.Sum()/ times.Count;
}
private static byte[][] CreateInputs()
{
var inputs = new byte[100][];
for (var i = 0; i < inputs.Length; ++i)
{
inputs[i] = new byte[500 + 200 * i];
for (var j = 0; j < inputs[i].Length; ++j)
inputs[i][j] = (byte)j;
}
return inputs;
}
}
}
On my machine, after a few runs it settles down to results like:
test original elapsed 429 ms (total: 132052576) (mean: 430 ms)
test list elapsed 3451 ms (total: 132052576) (mean: 3419 ms)
test Array.Copy elapsed 437 ms (total: 132052576) (mean: 425 ms)
test Linq elapsed 448 ms (total: 132052576) (mean: 440 ms)
test Buffer elapsed 443 ms (total: 132052576) (mean: 429 ms)
The runtime is very good at optimising LINQ. Writing something using a for loop instead of LINQ may give you an extra percent (and in some cases can allow you to perform other optimisations), but very often the difference is lost in the noise.