3

假设我有这样的伪代码:

main() {
    BOOL b = get_bool_from_environment(); //get it from a file, network, registry, whatever
    while(true) {
        do_stuff(b);
    }
}

do_stuff(BOOL b) {
    if(b)
        path_a();
    else
        path_b();
}

现在,既然我们知道外部环境可以影响可能get_bool_from_environment()产生真假结果,那么我们知道真假分支的代码必须包含在二进制文件中。我们不能简单地省略或从代码中删除。if(b)path_a();path_b();

但是——我们只设置BOOL b了一次,并且我们总是在程序初始化后重用相同的值。

如果我要制作这个有效的 C 代码,然后使用 编译它gcc -O0if(b)那么每次do_stuff(b)调用时都会在处理器上重复评估,在我看来,这会将不必要的指令插入管道中,用于基本上是静态的分支初始化后。

如果我假设我实际上有一个像 一样愚蠢的编译器,我会gcc -O0重写这段代码以包含一个函数指针和两个单独的函数do_stuff_a()和执行两条路径之一。然后,在 中,我将根据 的值分配函数指针,并在循环中调用该函数。这消除了分支,尽管它确实为函数指针取消引用添加了内存访问(由于架构实现,我认为我真的不需要担心)。do_stuff_b()if(b)main()b

即使在原则上,编译器是否有可能采用与原始伪代码示例相同样式的代码,并意识到一旦在 中b分配了一次值,就不需要进行测试main()?如果是这样,这个编译器优化的理论名称是什么,你能否举一个实际的编译器实现(开源或其他)的例子?

我意识到编译器不能在运行时生成动态代码,原则上唯一可以做到这一点的系统类型是字节码虚拟机或解释器(例如 Java、.NET、Ruby 等)——所以问题仍然存在是否有可能静态地执行此操作并生成包含path_a();分支和path_b()分支的代码,但避免为每次调用评估条件测试if(b)do_stuff(b);

4

1 回答 1

3

如果您告诉编译器进行优化,则很有可能if(b)只评估一次。

稍微修改给定的示例,使用标准_Bool而不是BOOL,并添加缺少的返回类型和声明,

_Bool get_bool_from_environment(void);
void path_a(void);
void path_b(void);

void do_stuff(_Bool b) {
    if(b)
        path_a();
    else
        path_b();
}

int main(void) {
    _Bool b = get_bool_from_environment(); //get it from a file, network, registry, whatever
    while(1) {
        do_stuff(b);
    }
}

clang -O3[clang-3.0]生产的程序集的(相关部分)是

    callq   get_bool_from_environment
    cmpb    $1, %al
    jne     .LBB1_2
    .align  16, 0x90
.LBB1_1:                                # %do_stuff.exit.backedge.us
                                        # =>This Inner Loop Header: Depth=1
    callq   path_a
    jmp     .LBB1_1
    .align  16, 0x90
.LBB1_2:                                # %do_stuff.exit.backedge
                                        # =>This Inner Loop Header: Depth=1
    callq   path_b
    jmp     .LBB1_2

b仅测试一次,然后根据 的值跳入或main进入无限循环。如果和足够小,它们将被内联(我强烈期望)。使用and ,由 clang 生成的代码将在循环的每次迭代中进行评估。path_apath_bbpath_apath_b-O-O2b

gcc (4.6.2) 的行为与以下类似-O3

    call    get_bool_from_environment
    testb   %al, %al
    jne     .L8
    .p2align 4,,10
    .p2align 3
.L9:
    call    path_b
    .p2align 4,,6
    jmp     .L9
.L8:
    .p2align 4,,8
    call    path_a
    .p2align 4,,8
    call    path_a
    .p2align 4,,5
    jmp     .L8

奇怪的是,它展开了 for 的循环path_a,但没有展开 for path_b。使用-O2or -O,它会调用do_stuff无限循环。

因此

即使在原则上,编译器是否有可能采用与原始伪代码示例相同样式的代码,并意识到一旦在 中b分配了一次值,就不需要进行测试main()

答案是肯定的 是的,编译器有可能认识到这一点并利用这一事实。好的编译器在被要求进行优化时会这样做。

如果是这样,这个编译器优化的理论名称是什么,你能否举一个实际的编译器实现(开源或其他)的例子?

我不知道优化的名称,但这样做的两个实现是 gcc 和 clang(至少,足够新的版本)。

于 2013-01-15T19:56:11.057 回答