看看C#编译器为下面的方法吐出的IL代码,至少在这种情况下编译器看起来不够聪明。不过,不确定当 IL 代码被翻译成本机代码甚至在处理器流水线的后期会发生什么 - 将会有进一步的优化:
private static bool Compare(int x)
{
return (x > 10 || x == 10) && (x < 10 || x == 10);
}
对应的IL:
IL_0000: ldarg.0 // x
IL_0001: ldc.i4.s 10 // 0x0a
IL_0003: bgt.s IL_000a
IL_0005: ldarg.0 // x
IL_0006: ldc.i4.s 10 // 0x0a
IL_0008: bne.un.s IL_0017
IL_000a: ldarg.0 // x
IL_000b: ldc.i4.s 10 // 0x0a
IL_000d: blt.s IL_0015
IL_000f: ldarg.0 // x
IL_0010: ldc.i4.s 10 // 0x0a
IL_0012: ceq
IL_0014: ret
IL_0015: ldc.i4.1
IL_0016: ret
IL_0017: ldc.i4.0
IL_0018: ret
这是第二个(优化的)版本:
private static bool Compare(int x)
{
return x >= 10 && x <= 10;
}
同样,对应的 IL 代码:
IL_0000: ldarg.0 // x
IL_0001: ldc.i4.s 10 // 0x0a
IL_0003: blt.s IL_000e
IL_0005: ldarg.0 // x
IL_0006: ldc.i4.s 10 // 0x0a
IL_0008: cgt
IL_000a: ldc.i4.0
IL_000b: ceq
IL_000d: ret
IL_000e: ldc.i4.0
IL_000f: ret
由于第二个版本明显更短,它在运行时内联的机会更大,因此我们应该期望它运行得更快一些。
最后,第三个,我们称它为“最好的”(x == 10):
private static bool Compare(int x)
{
return x == 10;
}
及其IL:
IL_0000: ldarg.0 // x
IL_0001: ldc.i4.s 10 // 0x0a
IL_0003: ceq
IL_0005: ret
简洁明了。
使用 Benchmark.NET 和 [MethodImpl(MethodImplOptions.NoInlining)] 运行基准测试揭示了运行时行为,这两种实现似乎仍然存在很大差异:
案例 1:测试不是 10 的候选人(否定案例)。
Method | Jit | Platform | Mean
----------- |---------- |--------- |----------
TestBest | LegacyJit | X64 | 2.329 ms
TestOpt | LegacyJit | X64 | 2.704 ms
TestNonOpt | LegacyJit | X64 | 3.324 ms
TestBest | LegacyJit | X86 | 1.956 ms
TestOpt | LegacyJit | X86 | 2.178 ms
TestNonOpt | LegacyJit | X86 | 2.796 ms
TestBest | RyuJit | X64 | 2.480 ms
TestOpt | RyuJit | X64 | 2.489 ms
TestNonOpt | RyuJit | X64 | 3.101 ms
TestBest | RyuJit | X86 | 1.865 ms
TestOpt | RyuJit | X86 | 2.170 ms
TestNonOpt | RyuJit | X86 | 2.853 ms
案例 2:使用 10 进行测试(阳性案例)。
Method | Jit | Platform | Mean
----------- |---------- |--------- |---------
TestBest | LegacyJit | X64 | 2.396 ms
TestOpt | LegacyJit | X64 | 2.780 ms
TestNonOpt | LegacyJit | X64 | 3.370 ms
TestBest | LegacyJit | X86 | 2.044 ms
TestOpt | LegacyJit | X86 | 2.199 ms
TestNonOpt | LegacyJit | X86 | 2.533 ms
TestBest | RyuJit | X64 | 2.470 ms
TestOpt | RyuJit | X64 | 2.532 ms
TestNonOpt | RyuJit | X64 | 2.552 ms
TestBest | RyuJit | X86 | 1.911 ms
TestOpt | RyuJit | X86 | 2.210 ms
TestNonOpt | RyuJit | X86 | 2.753 ms
有趣的是,在这两种情况下,对于 opt 和 non-opt X64 版本,新 JIT 的运行时间大致相同。
问题仍然是:为什么编译器不优化这些类型的模式?我的猜测是,这是因为运算符重载之类的东西,这使得编译器无法推断出一些正确的逻辑结论,但 II 可能非常偏离......此外,对于内置值类型,它应该是可能的。哦,好吧……
最后,这里有一篇关于布尔表达式优化的好文章:
https://hbfs.wordpress.com/2008/08/26/optimizing-boolean-expressions-for-speed/