Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is casting with dynamic faster than with object

I am really confused, take the following code:

[Benchmark]
public TEnum DynamicCast()
{
    return (TEnum)(dynamic)0;
}

[Benchmark]
public TEnum ObjectCast()
{
    return (TEnum)(object)0;
}

[Benchmark]
public TEnum DirectCast()
{
    return (TEnum)0;
}

public enum TEnum
{
    Foo,
    Bar
}

And yes I know that I could directly cast the integer to the given Enum, but this is a simplified version of my actual code which includes the work with generic extensions.

Anyhow I ran a few performance tests, because I was curious which one is faster. I am using BenchmarkDotNet and see there:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.100
  [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT


|      Method |       Mean |     Error |    StdDev |     Median |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------ |-----------:|----------:|----------:|-----------:|-------:|------:|------:|----------:|
| DynamicCast |  8.2124 ns | 0.0803 ns | 0.0671 ns |  8.2134 ns | 0.0076 |     - |     - |      24 B |
|  ObjectCast | 13.9178 ns | 0.4822 ns | 0.5922 ns | 13.6714 ns | 0.0076 |     - |     - |      24 B |
|  DirectCast |  0.0538 ns | 0.0422 ns | 0.0534 ns |  0.0311 ns |      - |     - |     - |         - |

The dynamic version is actually faster and not just by a bit. I ran the test multiple times and I can't wrap my head around it.

I looked at the compiled version, if there might be any optimization, but see here.

Can anyone explain this to me?

like image 269
Twenty Avatar asked Jan 05 '20 03:01

Twenty


1 Answers

You can have a look at IL code to see the difference under the hood. Object cast

public TEnum ObjectCast()
{
    return (TEnum)(object)0;
}

box int value into object and then unbox into TEnum value, since it's value type

IL_0001: ldc.i4.0
IL_0002: box          [System.Runtime]System.Int32
IL_0007: unbox.any    TestConsoleApp.Test/TEnum
IL_000c: stloc.0      // V_0
IL_000d: br.s         IL_000f

I guess that it's a main reason of the slowest execution in comparison with other samples.

The dynamic object cast

public TEnum DynamicCast()
{
    return (TEnum) (dynamic) 0;
}

looks more complicated

IL_0001: ldsfld       class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>> TestConsoleApp.Test/'<>o__0'::'<>p__0'
IL_0006: brfalse.s    IL_000a
IL_0008: br.s         IL_002f
IL_000a: ldc.i4.s     16 // 0x10
IL_000c: ldtoken      TestConsoleApp.Test/TEnum
IL_0011: call         class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
IL_0016: ldtoken      TestConsoleApp.Test
IL_001b: call         class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
IL_0020: call         class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::Convert(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, class [System.Runtime]System.Type, class [System.Runtime]System.Type)
IL_0025: call         class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<!0/*class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>*/> class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>>::Create(class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSiteBinder)
IL_002a: stsfld       class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>> TestConsoleApp.Test/'<>o__0'::'<>p__0'
IL_002f: ldsfld       class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>> TestConsoleApp.Test/'<>o__0'::'<>p__0'
IL_0034: ldfld        !0/*class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>*/ class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>>::Target
IL_0039: ldsfld       class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite`1<class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>> TestConsoleApp.Test/'<>o__0'::'<>p__0'
IL_003e: ldc.i4.0
IL_003f: box          [System.Runtime]System.Int32
IL_0044: callvirt     instance !2/*valuetype TestConsoleApp.Test/TEnum*/ class [System.Runtime]System.Func`3<class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite, object, valuetype TestConsoleApp.Test/TEnum>::Invoke(!0/*class [System.Linq.Expressions]System.Runtime.CompilerServices.CallSite*/, !1/*object*/)
IL_0049: stloc.0      // V_0
IL_004a: br.s         IL_004c

There is a type information loaded, then instance of CallSiteBinder is initialized using Binder.Convert static method. Then an instance of generic CallSite class is created, using Create static call (ldsfld pushes the value of static field into the stack). I'm not 100% sure, but the generic argument Func<CallSite, object, TEnum means a function, which will be invoked to convert the object into TEnum. Last lines show that this function is bound to TEnum class.

So, under the hood compiler already creates you a method to cast a dynamic object to required TEnum type. And there is only boxing operation from int to object to pass it to created function. This sounds like a good reason, why it's faster than object cast with boxing and unboxing operations

like image 140
Pavel Anikhouski Avatar answered Oct 24 '22 09:10

Pavel Anikhouski