Home .NET Boxing and unboxing- which is faster?

Boxing and unboxing- which is faster?

by admin

Boxing and unboxing- which is faster?

Interested in the speed of .NET packaging and decompressing operations, I decided to publish my small and highly subjective observations and measurements on this topic.

The example code is available at github , so I invite anyone who would like to report their measurement results in the comments.

Theory

Packing operation boxing is characterized by allocating memory in the managed heap to a value type object and then assigning a pointer to that memory location to a variable in the stack.

Unpacking unboxing on the other hand, allocates memory in the execution stack for an object obtained from the managed heap using a pointer.

It would seem that in both cases memory is allocated and there shouldn’t be much difference, if it weren’t for one but the most important detail is the memory area.

Recalling that the Garbage Collector is responsible for allocating memory in the .NET managed heap, it’s important to note that it does this non-linearly, due to its possible fragmentation (free memory chunks) and searching for the necessary free memory space of the required size.

Update:

As noted blanabrother in the comments, when allocating memory/copying a value into the managed heap there is no process to find a free memory area and its possible fragmentation due to the incriminating pointer and its further compactification using GC.However, based on the following measurements memory allocation rates in C++, I dare to suggest that the memory area (type) is the main reason for this difference in performance.

In the case of unpacking, however, memory is allocated in the execution stack, which contains a pointer to its end, which is also the beginning of the memory area for the new object.

The conclusion I draw from this is that the packing process should take much longer than unpacking, due to possible side effects related to GC and the slow speed of allocating memory/copying values to the managed heap.

Practice

To check this statement I have sketched 4 small functions: 2 for boxing and 2 for unboxing of int and struct types.

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;}}

To measure the performance the library was used BenchmarkDotNet in Release mode (I would be happy if DreamWalker would suggest how to make these measurements more objective). The following is the result of the measurements :

Boxing and unboxing- which is faster?
Boxing and unboxing- which is faster?

I don’t know for sure if there is no compiler optimization of the final code, but judging by the IL code, each function contains a unique checked operation.

Measured on several machines with different LoopCounts, however, the unpacking speed was faster from time to time than the packaging by a factor of 3-8

Example IL code for packing int 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

Example IL code to decompress struct 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

You may also like