【问题标题】:Does the (gcc) compiler optimize away empty-body functions?(gcc) 编译器是否优化了空体函数?
【发布时间】:2014-02-20 16:36:41
【问题描述】:

使用基于策略的设计,EncapsulatedAlgorithm

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

    public: 
        using Policy::subCalculate; 

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

可能有一个执行子计算的策略Policy。算法不需要子计算:在某些情况下可以使用它来加速算法收敛。所以,为了建模,假设有三个策略。

一个只是“记录”一些东西的人:

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;
}

here 就是一个活生生的例子。我尝试在打开优化的情况下检查gcc 生成的汇编代码:

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

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

我可以看到,当没有为 main 函数设置优化时,有一个 call 和一个与 'doNothing::subCalculate' 相关的后续 leave

call    _ZN9doNothing12subCalculateERd
leave

这是我的问题:

  1. 我从哪里开始学习才能阅读g++ -S 喷出的内容?
  2. 空函数是否已优化,main.s 中的哪些行?
  3. 这种设计可以吗?通常,实现一个什么都不做的函数是一件坏事,因为接口说的是完全不同的东西(subCalculate 而不是doNothing),但在策略的情况下,策略名称清楚地表明该函数不会做任何事物。否则我需要做类型特征的东西,比如enable_if,等等,只是为了排除一个函数调用。

【问题讨论】:

  • 首先删除iostream 以获得最少的输出。如果我这样做并使用 -O3 编译示例并且没有调试信息,它实际上会减少到 return 0: xorl %eax, %eax ,它只是将 eax 寄存器设置为 0 通过对自身进行异或运算。
  • 这个问题没有实质内容。如果编译器内联了一个函数,并且它是空的,那么实际上没有什么可以进一步优化的,所以在这个调用的其余优化阶段,no-op 会很好地完成。如果不允许内联,则不会优化调用。因此问题大致相当于:编译器会做一些本应在 beta 测试中发现并消除的极其愚蠢的事情吗?答案是“有时”:(
  • 您可以使用带有-fdump-tree-all 的中间 Gimple 转储...(它会吐出数百个文件,但有些文件非常易读,具有类似 C 的语法)。另见MELT

标签: c++ gcc optimization


【解决方案1】:

我去了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++ 编译器可以轻松优化出函数体。

【讨论】:

    【解决方案2】:

    我从哪里开始学习以便能够阅读 g++ -S 喷出的内容?

    本网站不用于推荐阅读材料。谷歌“x86 汇编语言”。

    空函数是否被优化掉了?这些行在 main.s 的什么位置?

    这将是启用优化器的时候,所以生成的 .S 中不会有任何行。你已经在未优化的输出中找到了调用......

    事实上,即使是用于进行乘法运算的策略也可能会被删除,因为编译器应该能够确定您没有使用结果值。添加代码以打印 x 的值,并从编译时无法知道的某个值中播种 x(在这样的小实验程序中使用 argc 通常很方便,那么您将强制编译器至少留在功能重要的代码中。

    这个设计好吗?

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

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

    您可能需要仔细考虑您的函数名称...do_any_necessary_calculations()ensure_exclusivity() 而不是 lock_mutex()after_each_value() 而不是 print_breaks 等。

    【讨论】:

      猜你喜欢
      • 2016-12-09
      • 2014-04-18
      • 1970-01-01
      • 2013-08-06
      • 2020-07-24
      • 2018-07-24
      • 2014-05-23
      • 2011-03-18
      • 1970-01-01
      相关资源
      最近更新 更多