以前、最適化によりbyte[]
, sbyte[]
がゼロアロケーションとなる場合を確認した。
【C#】ゼロアロケーションバイト列 - てくメモ
これについて、現在1は対象が拡大している。
参考
・コレクション式 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
また、C# 12 (.NET 8) ではコレクション式の導入により、new T[]
と書いてアロケーションがないという見た目だったのが解消された。
// これらはアロケーション回避 private ReadOnlySpan<byte> ByteArray => [1, 2, 3]; private ReadOnlySpan<sbyte> SbyteArray => [1, 2, 3]; private ReadOnlySpan<int> IntArray => [1, 2, 3]; private ReadOnlySpan<uint> UintArray => [1, 2, 3]; private ReadOnlySpan<float> SingleArray => [1, 2, 3]; private ReadOnlySpan<double> DoubleArray => [1, 2, 3]; private ReadOnlySpan<short> ShortArray => [1, 2, 3]; private ReadOnlySpan<ushort> UshortArray => [1, 2, 3]; private ReadOnlySpan<long> LongArray => [1, 2, 3]; private ReadOnlySpan<ulong> UlongArray => [1, 2, 3]; private ReadOnlySpan<char> CharArray => ['a', 'b', 'c']; private ReadOnlySpan<bool> BoolArray => [true, false, true]; // これらはダメのよう // private ReadOnlySpan<decimal> DecimalArray => [1, 2, 3]; // private ReadOnlySpan<nint> NintArray => [1, 2, 3]; // private ReadOnlySpan<nuint> NuintArray => [1, 2, 3];
以下に参考で IL を折りたたむ。
折りたたみ(確認用・長い)
※ ZeroAllocArraySandbox はメンバーを置いたクラス名
.method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<uint8> get_ByteArray () cil managed { // Method begins at RVA 0x2224 // Header size: 1 // Code size: 12 (0xc) .maxstack 8 IL_0000: ldsflda valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=3' '<PrivateImplementationDetails>'::'039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81' IL_0005: ldc.i4.3 IL_0006: newobj instance void valuetype [System.Runtime]System.ReadOnlySpan`1<uint8>::.ctor(void*, int32) IL_000b: ret } // end of method ZeroAllocArraySandbox::get_ByteArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<int8> get_SbyteArray () cil managed { // Method begins at RVA 0x2231 // Header size: 1 // Code size: 12 (0xc) .maxstack 8 IL_0000: ldsflda valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=3' '<PrivateImplementationDetails>'::'039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81' IL_0005: ldc.i4.3 IL_0006: newobj instance void valuetype [System.Runtime]System.ReadOnlySpan`1<int8>::.ctor(void*, int32) IL_000b: ret } // end of method ZeroAllocArraySandbox::get_SbyteArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<int32> get_IntArray () cil managed { // Method begins at RVA 0x223e // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12_Align=4' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<int32>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_IntArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<uint32> get_UintArray () cil managed { // Method begins at RVA 0x224a // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12_Align=4' '<PrivateImplementationDetails>'::'4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<uint32>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_UintArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<float32> get_SingleArray () cil managed { // Method begins at RVA 0x2256 // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=12_Align=4' '<PrivateImplementationDetails>'::'8E628779E6A74EE0B36991C10158F63CAFEC7D340AD4E075592502C8708524DD4' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<float32>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_SingleArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<float64> get_DoubleArray () cil managed { // Method begins at RVA 0x2262 // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=24_Align=8' '<PrivateImplementationDetails>'::A68DE4B5E96A60C8CEB3C7B7EF93461725BDBBFF3516B136585A743B5C0EC6648 IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<float64>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_DoubleArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<int16> get_ShortArray () cil managed { // Method begins at RVA 0x226e // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=6_Align=2' '<PrivateImplementationDetails>'::'047DBF5366372631BA7E3E02520E651446B899C96C4B64663BAC378A298A7BF72' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<int16>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_ShortArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<uint16> get_UshortArray () cil managed { // Method begins at RVA 0x227a // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=6_Align=2' '<PrivateImplementationDetails>'::'047DBF5366372631BA7E3E02520E651446B899C96C4B64663BAC378A298A7BF72' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<uint16>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_UshortArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<int64> get_LongArray () cil managed { // Method begins at RVA 0x2286 // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=24_Align=8' '<PrivateImplementationDetails>'::E2E2033AE7E19D680599D4EB0A1359A2B48EC5BAAC75066C317FBF85159C54EF8 IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<int64>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_LongArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<uint64> get_UlongArray () cil managed { // Method begins at RVA 0x2292 // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=24_Align=8' '<PrivateImplementationDetails>'::E2E2033AE7E19D680599D4EB0A1359A2B48EC5BAAC75066C317FBF85159C54EF8 IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<uint64>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_UlongArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<char> get_CharArray () cil managed { // Method begins at RVA 0x229e // Header size: 1 // Code size: 11 (0xb) .maxstack 8 IL_0000: ldtoken field valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=6_Align=2' '<PrivateImplementationDetails>'::'13E228567E8249FCE53337F25D7970DE3BD68AB2653424C7B8F9FD05E33CAEDF2' IL_0005: call valuetype [System.Runtime]System.ReadOnlySpan`1<!!0> [System.Runtime]System.Runtime.CompilerServices.RuntimeHelpers::CreateSpan<char>(valuetype [System.Runtime]System.RuntimeFieldHandle) IL_000a: ret } // end of method ZeroAllocArraySandbox::get_CharArray .method private hidebysig specialname instance valuetype [System.Runtime]System.ReadOnlySpan`1<bool> get_BoolArray () cil managed { // Method begins at RVA 0x22aa // Header size: 1 // Code size: 12 (0xc) .maxstack 8 IL_0000: ldsflda valuetype '<PrivateImplementationDetails>'/'__StaticArrayInitTypeSize=3' '<PrivateImplementationDetails>'::'85F90DFEA1D8027E1463E5CA971A250110A20DF0119D204A74220BC63516D15B' IL_0005: ldc.i4.3 IL_0006: newobj instance void valuetype [System.Runtime]System.ReadOnlySpan`1<bool>::.ctor(void*, int32) IL_000b: ret } // end of method ZeroAllocArraySandbox::get_BoolArray
最後に、配列の書き方と並べて BenchmarkDotNet にてアロケーションを見てみる。
[Benchmark] public int Alloc() { bool[] bs = [false, false, true]; static int challenge(ReadOnlySpan<bool> span, int count) { var result = 0; for (int i = 0; i < count; i++) { if (span[i % span.Length]) result++; } return result; } return challenge(bs, 10); } [Benchmark] public double ZeroAlloc() { ReadOnlySpan<bool> bs = [false, false, true]; static int challenge(ReadOnlySpan<bool> span, int count) { var result = 0; for (int i = 0; i < count; i++) { if (span[i % span.Length]) result++; } return result; } return challenge(bs, 10); }
Method | Mean | Error | StdDev | Gen0 | Allocated | ---------- |---------:|---------:|---------:|-------:|----------:| Alloc | 19.33 ns | 2.002 ns | 0.110 ns | 0.0102 | 32 B | ZeroAlloc | 17.86 ns | 7.375 ns | 0.404 ns | - | - |
確かにアロケーションなし。