【问题标题】:Compiler predicate optimizations编译器谓词优化
【发布时间】:2017-08-04 01:04:57
【问题描述】:

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

  1. x > 10 and x > 20
  2. (x > 10 or x == 10) and (x < 10 or x == 10)又名x >= 10 and x <= 10

谓词1.可以简化为x > 20,2.可以简化为x == 10。编译器会优化这种(或更复杂的)谓词吗?如果是的话,使用什么算法来优化?

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

【问题讨论】:

  • 我怀疑大多数编译器不会有这些优化。编译器编写者需要决定在哪些功能上投入时间,而这似乎不是花费大量时间的最佳场所。为什么?因为对于开发人员来说编写可读的最佳代码是如此微不足道,以至于这个编译器功能实际需要做某事的概率在我看来似乎很低。但这只是我的猜测。

标签: performance compiler-optimization predicate


【解决方案1】:

这取决于编译器,但 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('instcombine')。您可以在InstructionSimplify.cpp 源代码中看到转换。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 您能否详细说明如何将布尔表达式的优化应用于关系运算符?我想您可以将表达式扩展为按位加法器链等,但是对于两个 32 位输入所产生的组合爆炸似乎会导致直接评估结果真值表在没有改进算法的情况下是不可行的。
    猜你喜欢
    • 2022-01-01
    • 2015-09-30
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 2011-11-07
    • 2014-02-21
    • 2011-08-24
    相关资源
    最近更新 更多