依靠C ++进行编译器优化

时间:2019-02-11 23:26:39

标签: c++ optimization c++17

问题:编码时可以依靠编译器优化吗?

比方说,我需要计算calculateFcalcuateG,它们都取决于getValue返回的另一个值。有时我需要两个值,而有时我只需要其中一个值。

// some function
double getValue(double value)
{
    double val(0.0);
    // do some math with value
    return val;
}

// calculateF depends on getValue
double calculateF(double value)
{
    double f(0.0);
    auto val = getValue(value);
    // calculate f which depends on val (and value)
    return f;
}

// calculateG depends on getValue
double calculateG(double value)
{
    double g(0.0);
    auto val = getValue(value);
    // calculate g which depends on val (and value)
    return g;
}

现在,我可以写得更优雅一些:

std::pair<double,double> calculateFG(double value)
{
    auto val = getValue(value);
    double f(0.0), g(0.0);
    // calculate f and g which depend on val (and value)
    return {f,g};
}

如果我想要两个值:

double value(5.3);
auto [f,g] = calculateFG(value); // since C++17
// do things with f and g

如果我只想要1个值,例如f,我就不用g,它将被优化。因此,如果我不使用calculateFG,则calculateF的性能与g完全相同。此外,如果我同时需要fg,则只需调用一次getValue,而不是两次。

代码更简洁(仅使用1个功能calculateFG而不是calculateFcalculateG),如果同时需要fg,则代码更快。但是依靠编译器优化是明智的选择吗?

6 个答案:

答案 0 :(得分:2)

很难说这是否明智。这取决于编译器的优化-函数内联。

  • 如果内联calculateFG,则编译器可以优化未使用的代码。内联后,g就不再使用,因此用于生成g的所有代码都是无效代码 [1] 。 (例如,如果计算代码有一些副作用,则可能无法执行此操作)

  • 否则,我认为无法应用优化(始终计算fg)。

现在,您可能想知道是否始终可以内联特定功能。

请注意,使用inline关键字不会强制编译器内联该函数。这只是一个提示。有或没有关键字,它都是编译器的调用。似乎有一种非标准的方式-How do I force gcc to inline a function?

[1] 相关的编译器选项:-fdce -fdse -ftree-dce -ftree-dse

答案 1 :(得分:1)

如果有机会,现代C ++编译器非常擅长优化选择。

这就是说,如果您声明一个函数inline,这并不意味着优化程序实际上将100%的时间关闭它。效果更加微妙:inline意味着您避免使用“一个定义规则”,因此函数定义可以放入头文件中。这使优化程序变得更加容易

现在在您的double [f,g]示例中,优化器非常擅长跟踪简单标量值的使用,并且可以消除只写操作。内联允许优化器也消除被调用函数中不必要的写入。对您来说,这意味着以后调用代码不使用f时,优化程序可以消除对calculateFGf的写操作。

答案 2 :(得分:1)

也许最好将逻辑由内到外。您可以更改代码以传递函数而不是计算值,而不是计算值(getValue(),将其传递到calculateF()calculateG()并将结果传递到另一个位置

这样,如果客户端代码不需要calculateF的值,则不会调用它。与calculateG相同。如果getValue也很昂贵,则可以调用一次并绑定或捕获值。

这些是在函数式编程范例中广泛使用的概念。

您可以像这样或多或少地重写calculateFG()函数:

auto getFG(double value)
{
    auto val = getValue(value);
    return {
        [val]{ return calculateF(val); },
        [val]{ return calculateG(val); }};
}

答案 3 :(得分:0)

听起来您的目标是仅根据呼叫者的需求,尽可能少地执行getValue()fg(可能很昂贵)的计算-即您不想执行任何调用者不会使用其结果的计算。

在这种情况下,实现一个小类来执行必要的按需计算和缓存可能是最简单的,就像这样:

#include <stdio.h>
#include <math.h>

class MyCalc
{
public:
   MyCalc(double inputValue)
      : _inputValue(inputValue), _vCalculated(false), _fCalculated(false), _gCalculated(false)
   {
      /* empty */
   }

   double getF() const
   {
      if (_fCalculated == false)
      {
         _f = calculateF();
         _fCalculated = true;
      }
      return _f;
   }

   double getG() const
   {
      if (_gCalculated == false)
      {
         _g = calculateG();
         _gCalculated = true;
      }
      return _g;
   }

private:
   const double _inputValue;

   double getV() const
   {
      if (_vCalculated == false)
      {
         _v = calculateV();
         _vCalculated = true;
      }
      return _v;
   }

   mutable bool _vCalculated;
   mutable double _v;

   mutable bool _fCalculated;
   mutable double _f;

   mutable bool _gCalculated;
   mutable double _g;

   // Expensive math routines below; we only want to call these (at most) one time
   double calculateV() const {printf("calculateV called!\n"); return _inputValue*sin(2.14159);}
   double calculateF() const {printf("calculateF called!\n"); return getV()*cos(2.14159);}
   double calculateG() const {printf("calculateG called!\n"); return getV()*tan(2.14159);}
};

// unit test/demo
int main()
{
   {
      printf("\nTest 1:  Calling only getF()\n");
      MyCalc c(1.5555);
      printf("f=%f\n", c.getF());
   }

   {
      printf("\nTest 2:  Calling only getG()\n");
      MyCalc c(1.5555);
      printf("g=%f\n", c.getG());
   }

   {
      printf("\nTest 3:  Calling both getF and getG()\n");
      MyCalc c(1.5555);
      printf("f=%f g=%f\n", c.getF(), c.getG());
   }
   return 0;
}

答案 4 :(得分:0)

我认为最好以表达您要完成的工作的方式编写代码。

如果您的目标是确保某些计算仅执行一次,请使用Jeremy的答案。

答案 5 :(得分:0)

一个好的函数应该只做一件事。我会像下面这样设计。

class Calc {
    public:
    Calc(double value) : value{value}, val{getValue(value)} {
    }
    double calculateF() const;
    double calculateG() const;
    //If it is really a common usecase to call both together
    std::pair<double, double> calculateFG() const {
        return {calculateF(), calculateG()};
    }
    static double getValue(double value);
    private:
    double value;
    double val;
};

要知道编译器是否会优化,将取决于其余代码。例如,如果存在诸如log_debug(...)之类的调试消息,则可能会影响清除无效代码。如果编译器可以证明无效代码在编译时没有副作用(即使您强制内联),则它也可以摆脱。

其他选择是,您可以使用特殊的编译器特定属性(例如getValuepure来标记const函数。这可以迫使编译器优化getValue的第二次调用。 https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bpure_007d-function-attribute-3348