为什么使用 dynamic 进行转换比使用 object 更快
Why is casting with dynamic faster than with object
我真是一头雾水,取下面代码:
[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
}
是的,我知道我可以直接将整数转换为给定的枚举,但这是我的实际代码的简化版本,其中包括使用通用扩展的工作。
无论如何我 运行 进行了一些性能测试,因为我很好奇哪个更快。我正在使用 BenchmarkDotNet
并看到那里:
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 | - | - | - | - |
动态版本实际上更快,而且不止一点点。我 运行 多次测试,但我无法理解它。
我看了一下编译后的版本,有没有优化的地方,但是看here。
谁能给我解释一下?
我相信 implicit/explicit 类型转换对您的操作有影响。
DirectCast
是显式转换中最快的 - ObjectCast
和 DynamicCast
正在实施隐式转换,通常会出现在您的结果中。
您可以查看 IL 代码以了解底层的区别。对象投射
public TEnum ObjectCast()
{
return (TEnum)(object)0;
}
将 int
值装箱到 object
中,然后拆箱到 TEnum
值中,因为它是值类型
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
我想这是与其他示例相比执行速度最慢的主要原因。
dynamic
对象投射
public TEnum DynamicCast()
{
return (TEnum) (dynamic) 0;
}
看起来更复杂
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
加载了类型信息,然后使用Binder.Convert
static method. Then an instance of generic CallSite
class is created, using Create
static call初始化CallSiteBinder
的实例(ldsfld
将静态字段的值压入堆栈)。我不是 100% 确定,但是通用参数 Func<CallSite, object, TEnum
表示一个函数,它将被调用以将对象转换为 TEnum
。最后几行显示此函数绑定到 TEnum
class。
因此,在幕后,编译器已经为您创建了一种将动态对象转换为所需 TEnum
类型的方法。并且只有从int
到object
的装箱操作将其传递给创建的函数。这听起来像是一个很好的理由,为什么它比使用装箱和拆箱操作的对象转换更快
我真是一头雾水,取下面代码:
[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
}
是的,我知道我可以直接将整数转换为给定的枚举,但这是我的实际代码的简化版本,其中包括使用通用扩展的工作。
无论如何我 运行 进行了一些性能测试,因为我很好奇哪个更快。我正在使用 BenchmarkDotNet
并看到那里:
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 | - | - | - | - |
动态版本实际上更快,而且不止一点点。我 运行 多次测试,但我无法理解它。
我看了一下编译后的版本,有没有优化的地方,但是看here。
谁能给我解释一下?
我相信 implicit/explicit 类型转换对您的操作有影响。
DirectCast
是显式转换中最快的 - ObjectCast
和 DynamicCast
正在实施隐式转换,通常会出现在您的结果中。
您可以查看 IL 代码以了解底层的区别。对象投射
public TEnum ObjectCast()
{
return (TEnum)(object)0;
}
将 int
值装箱到 object
中,然后拆箱到 TEnum
值中,因为它是值类型
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
我想这是与其他示例相比执行速度最慢的主要原因。
dynamic
对象投射
public TEnum DynamicCast()
{
return (TEnum) (dynamic) 0;
}
看起来更复杂
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
加载了类型信息,然后使用Binder.Convert
static method. Then an instance of generic CallSite
class is created, using Create
static call初始化CallSiteBinder
的实例(ldsfld
将静态字段的值压入堆栈)。我不是 100% 确定,但是通用参数 Func<CallSite, object, TEnum
表示一个函数,它将被调用以将对象转换为 TEnum
。最后几行显示此函数绑定到 TEnum
class。
因此,在幕后,编译器已经为您创建了一种将动态对象转换为所需 TEnum
类型的方法。并且只有从int
到object
的装箱操作将其传递给创建的函数。这听起来像是一个很好的理由,为什么它比使用装箱和拆箱操作的对象转换更快