【C#】配列のシャローコピー - てくメモ
上記の比較の際、念のため検索で下調べしたところ、.Skip(int).Take(int)
が方法として紹介されている場合があった。
表現力のLINQ。
ところでこれは、Range
導入後のC#であればTake(Range)
によりSkip(int)
を省くことができる。
LINQの強みである宣言的な記述という意味でも、よりノイズが少ない。
// const int START; // const int LEN; public int[] ByLINQ() => source.Take(START..(START + LEN)).ToArray();
というか、Range
が使えるなら配列を直接スライスすればOK。
public int[] BySlice() => source[START..(START + LEN)];
配列のスライシングは(Span
として扱う場合と異なり)アロケーションが行われる。
というわけで、部分的なコピーについて、手段を Array.Copy
、Buffer.BlockCopy
、ループ、LINQ、スライスとして BenchmarkDotNet にて計測してみる。
private readonly int[] source = Enumerable.Range(0, 1024).ToArray(); private const int START = 512; private const int LEN = 128; [Benchmark] public int[] ByArrayCopy() { int[] dest = new int[LEN]; Array.Copy(source, START, dest, 0, LEN); return dest; } [Benchmark] public int[] ByBlockCopy() { int[] dest = new int[LEN]; Buffer.BlockCopy(source, sizeof(int) * START, dest, 0, sizeof(int) * LEN); return dest; } [Benchmark] public int[] ByLoop() { int[] dest = new int[LEN]; for (int i = 0; i < dest.Length; i++) { dest[i] = source[i + START]; } return dest; } [Benchmark] public int[] ByLINQ() => source.Take(START..(START + LEN)).ToArray(); [Benchmark] public int[] BySlice() => source[START..(START + LEN)];
Method | Mean | Error | StdDev | Gen0 | Allocated | ------------ |----------:|----------:|---------:|-------:|----------:| ByArrayCopy | 66.47 ns | 4.618 ns | 0.253 ns | 0.1708 | 536 B | ByBlockCopy | 78.10 ns | 64.387 ns | 3.529 ns | 0.1708 | 536 B | ByLoop | 161.58 ns | 57.896 ns | 3.173 ns | 0.1707 | 536 B | ByLINQ | 520.73 ns | 65.406 ns | 3.585 ns | 0.1860 | 584 B | BySlice | 74.57 ns | 69.500 ns | 3.810 ns | 0.1708 | 536 B |
コピーという観点ではループやLINQはやはり直接的な手段に後れる。
選択肢は専用のメソッドかスライシング、ということでいい気がする。