聪明的if / else结构

时间:2017-01-14 11:09:24

标签: c++ if-statement

我有三个变量,如果所有变量都低于某个x,我想做点什么。如果他们都在上面,我想做别的事情,我也想做第三件事,如果有些变量在下面,有些变量在上面。

现在我想以最有效的方式做到这一点。当然,我可以先检查是否所有这些都在上面,如果没有,检查一下,如果所有这些都在下面......

但是有没有更有效的方法,我没有认识到?

感谢大家和女孩们的意见。

4 个答案:

答案 0 :(得分:4)

  

现在我想以最有效的方式做到这一点。当然,我可以先检查是否所有这些都在上面,如果没有,检查一下,如果所有这些都在下面......

写出富有表现力的代码

  

但是有没有更有效的方式,我不认识?

你不需要。优化器会为您识别它。

如果它看到对不变量的重新测试,它将寻求为您重新排序代码(它将成功)。

始终编写具有逻辑意义的富有表现力的代码。我已经看到gcc和clang的优化器将数百行c ++代码(表达的意图)转换成一个立即寄存器加载,因为编译器意识到只能有一个结果。

这是一个例子(c ++ 14):

#include <cstdint>
#include <cstddef>
#include <utility>
#include <type_traits>

//
// library boilerplate
//
namespace detail {
  template<class Bool>
  constexpr bool all_of(Bool&& b) {
    return b;
  }

  template<class Bool1, class...Rest>
  constexpr bool all_of(Bool1&& b1, Rest&&... rest) {
    return all_of(std::forward<Bool1>(b1)) and all_of(std::forward<Rest>(rest)...);
  }

  template<class Bool>
  constexpr bool none_of(Bool&& b) {
    return not b;
  }

  template<class Bool1, class...Rest>
  constexpr bool none_of(Bool1&& b1, Rest&&... rest) {
    return none_of(std::forward<Bool1>(b1)) and none_of(std::forward<Rest>(rest)...);
  }

}

template<class...Bools>
constexpr bool all_of(Bools&&... bs)
{
  return detail::all_of(std::forward<Bools>(bs)...);
}

template<class...Bools>
constexpr bool none_of(Bools&&... bs)
{
  return detail::none_of(std::forward<Bools>(bs)...);
}

//
// external functions
//

void doX();
void doY();
void doZ();

bool testA();
bool testB();
bool testC();

//
// a test
//    
void test()
{
  const bool a = testA(), b = testB(), c = testC();
  if (all_of(a, b, c)) {
    doX();
  }
  else if (none_of(a, b, c)) {
    doZ();
  }
  else {
    doY();
  }
}

GCC将此解析为以下汇编语言:

test():                               # @test()
        push    rbp
        push    rbx
        push    rax
        call    testA()
        mov     ebp, eax
        call    testB()
        mov     ebx, eax
        call    testC()
        test    bpl, bpl
        je      .LBB0_3
        and     bl, al
        cmp     bl, 1
        jne     .LBB0_5
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doX()                 # TAILCALL
.LBB0_3:
        or      bl, al
        je      .LBB0_4
.LBB0_5:
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doY()                 # TAILCALL
.LBB0_4:
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doZ()                 # TAILCALL

注意编译器如何使用ax,bx和ebp寄存器来缓存测试的状态。另请注意,在程序集输出中的任何位置都完全没有调用any_ofall_of

我们当然可以这样写:

void test2()
{
  const bool a = testA(), b = testB(), c = testC();
  if (a and b and c) {
    doX();
  }
  else if (not (a or b or c)) {
    doZ();
  }
  else {
    doY();
  }
}

结果是产生相同的组装:

test2():                              # @test2()
        push    rbp
        push    rbx
        push    rax
        call    testA()
        mov     ebx, eax
        call    testB()
        mov     ebp, eax
        call    testC()
        test    bl, bl
        je      .LBB1_3
        test    bpl, bpl
        je      .LBB1_3
        test    al, al
        je      .LBB1_3
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doX()                 # TAILCALL
.LBB1_3:
        or      bl, bpl
        add     rsp, 8
        or      bl, al
        je      .LBB1_6
        pop     rbx
        pop     rbp
        jmp     doY()                 # TAILCALL
.LBB1_6:
        pop     rbx
        pop     rbp
        jmp     doZ()                 # TAILCALL
我们甚至可以写下这个:

void test3()
{
  const bool a = testA(), b = testB(), c = testC();
  if (a) {
    if (b) {
      if (c) {
        doX();
      }
      else {
        doY();
      }
    }
    else {
      doY();
    }
  }
  else {
    if (b) {
      doY();
    }
    else {
      if (c) {
        doY();
      }
      else {
        doZ();
      }
    }
  } 
}

产生几乎相同的代码:

test3():                              # @test3()
        push    rbp
        push    rbx
        push    rax
        call    testA()
        mov     ebx, eax
        call    testB()
        mov     ebp, eax
        call    testC()
        test    bl, bl
        je      .LBB2_4
        test    bpl, bpl
        je      .LBB2_3
        test    al, al
        je      .LBB2_3
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doX()                 # TAILCALL
.LBB2_4:
        test    bpl, bpl
        jne     .LBB2_3
        test    al, al
        je      .LBB2_6
.LBB2_3:
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doY()                 # TAILCALL
.LBB2_6:
        add     rsp, 8
        pop     rbx
        pop     rbp
        jmp     doZ()                 # TAILCALL

演示:https://godbolt.org/g/j4q1ke

这个故事的寓意与所有c ++故事的道德观点相同:“正确,清晰,优雅,简洁地表达自己。然后让优化者做好自己的工作。”

gcc,msvc,intel和clang优化器已经被成千上万的人所吸引,他们比我(也可能是你)更聪明。我们手写的“优化”源代码不太可能做得更好。此外,这些编译器的未来迭代将更加出色。因此,即使您从未改进自己的代码,只要您接受更新版本的编译器,它就会自动改进。

所以我猜这个故事的第二个道理是(与许多软件经理的信念相反......),“经常升级你的编译器。”

最后的说明:

我们甚至可以变得非常先进并使用元组来保存条件的结果。这意味着我们正在测试的条件集是DRY(这很好!)。这也将在gcc上生成相同的代码:

namespace detail {
  template<class Tup, std::size_t...Is>
  bool none_of(Tup&& tup, std::index_sequence<Is...>)
  {
    bool result = true;
    using unwrap = int[];
    void(unwrap{0,
                (result = result and not std::get<Is>(tup),0)...
               });
    return result;
  }

  template<class Tup, std::size_t...Is>
  bool all_of(Tup&& tup, std::index_sequence<Is...>)
  {
    bool result = true;
    using unwrap = int[];
    void(unwrap{0,
                (result = result and std::get<Is>(tup),0)...
               });
    return result;
  }
}

template<class...Bools>
constexpr bool none_of(std::tuple<Bools...> const& tup)
{
  constexpr auto N = std::tuple_size<std::tuple<Bools...>>::value;
  return detail::none_of(tup, std::make_index_sequence<N>());
}

template<class...Bools>
constexpr bool all_of(std::tuple<Bools...> const& tup)
{
  constexpr auto N = std::tuple_size<std::tuple<Bools...>>::value;
  return detail::all_of(tup, std::make_index_sequence<N>());
}

void test4()
{
  // note, the list of conditions is now expressed only once.
  // suddenly our code is trivial to maintain
  const auto conditions = std::make_tuple(testA(), testB(), testC());

  // all_of has obvious meaning, regardless of how many conditions are in the tuple
  if (all_of(conditions)) {
    doX();
  }
  // ditto for none_of
  else if (none_of(conditions)) {
    doZ();
  }
  else {
    doY();
  }
}

答案 1 :(得分:3)

假设v1,v2和v3是你的值:

switch((v1 < x) + (v2 < x) + (v3 < x))
{
   case 0: // none is smaller
   break;

   case 3: // all are smaller
   break;

   default: // some, but not all are smaller
   break;
}

我担心这段代码不如明确的if / else继承明确,而且可能不会更快,但你要求它; - )。

评论后的结语:

跳转总是会减慢程序流,因为CPU必须刷新执行流水线。几乎可以保证更快,更常见的是以避免完全跳过并做(甚至代价高昂的)计算。对于一个简单且非常适合的示例,我们假设您要为变量分配一个值:比如销售人员的奖金与销售额超过x的区域数量成比例。以下肯定会比任何ifswitch构造快得多:

auto bonus = regionBonus * ((v1 >= x) + (v2 >= x) + (v3 >= x));

这将导致每个符合条件的地区的奖金支付,并且在没有资格的极端情况下为0。在我看来,此代码需要发表评论。像这样的应用程序(可能使用数据库值,不超过几百名销售人员)也不太可能成为有价值的优化目标。但是如果你试图在大型强子对撞机的内环中从万亿事件中清除不重要的事件,那么你会对提高速度感到高兴。

答案 2 :(得分:2)

使用检查结果制作三个布尔变量。现在,您可以使用&&检查这三个是否属实,并且使用false检查这三个是||

bool aAbove = a > aMin;
bool bAbove = b > bMin;
bool cAbove = c > cMin;
if (aAbove && bAbove && cAbove) {
    // All above
} else if (!(aAbove || bAbove || cAbove)) {
    // All below
} else {
    // Mixed case
}

当所有值都准备好进行比较时,这在效率方面应该没问题。如果需要时间来获取这些值,您可以在50%的情况下节省第三次比较,但代价是使代码的可读性降低:

bool aAbove = computeA() > aMin;
bool bAbove = computeB() > bMin;
if ((aAbove == bAbove) && aAbove == (computeC() > cMin)) {
    if (aAbove) {
        // All above
    } else {
        // All below
    }
} else {
    // Mixed case
}

请注意,仅当前两次比较产生相同结果时才进行第三次计算和比较。如果他们的结果是混合的,那么整体结果也会混合,所以第三次比较是无关紧要的。

答案 3 :(得分:0)

我不知道它是更有效还是更快,但它在内存空间上是保守的,但是这种类型的编程需要非常谨慎,因为它将取决于endian中机器的体系结构存储数字,但你可以写一个函数,做一个位掩码或位字段,如这个小程序:

#include <iostream>

struct mask {
    // Each Bit Mask needs two bits to store at least 3 possible answers.
    // { 00 = 0, 01 = <, 10 = >, 11 ignored except in the output }
    unsigned char first  : 2;   // -- -- -- **  These are the bits in the mask
    unsigned char second : 2;   // -- -- ** --              
    unsigned char third  : 2;   // -- ** -- --    
    unsigned char output : 2;   // ** -- -- --
};

int getValComparison( unsigned value, unsigned factor ) {
    if ( value > factor ) {
        return 2;
    } else if ( value < factor ) {
        return 1;
    } else if ( value == factor ) {
        return 0;
    }
}

mask setMask( unsigned value1, unsigned value2, unsigned value3, unsigned factor ) {

    mask a;
    a.first  = getValComparison( value1, factor );
    a.second = getValComparison( value2, factor );
    a.third  = getValComparison( value3, factor );

    // Determine the Output Value.
    if ( (a.first == 2) && (a.second == 2) && (a.third == 2) ) {
        // All are 2 or high
        a.output = 2;
    } else if ( (a.first == 1) && (a.second == 1) && (a.third == 1) ) {
        a.output = 1;
    } else if ( (a.first == 0) && (a.second == 0) && (a.third == 0) ) {
        a.output = 0;
    } else {
        a.output = 3;
    }

    return a;
}


int void main () {

    int x,y,z, factor;
    x = 7;
    y = 7;
    z = 7;
    factor = 7;

    mask m = setMask( x, y, z, factor );

    if ( m.output == 3 ) {
        std::cout << "Mixed Cases: " << std::endl;
    } else if ( m.output == 2 ) {
        std::cout << "All are greater than factor " << std::endl;
    } else if ( m.output == 1 ) {
        std::cout << "All are less than factor " << std::endl;
    } else {
        std::cout << "All are equal to factor " << std::endl;
    }

    return 0;
}

您所要做的就是将x,y,z的值更改为不同的组合,以查看它们是否都大于,全部小于,全部等于或混合的情况。

现在如果您不需要跟踪位域中的比较决策,可以使用较小的掩码进行跟踪,并将函数略微改为:

struct mask2 {
    unsigned char first  : 2;
    unsigned char second : 2;
    unsigned char third  : 2;
};

int getValComparison( unsigned value, unsigned factor ) {
    if ( value > factor ) {
        return 2;
    } else if ( value < factor ) {
        return 1;
    } else if ( value == factor ) {
        return 0;
    }
    return -1; // Some Unknown error here.
}

unsigned setMask2( unsigned value1, unsigned value2, unsigned value3, unsigned factor ) {
    mask a;
    a.first  = getValComparison( value1, factor );
    a.second = getValComparison( value2, factor );
    a.third  = getValComparison( value3, factor );

    // Determine the Output Value.
    if ( (a.first == 2) && (a.second == 2) && (a.third == 2) ) {
        // All are 2 or high
        return 2;
    } else if ( (a.first == 1) && (a.second == 1) && (a.third == 1) ) {
        return 1;
    } else if ( (a.first == 0) && (a.second == 0) && (a.third == 0) ) {
        return 0;
    } else {
        return 3;
    }
}

int main () {
    int x,y,z, factor;
    x = 7;
    y = 7;
    z = 7;
    factor = 7;

    unsigned decision = setMask2( x, y, z, factor );
    if ( decision == 3 ) {
        std::cout << "Mixed Cases: " << std::endl;
    } else if ( decision == 2 ) {
        std::cout << "All are greater than factor " << std::endl;
    } else if ( decision == 1 ) {
        std::cout << "All are less than factor " << std::endl;
    } else {
        std::cout << "All are equal to factor " << std::endl;
    }

    return 0;
}

但是当涉及到位域和工会时,您必须了解架构以及不同的处理器和操作系统如何在其数据类型中存储这些位。有些是Little Endian,有些是Big Endian。你还有一些复杂的存在签名整数的存在,比如它们的编码,比如两个恭维和其他。但这对您有效。