编译器谓词优化

时间:2017-08-04 01:04:58

标签: performance compiler-optimization predicate

考虑以下示例条件/谓词:

  1. x > 10 and x > 20
  2. (x > 10 or x == 10) and (x < 10 or x == 10)又名x >= 10 and x <= 10
  3. 谓词1.可以简化为x > 20,2可以简化为x == 10。编译器是否会优化这种(或更复杂的)谓词,如果是,那么使用什么算法呢?

    谓词有哪些常见的优化技术?

2 个答案:

答案 0 :(得分:2)

这取决于编译器,但是clang和gcc会执行此优化:

#include <stdio.h>

void foo(int x) {
  if (x > 10 && x > 20)
    puts("foo");
}

void foo2(int x) {
  if ((x > 10 || x == 10) && (x < 10 || x == 10))
    puts("foo2");
}

你可以see the assembly here - 两个函数都包含一个比较。

对于clang(使用LLVM),它使用instruction combine pass(&#39; instcombine&#39;)。您可以在InstructionSimplify.cpp源代码中看到转换。

答案 1 :(得分:1)

查看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

有趣的是,在这两种情况下,新的JIT几乎同时运行opt和非opt X64版本。

问题仍然是:为什么编译器不优化这些模式?我的猜测是因为运算符重载之类的东西使得编译器不可能推断出一些正确的逻辑结论,但是II可能非常关闭...而且,对于内置值类型,它应该是可能的。哦,好吧......

最后,这里有一个关于布尔表达式优化的好文章: https://hbfs.wordpress.com/2008/08/26/optimizing-boolean-expressions-for-speed/