ボクシングとアンボクシング-どちらが速いですか?

画像

.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倍超えました。







intパッケージのILコードの例
.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









構造体を解凍するためのILコードの例
.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









All Articles