编译器是否会对空函数体进行优化?(gcc)

6

使用基于策略的设计,一个EncapsulatedAlgorithm

template< typename Policy>
class EncapsulatedAlgorithm : public Policy
{
    double x = 0; 

    public: 
        using Policy::subCalculate; 

        void calculate()
        {
            Policy::subCalculate(x); 
        }
    protected:
        ~EncapsulatedAlgorithm() = default;
};

可能有一个执行子计算的 策略。这个子计算对于算法来说不是必要的:它只是在某些情况下用于加速算法收敛。因此,为了模拟这种情况,假设有三种策略。

其中一种只是“记录”一些东西:

struct log
{
    static void subCalculate(double& x)
    {
        std::cout << "Doing the calculation" << endl; 
    }
};

计算机:

struct calculate
{
    static void subCalculate(double& x)
    {
        x = x * x; 
    }
};

有一个将它们聚集在一起并将它们束缚在黑暗中的人:D - 这完全没有任何作用:

struct doNothing
{
    static void subCalculate(double& x)
    {
        // Do nothing. 
    }
};

这是示例程序:
typedef EncapsulatedAlgorithm<doNothing> nothingDone; 
typedef EncapsulatedAlgorithm<calculate> calculationDone; 
typedef EncapsulatedAlgorithm<loggedCalculation>  calculationLogged; 

int main(int argc, const char *argv[])
{
    nothingDone n; 
    n.calculate(); 

    calculationDone c; 
    c.calculate();

    calculationLogged l; 
    l.calculate(); 

    return 0;
}

这里有一个实时的例子(点击跳转)。我试着检查带优化的gcc生成的汇编代码:

g++ -S -O3 -std=c++11 main.cpp

但我对汇编语言不够了解,无法确定结果的解释-生成的文件很小,我无法识别函数调用,因为所有策略的静态函数的代码都是内联的。

我能看到的是,在没有针对主函数进行优化的情况下,在主函数中存在一个与“doNothing :: subCalculate”相关的call和随后的leave

call    _ZN9doNothing12subCalculateERd
leave

以下是我的问题:

  1. 我应该从何处开始学习才能够读懂 g++ -S 的输出?
  2. 空函数是否被优化掉了?在 main.s 中的哪些行中可以看到?
  3. 这个设计是否可行?通常来说,实现一个什么都不做的函数是一件坏事,因为接口所表达的意思完全不同于实际情况(例如,subCalculate 而不是 doNothing),但是对于策略模式来说,策略名称清楚地表明该函数不会执行任何操作。否则,我需要进行类型特征处理,例如使用 enable_if 等,只是为了排除一个单独的函数调用。

首先删除 iostream 以获得最小的输出。如果我这样做并使用 -O3 编译示例且没有调试信息,它就变成了 return 0:即 xorl %eax, %eax,该语句只是通过将 eax 寄存器与自身异或来将 eax 寄存器设置为 0 - pmr
如果编译器内联一个函数,而该函数为空,则实际上没有更多可以优化的内容,因此在调用的优化阶段中,无操作将很好地完成其余部分的优化。 如果不允许内联,调用将不会被优化掉。 因此,这个问题大致等同于:编译器是否会做一些极其愚蠢的事情,而这些事情本应在测试版中被捕捉和消除?答案是“有时候” :( - n. m.
你可以使用 -fdump-tree-all 生成中间 Gimple dumps ...(它会输出数百个文件,但其中一些非常易读,具有类似 C 的语法)。另请参阅 MELT - Basile Starynkevitch
2个回答

5

我访问了http://assembly.ynh.io/,这个网站展示了汇编输出。我

template< typename Policy>
struct EncapsulatedAlgorithm : public Policy
{
        void calculate(double& x)
        {
            Policy::subCalculate(x); 
        }
};
struct doNothing
{
    static void subCalculate(double& x)
    {
    }
};
void func(double& x) {
   EncapsulatedAlgorithm<doNothing> a;
   a.calculate(x);
}

并获得了以下结果:

            .Ltext0:
                .globl  _Z4funcRd 
            _Z4funcRd:
            .LFB2:
                .cfi_startproc    #void func(double& x) {
            .LVL0:
0000 F3             rep           #not sure what this is
0001 C3             ret           #}
                .cfi_endproc
            .LFE2:
            .Letext0:

嗯,我只在汇编代码中看到了两个操作码。一个是rep(不知道是什么),另一个是结束函数的指令。这意味着G++编译器可以轻松地优化掉函数体。


3

如何开始学习以便能够阅读g++ -S输出的内容?

本站不提供推荐阅读材料。请在Google中搜索" x86汇编语言 "。

空函数是否被优化掉了,它在main.s中的哪些行?

在启用优化器时,它将被优化掉,因此在生成的.S文件中不会有任何行。您已经在未优化的输出中找到了调用....

实际上,甚至执行乘法的策略也可能被删除,因为编译器应该能够计算出您没有使用结果值。添加代码以打印x的值,并从一些在编译时无法知道的值中设置x(在像这样的小实验程序中使用argc通常很方便,然后您将强制编译器至少保留具有功能意义的代码。)

这个设计是否可以?

这取决于很多事情(例如,是否要使用模板,因为实现需要在头文件中公开,是否要处理每个实例的不同类型等),但是您正在正确地实现设计。

通常,实现一个什么都不做的函数是一件坏事,因为接口完全说明了不同的事情(subCalculate而不是doNothing),但在策略的情况下,策略名称清楚地表明该函数不会做任何事情。否则,我需要进行类型特征处理,如enable_if等,只排除一个函数调用。

您可能需要仔细考虑函数名称... do_any_necessary_calculations()ensure_exclusivity()代替lock_mutex()after_each_value()代替print_breaks等等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接