我正在研究一些性能非常关键的代码,并且发现使用委托调用匿名方法比通过Func委托调用相同的代码更糟糕。
public class DelegateTests
{
public delegate int GetValueDelegate(string test);
private Func<string, int> getValueFunc;
private GetValueDelegate getValueDelegate;
public DelegateTests()
{
getValueDelegate = (s) => 42;
getValueFunc = (s) => 42;
}
[Benchmark]
public int CallWithDelegate()
{
return getValueDelegate.Invoke("TEST");
}
[Benchmark]
public int CallWithFunc()
{
return getValueFunc.Invoke("TEST");
}
}
BenchmarkDotNet
给出:
// * Summary *
BenchmarkDotNet=v0.10.4, OS=Windows 10.0.14393
Processor=Intel Core i7-4770HQ CPU 2.20GHz (Haswell), ProcessorCount=2
Frequency=10000000 Hz, Resolution=100.0000 ns, Timer=UNKNOWN
[Host] : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.6.1637.0
RyuJitX64 : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
Job=RyuJitX64 Jit=RyuJit Platform=X64
Method | Mean | Error | StdDev |
----------------- |----------:|----------:|----------:|
CallWithDelegate | 0.9926 ns | 0.0559 ns | 0.0783 ns |
CallWithFunc | 0.8763 ns | 0.0168 ns | 0.0131 ns |
// * Hints *
Outliers
DelegateTests.CallWithFunc: RyuJitX64 -> 3 outliers were removed
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
// ***** BenchmarkRunner: End *****
我们可以看到,使用Func
委托调用函数比使用GetValueDelegate
调用函数要快。我正在尝试寻找证据证明其为何如此运行。查看JIT优化的机器代码
26: return getValueDelegate.Invoke("TEST");
00E105C0 8B 49 08 mov ecx,dword ptr [ecx+8]
00E105C3 8B 15 C4 22 71 03 mov edx,dword ptr ds:[37122C4h]
00E105C9 8B 41 0C mov eax,dword ptr [ecx+0Ch]
00E105CC 8B 49 04 mov ecx,dword ptr [ecx+4]
00E105CF FF D0 call eax
00E105D1 C3 ret
相比
32: return getValueFunc.Invoke("TEST");
00E10608 8B 49 04 mov ecx,dword ptr [ecx+4]
00E1060B 8B 15 C4 22 71 03 mov edx,dword ptr ds:[37122C4h]
00E10611 8B 41 0C mov eax,dword ptr [ecx+0Ch]
00E10614 8B 49 04 mov ecx,dword ptr [ecx+4]
00E10617 FF D0 call eax
00E10619 C3 ret
它们看起来很相似。我开始认为这对于两个委托在Invoke方法内部可能有所不同。它们都从MulticastDelegate派生,这是CLR上所有委托的要求。为什么一个比另一个快?
这里是使用LegacyJitx86的数字。请注意,我只是对为什么有所不同感兴趣。顺便说一句,交换序列或可变顺序不会影响结果
// * Summary *
BenchmarkDotNet=v0.10.4, OS=Windows 10.0.14393
Processor=Intel Core i7-4770HQ CPU 2.20GHz (Haswell), ProcessorCount=2
Frequency=10000000 Hz, Resolution=100.0000 ns, Timer=UNKNOWN
[Host] : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.6.1637.0
LegacyJitX86 : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.6.1637.0
Job=LegacyJitX86 Jit=LegacyJit Platform=X86
Runtime=Clr
Method | Mean | Error | StdDev |
----------------- |----------:|----------:|----------:|
CallWithDelegate | 2.3385 ns | 0.0361 ns | 0.0320 ns |
CallWithFunc | 2.0144 ns | 0.0410 ns | 0.0384 ns |
// * Hints *
Outliers
DelegateTests.CallWithDelegate: LegacyJitX86 -> 1 outlier was removed
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
// ***** BenchmarkRunner: End *****
使用计算机上所有内容的当前版本运行它们,我找不到一致的赢家。
在此运行中,CallWithFunc
的平均时间加上错误是CallWithDelegate
的平均时间减去错误的99%,所以我觉得可能有些残留的旧行为...
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
| Method | Mean | Error | StdDev |
|----------------- |---------:|----------:|----------:|
| CallWithDelegate | 1.103 ns | 0.0024 ns | 0.0019 ns |
| CallWithFunc | 1.090 ns | 0.0050 ns | 0.0044 ns |
但是当我再次简单地运行它时,CallWithFunc
和CallWithDelegate
恰好在彼此的误差范围内,因此我的猜测是,如果存在[[is一个有利于彼此的因素,那么这可能是非常具体的。
和用[StructLayout(LayoutKind.Sequential)]
标记该类可能会显示一些内容。
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
| Method | Mean | Error | StdDev |
|----------------- |---------:|----------:|----------:|
| CallWithDelegate | 1.062 ns | 0.0036 ns | 0.0030 ns |
| CallWithFunc | 1.094 ns | 0.0039 ns | 0.0034 ns |