【问题标题】:Logical / Relational Expression Optimization逻辑/关系表达式优化
【发布时间】:2013-02-25 15:52:30
【问题描述】:

我需要优化一个表单的表达式:

(a > b) || (a > c)

我尝试了几种优化形式,其中一种如下:

(a * 2) > (b + c)

优化不是从编译器的角度来看的。我想将两个 > 减为一个。

这是基于 1 的假设

但是,这仅适用于某些情况。我正在尝试做的优化真的可能吗?如果是的话,开始会很有帮助。

【问题讨论】:

  • 我怀疑 (a * 2) > (b + c) 实际上更优化,至少如果我正确理解您想要提高执行速度的话。
  • 因为没有可以防止短路的副作用,编译器可能已经将其优化为单个分​​支——将逻辑或转换为按位或。
  • 让a=5, b=2, c=20; OP 的第二个表达式在我看来不像是第一个表达式的优化。
  • 所以,你只需要知道a是否大于b和c中的较小者。 (a > min(b, c)) 编译成什么?
  • 我会用这个:a > max(b,c)

标签: c++ c expression logical-operators relational


【解决方案1】:

答案可能是:您不想优化它。此外,我怀疑是否有任何方法可以更有效地编写此代码。如果你说 a、b 和 c 是 1 到 26 之间的值,那么如果你想要优化(在大小上),你就不应该使用整数(你不需要那个精度)。

如果 a > b,表达式 a > c 无论如何都不会被执行。所以你最多有 2 个(至少 1 个)条件操作,这真的不值得优化。

【讨论】:

    【解决方案2】:

    我很怀疑在大多数情况下这甚至是一种优化。

     a > b || a > c 
    

    将评估为:

     compare a b
     jump not greater
     compare a c
     jump not greater
    

    在哪里

     a * 2 > b + c
    

    给予:

     shift a left 1 (in temp1)
     add b to c (in temp2)
     compare temp1 temp2
     jump if not greater
    

    与性能一样,根据实际性能测量(最好是选择的处理器架构)做出决定总是要好得多。

    【讨论】:

    • 忽略这种级别的推理是危险和困难的——而且几乎总是毫无意义的引导——我会说这些 sn-ps 的相对性能在很大程度上取决于。后者可能更容易流水线化并且对分支预测器更好。
    • 在大多数情况下,性能差异根本不重要,或者通过对特定用例进行基准测试更容易、更准确地确定。
    • 它并不总是有效。如果您将有 a = 10、b = 100 和 c = 7,则表达式 a > b || a > c 将返回真。而您的 (10*2 > 100 + 7) 将返回 false。如果我错了,请原谅我 =)
    【解决方案3】:

    我能想到的最好的就是这个

    char a, b, c;
    std::cin >> a >> b >> c;
    
    if (((b-a) | (c-a)) & 0x80) {
        // a > b || a > c
    }
    

    gcc -O2 只生成一个条件分支

    40072e:       29 c8                   sub    %ecx,%eax
    400730:       29 ca                   sub    %ecx,%edx
    400732:       09 d0                   or     %edx,%eax
    400734:       a8 80                   test   $0x80,%al
    400736:       74 17                   je     40074f <main+0x3f>
    

    这利用了输入值的约束,因为这些值不能大于 26,所以当 a &gt; b 时,从 b 中减去 a 会给你一个负值,在二进制补码中你知道位 7 会在这种情况下设置 - 这同样适用于 c。然后我 OR 两者,以便位 7 指示是否为 a &gt; b || a &gt; c,最后我们通过 AND 检查位 7 与 0x80 并在其上进行分支。

    更新:出于好奇,我对 4 种不同的编码方式进行了计时。为了生成测试数据,我使用了一个简单的线性同余伪随机数生成器。我在一个循环中对它进行了 1 亿次迭代。为简单起见,我假设如果条件为真,我们希望将 5 添加到计数器,否则什么也不做。我使用g++ (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2) 在Intel Xeon X5570 @ 2.93GHz 上使用-O2 优化级别对其进行计时。

    这是代码(注释掉除一个条件变量之外的所有变量):

    #include <iostream>
    unsigned myrand() {
        static unsigned x = 1;
        return (x = x * 1664525 + 1013904223);
    }
    
    int main() {
        size_t count = 0;
        for(size_t i=0; i<100000000; ++i ) {
            int a = 1 + myrand() % 26;
            int b = 1 + myrand() % 26;
            int c = 1 + myrand() % 26;
    
            count += 5 & (((b-a) | (c-a)) >> 31);       // 0.635 sec
            //if (((b-a) | (c-a)) & 0x80) count += 5;     // 0.660 sec
            //if (a > std::max(b,c)) count += 5;          // 0.677 sec
            //if ( a > b || a > c) count += 5;            // 1.164 sec
        }
        std::cout << count << std::endl;
        return 0;
    }
    

    最快的是修改我的回答中的建议,我们使用符号扩展来生成一个掩码,它是 32 1s 或 32 0s,具体取决于条件是真还是假,并使用它来屏蔽正在添加的5,使其添加 5 或 0。此变体没有分支。时间在每一行的注释中。最慢的是原始表达式( a &gt; b || a &gt; c)。

    【讨论】:

    • 您应该确保编译器没有为您执行此操作。首先让您的代码保持简单,然后仅在您可以识别瓶颈时进行微优化。
    猜你喜欢
    • 2015-03-26
    • 2011-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-13
    • 1970-01-01
    相关资源
    最近更新 更多