てくメモ

trivial な notes

【C#】配列の部分的なコピー

【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.CopyBuffer.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はやはり直接的な手段に後れる。
選択肢は専用のメソッドかスライシング、ということでいい気がする。