.NETでのパッキングおよびアンパッキング操作の速度の問題に興味があるため、このトピックに関する小規模で非常に主観的な観察と測定値を公開することにしました。
サンプルコードはgithubで入手できるので、コメントで測定結果を報告してください。
理論
ボクシングのパッキング操作は、値型オブジェクトのマネージヒープ内のメモリの割り当てと、このメモリセクションへのポインタのスタック上の変数へのさらなる割り当てによって特徴付けられます。
一方、ボックス化解除は、ポインターを使用してマネージヒープから取得したオブジェクトの実行スタックにメモリを割り当てます。
どちらの場合もメモリが割り当てられているように見えますが、メモリ領域が1つでなければ非常に重要な詳細は大きな違いはありません。
ガベージコレクター(ガベージコレクター)は、マネージヒープ内の.NETでメモリを割り当てることに留意してください。断片化(空きメモリ領域の可用性)と必要なサイズの必要な空き領域の検索のため、非線形にこれを行うことに注意することが重要です。
更新:
コメントでblanabrotherが指摘したように、メモリの割り当て/マネージヒープ内の値のコピー時に、メモリの空き部分を検索するプロセスはなく、GCを使用したポインタとそのさらなるコンパクト化により、断片化の可能性があります。 ただし、C ++ でのメモリ割り当ての速度の以下の測定に基づいて、 メモリの領域(タイプ)がこのパフォーマンスの違いの主な理由であると思い込んでいます。
アンパックの場合、メモリは実行スタックに割り当てられます。実行スタックには、新しいオブジェクト用のメモリの先頭である、その末尾へのポインタが含まれます。
このことから、GCに関連する副作用の可能性とメモリ割り当て/マネージヒープの値のコピーの速度が遅いため、パッキングプロセスはアンパックよりもはるかに長くかかると結論付けました。
練習する
このステートメントを検証するために、4つの小さな関数をスケッチしました。2つはボクシング用で、2つはボックス化解除タイプintおよびstructです。
public class BoxingUnboxingBenchmark { private long LoopCount = 1000000; private object BoxedInt = 1; private object BoxedStruct = new ExampleStruct { Amount = 1000, Currency = "RUB" }; [Benchmark] public object BoxingInt() { int unboxed = 1000; for (var i = 0; i < LoopCount; i++) { BoxedInt = (object) unboxed; } return BoxedInt; } [Benchmark] public int UnboxingInt() { int unboxed = 1000; for (var i = 0; i < LoopCount; i++) { unboxed = (int)BoxedInt; } return unboxed; } [Benchmark] public object BoxingStruct() { ExampleStruct unboxed = new ExampleStruct() { Amount = 1000, Currency = "RUB" }; for (var i = 0; i < LoopCount; i++) { BoxedStruct = (object) unboxed; } return BoxedStruct; } [Benchmark] public ExampleStruct UnBoxingStruct() { ExampleStruct unboxed = new ExampleStruct(); for (var i = 0; i < LoopCount; i++) { unboxed = (ExampleStruct) BoxedStruct; } return unboxed; } }
パフォーマンスを測定するために、リリースモードでBenchmarkDotNetライブラリが使用されました( DreamWalkerがこれらの測定をより客観的にする方法を教えてくれたら嬉しいです)。 測定結果は次のとおりです。
最終コードのコンパイラによる最適化の欠如をしっかりと確信することはできませんが、ILコードから判断すると、各関数には検証すべき特異な操作が含まれています。
LoopCountの数が異なる複数のマシンで測定を実行しましたが、時々、開梱速度が包装を3〜8倍超えました。
.method public hidebysig instance object
BoxingInt() cil managed
{
.custom instance void [BenchmarkDotNet.Core]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = ( 01 00 00 00 )
// Code size 43 (0x2b)
.maxstack 2
.locals init ([0] int32 unboxed,
[1] int32 i)
IL_0000: ldc.i4 0x3e8
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: stloc.1
IL_0008: br.s IL_001a
IL_000a: ldarg.0
IL_000b: ldloc.0
IL_000c: box [mscorlib]System.Int32
IL_0011: stfld object ConsoleApp1.BoxingUnboxingBenchmark::BoxedInt
IL_0016: ldloc.1
IL_0017: ldc.i4.1
IL_0018: add
IL_0019: stloc.1
IL_001a: ldloc.1
IL_001b: conv.i8
IL_001c: ldarg.0
IL_001d: ldfld int64 ConsoleApp1.BoxingUnboxingBenchmark::LoopCount
IL_0022: blt.s IL_000a
IL_0024: ldarg.0
IL_0025: ldfld object ConsoleApp1.BoxingUnboxingBenchmark::BoxedInt
IL_002a: ret
} // end of method BoxingUnboxingBenchmark::BoxingInt
.method public hidebysig instance valuetype ConsoleApp1.ExampleStruct
UnBoxingStruct() cil managed
{
.custom instance void [BenchmarkDotNet.Core]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = ( 01 00 00 00 )
// Code size 40 (0x28)
.maxstack 2
.locals init ([0] valuetype ConsoleApp1.ExampleStruct unboxed,
[1] int32 i)
IL_0000: ldloca.s unboxed
IL_0002: initobj ConsoleApp1.ExampleStruct
IL_0008: ldc.i4.0
IL_0009: stloc.1
IL_000a: br.s IL_001c
IL_000c: ldarg.0
IL_000d: ldfld object ConsoleApp1.BoxingUnboxingBenchmark::BoxedStruct
IL_0012: unbox.any ConsoleApp1.ExampleStruct
IL_0017: stloc.0
IL_0018: ldloc.1
IL_0019: ldc.i4.1
IL_001a: add
IL_001b: stloc.1
IL_001c: ldloc.1
IL_001d: conv.i8
IL_001e: ldarg.0
IL_001f: ldfld int64 ConsoleApp1.BoxingUnboxingBenchmark::LoopCount
IL_0024: blt.s IL_000c
IL_0026: ldloc.0
IL_0027: ret
} // end of method BoxingUnboxingBenchmark::UnBoxingStruct