【问题标题】:Compile-time with mixed const/non-const ternary operator使用混合 const/non-const 三元运算符的编译时
【发布时间】:2012-09-17 12:56:47
【问题描述】:

考虑以下示例:

template<int X> class MyClass
{
    public:
        MyClass(int x) {_ncx = x;}
        void test() 
        {
            for (unsigned int i = 0; i < 1000000; ++i) {
                if ((X < 0) ? (_cx > 5) : (_ncx > 5)) {
                    /* SOMETHING */
                } else {
                    /* SOMETHING */
                }
            }
        }
    protected:
        static const int _cx = (X < 0) ? (-X) : (X);
        int _ncx;
};

我的问题是:MyClass::test() 和 MyClass::test() 有不同的速度吗?

我希望如此,因为在模板参数为负的情况下,可以在编译时评估测试函数中的if,但我不确定如果存在编译时编译器的行为是什么三元运算符中的事物和非编译时事物(这里就是这种情况)。

注意:这是一个纯粹的“理论”问题。如果有“是”的非空概率,我将为我的代码实现一些具有此类编译时模板参数的类,如果没有,我将只提供运行时版本。

【问题讨论】:

标签: c++ templates optimization ternary-operator compile-time


【解决方案1】:

对于我的编译器(OS X 上的 clang++ v2.9)编译这个相似但不相同的代码:

void foo();
void bar();

template<int N>
void do_something( int arg ) {
  if ( N<0 && arg<0 ) { foo(); }
  else { bar(); }
}

// Some functions to instantiate the templates.
void one_fn(int arg) {
  do_something<1>(arg);
}

void neg_one_fn(int arg) {
  do_something<-1>(arg);
}

这会生成以下带有clang++ -S -O3 的程序集。

one_fn = do_something

第一个函数程序集显然只有对bar的调用。

    .globl  __Z6one_fni
    .align  4, 0x90
__Z6one_fni:                            ## @_Z6one_fni
Leh_func_begin0:
    pushl   %ebp
    movl    %esp, %ebp
    popl    %ebp
    jmp __Z3barv                ## TAILCALL
Leh_func_end0:

neg_one_fn = do_something

第二个函数已简化为一个简单的 if 调用 barfoo

    .globl  __Z10neg_one_fni
    .align  4, 0x90
__Z10neg_one_fni:                       ## @_Z10neg_one_fni
Leh_func_begin1:
    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $0, 8(%ebp)
    jns LBB1_2                  ## %if.else.i
    popl    %ebp
    jmp __Z3foov                ## TAILCALL
LBB1_2:                                 ## %if.else.i
    popl    %ebp
    jmp __Z3barv                ## TAILCALL
Leh_func_end1:

总结

因此您可以看到编译器内联了模板,然后在可能的情况下优化了分支。因此,您希望的那种转换确实发生在当前的编译器中。我也从旧的 g++ 4.0.1 编译器得到了类似的结果(但汇编不太清楚)。

附录:

我认为这个例子与你最初的例子不太相似(因为它不涉及三元运算符)所以我把它改成这样:(得到相同的结果)

template<int X>
void do_something_else( int _ncx ) {
  static const int _cx = (X<0) ? (-X) : (X);
  if ( (X < 0) ? (_cx > 5) : (_ncx > 5)) {
    foo();
  } else {
    bar();
  }
}

void a(int arg) {
  do_something_else<1>(arg);
}

void b(int arg) {
  do_something_else<-1>(arg);
}

这会生成程序集

a() = do_something_else

这仍然包含分支。

__Z1ai:                                 ## @_Z1ai
Leh_func_begin2:
    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $6, 8(%ebp)
    jl  LBB2_2                  ## %if.then.i
    popl    %ebp
    jmp __Z3foov                ## TAILCALL
LBB2_2:                                 ## %if.else.i
    popl    %ebp
    jmp __Z3barv                ## TAILCALL
Leh_func_end2:

b() = do_something_else

分支被优化掉。

__Z1bi:                                 ## @_Z1bi
Leh_func_begin3:
    pushl   %ebp
    movl    %esp, %ebp
    popl    %ebp
    jmp __Z3barv                ## TAILCALL
Leh_func_end3:

【讨论】:

    【解决方案2】:

    将条件移出循环:

            ...
            if ((X < 0) ? (_cx > 5) : (_ncx > 5)) {
                for (unsigned int i = 0; i < 1000000; ++i) {
                    /* SOMETHING */
                }
            } else {
                for (unsigned int i = 0; i < 1000000; ++i) {
                    /* SOMETHING */
                }
            }
            ...
    

    这样您就不必依赖编译器优化来删除未使用的代码;如果编译器没有删除条件的未使用部分,您只需为条件分支支付一次费用,而不是每次循环。

    【讨论】:

    • 当然,但这只是说明“理论”问题的示例。我要优化的函数要复杂得多,我需要知道编译器是否可以优化 const/non-const 三元运算符。
    • 我提供了一个实用的答案,如果最重要的速度和“取决于编译器”响应是不可接受的,接下来要做的是避免依赖编译器来执行此操作。标准中没有强制或限制编译器如何对代码进行优化,只要它产生正确的输出。
    • 既然如此,理论上的答案是:在这个例子中,编译器可能会首先弥补工程师在放置条件时的设计缺陷。仅对生成的 ASM 进行详细检查就足以确定更复杂的功能的上述情况。
    【解决方案3】:

    这可能取决于你的编译器有多聪明。我建议您编写一个小基准程序,在您的环境中自己进行测试,以确定是否正确。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-07
      • 2021-07-03
      • 1970-01-01
      • 2019-09-03
      • 2020-12-23
      相关资源
      最近更新 更多