问题:编码时可以依靠编译器优化吗?
比方说,我需要计算calculateF
和calcuateG
,它们都取决于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
完全相同。此外,如果我同时需要f
和g
,则只需调用一次getValue
,而不是两次。
代码更简洁(仅使用1个功能calculateFG
而不是calculateF
和calculateG
),如果同时需要f
和g
,则代码更快。但是依靠编译器优化是明智的选择吗?
答案 0 :(得分:2)
很难说这是否明智。这取决于编译器的优化-函数内联。
如果内联calculateFG
,则编译器可以优化未使用的代码。内联后,g
就不再使用,因此用于生成g
的所有代码都是无效代码 [1] 。 (例如,如果计算代码有一些副作用,则可能无法执行此操作)
否则,我认为无法应用优化(始终计算f
和g
)。
现在,您可能想知道是否始终可以内联特定功能。
请注意,使用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
时,优化程序可以消除对calculateFG
中f
的写操作。
答案 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()
,f
和g
(可能很昂贵)的计算-即您不想执行任何调用者不会使用其结果的计算。
在这种情况下,实现一个小类来执行必要的按需计算和缓存可能是最简单的,就像这样:
#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(...)
之类的调试消息,则可能会影响清除无效代码。如果编译器可以证明无效代码在编译时没有副作用(即使您强制内联),则它也可以摆脱。
其他选择是,您可以使用特殊的编译器特定属性(例如getValue
或pure
来标记const
函数。这可以迫使编译器优化getValue
的第二次调用。 https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bpure_007d-function-attribute-3348