内联功能v.C中的宏 - 什么是开销(内存/速度)?

时间:2011-03-07 23:56:10

标签: c performance optimization macros inline

我在Stack Overflow搜索了类似函数的宏与内联函数的优点/缺点。

我发现了以下讨论: Pros and Cons of Different macro function / inline methods in C

......但它没有回答我的主要问题。

即,在内存使用和执行速度方面,使用宏函数(包含变量,可能还有其他函数调用)和内联函数的开销是多少?

是否存在编译器相关的开销差异?我可以使用icc和gcc。

我的代码片段是模块化的:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = AttractiveTerm * AttractiveTerm;
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

我将其转换为内联函数/宏的原因是我可以将其放入c文件中,然后有条件地编译其他类似但略有不同的函数/宏。

e.g:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9);
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

(注意第二行的差异......)

这个函数是我的代码的核心功能,在我的程序中每步调用数千次,我的程序执行数百万步。因此,我希望尽可能减少开销,因此我不得不浪费时间来担心内联的转换,将代码转换为宏。

根据之前的讨论,我已经意识到宏的其他优点/缺点(类型独立性和由此产生的错误)......但我最想知道的是,目前不知道的是性能。< / p>

我知道你们中的一些C老兵会对我有一些很好的见解!!

9 个答案:

答案 0 :(得分:23)

调用内联函数可能会也可能不会生成函数调用,这通常会产生非常少量的开销。实际内联inline函数的确切情况因编译器而异;大多数人都努力内联小函数(至少在启用优化时),但没有要求他们这样做(C99,§6.7.4):

  

使函数成为内联函数   建议调用函数   尽可能快地。程度   哪些建议有效   是实现定义的。

宏不太可能产生这样的开销(尽管如此,几乎没有什么能阻止编译器以某种方式做某事;标准没有定义哪些机器代码程序必须扩展到,只有编译程序的可观察行为)。

使用更清洁的东西。轮廓。如果重要的话,做一些不同的事情。

另外, fizzer 说的是什么;对pow(和division)的调用通常比函数调用开销更昂贵。尽量减少这些是一个好的开始:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0);

EnergyContribution仅由看起来像这样的字词组成吗?如果是这样,请拉出4 * Epsilon,每次迭代保存两次乘法:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0);
// later, once you've done all of those terms...
EnergyContribution *= 4 * Epsilon;

答案 1 :(得分:10)

宏并不是真正的功能。无论你定义什么,宏都会在编译器看到它之前通过预处理器逐字地发布到你的代码中。预处理器只是一个软件工程师工具,可以使各种抽象更好地构建代码。

内联函数或编译器确实知道的函数,并且可以决定如何处理它。用户禁用inline关键字只是一个建议,编译器可能会覆盖它。正是这种超越,在大多数情况下会产生更好的代码。

编译器了解这些函数的另一个副作用是,您可能会强制编译器做出某些决定 - 例如,禁用内联代码,这可以使您更好地调试或分析代码。可能还有许多其他用例,内联函数可以启用宏。

宏非常强大,为了支持这一点,我会引用谷歌测试和谷歌模拟。使用宏的原因有很多:D。

使用函数链接在一起的简单数学运算通常由编译器内联,特别是如果函数仅在转换步骤中调用一次。因此,无论天气如何提供,编译器都会为您做出内联决定,我不会感到惊讶。

但是,如果编译器没有,您可以手动平掉代码段。如果你把它弄平,也许宏可以作为一个很好的抽象,毕竟它们提供了与“真实”函数类似的语义。

The Crux

那么,您是否希望编译器知道某些逻辑边界,以便它可以生成更好的物理代码,或者您是否希望通过手动或使用宏将其展平来对编译器进行强制决策。该行业倾向于前者。

在这种情况下,我倾向于使用宏,只是因为它快速而肮脏,而不必学习更多东西。但是,由于宏是软件工程抽象,并且因为您关注编译器生成的代码,如果问题变得稍微高级,我会使用C ++模板,因为它们是为您正在考虑的问题而设计的。

答案 2 :(得分:8)

这是你要消除的对pow()的调用。此函数采用一般浮点指数,并且无法提升到整数指数。用例如这些电话取代这些电话

inline double cube(double x)
{
    return x * x * x;
}

是唯一会对你的表现产生重大影响的事情。

答案 3 :(得分:3)

请查看CERT安全编码标准,在安全性和错误唤醒方面谈论宏和内联函数,我不鼓励使用类似函数的宏,因为: - 减少分析 - 较少可追溯 - 更难调试 - 可能导致严重的错误

答案 4 :(得分:2)

回答问题的最佳方法是使用您的测试数据对这两种方法进行基准测试,以了解 应用程序中哪些方法实际上更快。除了最粗糙的水平外,对性能的预测是众所周知的不可靠。

那就是说,我希望宏和真正的内联函数调用之间没有显着差异。在这两种情况下,您最终都应该使用相同的汇编代码。

答案 5 :(得分:2)

宏,包括类似函数的宏,都是简单的文本替换,因此如果你不是非常小心你的参数,那么你可以咬你。例如,不受欢迎的SQUARE宏:

#define SQUARE(x) ((x)*(x))
如果您将其称为SQUARE(i++)

可能会等待发生灾难。此外,类似函数的宏没有范围的概念,也不支持局部变量;最受欢迎的黑客就像是

#define MACRO(S,R,E,C)                                     \
do                                                         \   
{                                                          \
  double AttractiveTerm = pow((S)/(R),3);                  \
  double RepulsiveTerm = AttractiveTerm * AttractiveTerm;  \
  (C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm);        \
} while(0)

当然,很难分配像x = MACRO(a,b);这样的结果。

正确性可维护性立场的最佳选择是使其成为一个函数并指定inline。宏不是函数,不应与它们混淆。

完成上述操作后,衡量效果并在黑客入侵之前找到实际瓶颈的位置(对pow的调用肯定会成为精简的候选者)。

答案 6 :(得分:2)

如果你random-pause这个,你可能会看到的是100%(减去epsilon)的时间在pow函数内,所以它如何到达那里基本上是没有差异。

假设你发现了,首先要做的就是摆脱你在堆栈上找到的对pow的调用。 (一般来说,它的作用是取第一个参数的log,将它乘以第二个参数,然后将exp乘以它,或做同样事情的东西。log并且exp可以通过某种涉及大量算术的系列来完成。它当然会查找特殊情况,但它仍然需要比你想象的更长的时间。) 仅此一项就能让你获得大约一个数量级的加速。

然后再次进行随机暂停。现在,您将看到其他需要花费大量时间的东西。我无法猜测它会是什么,其他人也无法猜测,但你也可以减少它。继续这样做,直到你不能再做了。

它可能会在您选择使用宏的过程中发生,并且可能比内联函数稍快。当你到达那里时,你可以判断。

答案 7 :(得分:0)

正如其他人所说,它主要取决于编译器。

我敢打赌,“pow”花费的费用比任何内联或宏都能节省你的费用:)

我认为它更干净,如果它是内联函数而不是宏。

如果你在现代处理器上运行它,那么缓存和流水线技术确实是你将获得好收益的地方。即。删除分支语句,如'if'会产生巨大的差异(可以通过一些技巧来完成)

答案 8 :(得分:0)

正如我从一些编写编译器的人那里理解的那样,一旦你从里面调用一个函数,你的代码就不太可能被内联了。但是,这就是你不应该使用宏的原因。宏删除信息并使编译器的优化选项少得多。通过多遍编译器和整个程序优化,他们将知道内联代码将导致分支预测失败或缓存未命中或现代CPU使用的其他黑魔法力量。我认为每个人都应该指出上面的代码不是最优的,所以这就是重点所在。