为什么不每个函数都用inline??

xyq1986 2006-02-27 10:38:16
既然inline可以免去函数的调用,那为什么不每个函数都用inline呢??
...全文
823 37 打赏 收藏 转发到动态 举报
写回复
用AI写文章
37 条回复
切换为时间正序
请发表友善的回复…
发表回复
sjjf 2006-03-02
  • 打赏
  • 举报
回复
小声地说一声:如果真的需要用到了inline,那么推荐看一下
"组合语言设计之艺术" 作者:朱邦复 写作时间:1990年
虽然书有些老了,但是思想是没有老的,而且能够帮助你设计出高效的程序。
ox_thedarkness 2006-03-02
  • 打赏
  • 举报
回复
嗯,同意。我那贴主要是说紧凑程度是一个混沌的系数,在局部、大范围、宏观可能有不同的效果。

说的都是inline 的缺点。当然优点很多:

函数调用本身属于不确定因素,他强制隔断了上下流程。而 inline 的另一个作用是,让编译器有能力把上下文接起来继续优化。



不过,我建议不要写inline。

一来,几乎所有的程序大师都说过:不要考虑效率。需要考虑的时候,用剖析程序分析瓶颈再优化。 这不只是出于开发效率的考量。大部分优化技巧都是以空间为代价的,所以大面积优化在规模上的增长往往抵消了优化效果、甚至适得其反。 我举了一个例子。

二来,现在的编译器已经很聪明了。他们会进行开销/收益评估,而且会自动把非inline小函数 inline 化。上面已经有好几个例子说明这一点。

做C++ 优化,我个人认为应该相信编译器的优化, 警惕编译器的输出。

前者因为C++编译器已经很好了。以前某人用C++写过一个复杂的memset,发现居然不如一个4行的版本。我这个汇编初学者试图用内嵌汇编比拼,在大数据时不相上下,小数据时仍然比不过。

后者是因为C++很多看起来很美的特性,底层可能产生你繁杂到匪夷所思的代码...
ox_thedarkness 2006-03-02
  • 打赏
  • 举报
回复
在线函数?
sjjf 2006-03-01
  • 打赏
  • 举报
回复
看看cpp的设计者是如何说的吧:
===================================摘自 cpp 设计与演化
在带类的c的初始版本里没有提供inline函数,以从可用的表现形式中进一步得益。
当然,不久我们就提供了在线函数。引进在线函数的一般性理由与越过保护屏障的代价有关。
这种代价有可能导致人们不愿意去用类来隐藏表示的细节,特别的。[stroustrup,1982b]中
观察到人们总把数据成员做成公有的,以避免调用简单类的建构函数而带来的开销。
因为对这些对象的初始化可能只需要一两个赋值语句。将在线函数引进带类的c直接的原因
是一个具体的项目。在该项目中,由于某些类与实时处理有关,这种函数的调用开销无法接受。
为了使类机制能够成为在这个应用中有用的东西,就要求在跨越保护屏障时不付出任何代价。
只有在类声明中提供一种可用表示。并能把对公用(界面)函数的调用都变成在线的。
才能够达到这个目的。
==================================摘录结束

如作者所言,在认为支付调用函数的代价比较吃力时候才需要考虑使用内联函数。
(这一般都在实时系统里面)
后面还有一小节说在线机制的,不过太长了,我没有电子书,懒得抄了:)
也就是说,在需要时用inline的环境里,作者是很在乎效率的,
不会把一大堆的代码塞到函数中,不然,调用函数的开销和函数体的执行体开销相比就微乎其微了。

ox_thedarkness() 的分析挺细致的。
不过有些地方我有不同的看法。

----------------------------
1 总指令执行量。 需要执行的总指令越多,就越需要CPU的执行开销。
这个很容易定量。

2 流程中代码紧凑程度。 这关系到Cache换页频度。
这个则是一个混沌的系数。 分析代码流才能确定影响力, 我会慢慢解释。
总的来说是这个规律:指令段越短小越好。
----------------------------
我想还应该加上些限制条件吧,
流水线cpu的效率很大程度上跟流水线被中断有关,
导致流水线中断主要是相关和条件转移,
相关指挨得比较近指令间有依赖关系。一条指令没有执行完,下一条也不能执行。
一条指令的执行分了好几个工序,这将导致可能会最多浪费一个指令周期(可能会很多个时钟周期),
但是问题是进入处理工序的指令也会等待这个指令周期。无形中让浪费加大了很多倍。
这种情况,一般都用乱序执行的方法:把后面的无关的指令提前到前面来,
条件转移指遇到带条件转移的指令需要在等执行完后才能决定那条指令被执行。
目前一般用分枝预测来解决,就是推测结果,预先装载预测的指令,当然猜测错了,代价也是很昂贵的。
因为流水线上的指令都要被废除。(据说intel的奔腾系列的cpu预测准确可达 80% , 不知道是真是假)。

因此cpu执行的开销不应该只是包括需要被执行的总指令,还包括那些被浪费的。


分析代码流并不能确定"影响力",呵呵。
==================摘自 量子混屯,这本书俺也看不懂 :(
混屯理论认为混屯运动之所以表现出随机特性,其根本原因在于邻近轨道
的指数型分离。换句话说。一个确定性的运动是否混屯,与它附近的
轨迹运动有密切关系。
。。。。
这种特征被称作运动对初值的敏感性,也称作指数型指数不稳定性。
描述这种特征需要用李雅普诺夫指数(lyapunov characteristic exponent)
。。。。。。
==================
在只考虑单道程序运行的例子中,
可以看作程序由代码流和数据流。
然后代码流的随机特性依赖于数据流。
经典的混屯理论认为在确定性的运动系统中。
人们能够根据初始状态推算系统的未来某一时刻的状态,
但是由于存在干扰因子,所以得到的推算值并不绝对正确。
这种干扰误差累积起来,经过很长一段时间后,结果将会和最初的预测大相径庭不可预测。
如果我们把代码流和数据流视作一个封闭的系统
(它是确定性的系统:给定代码流当前状态和数据流当前状态,我们可以确定代码流的下一个状态)。
那么代码流表现出来的随机特性取决于数据流。
比如代码中的条件转移部分需要根据数据流的状态来定。
相对于代码流,存在的条件转移和转移时的数据流,就是一个干扰因子。
这些干扰因子的效应累积起来后,将导致绝对正确的结果不可被预测。
例如,你在写完一段排序程序后,你能够绝对正确预测他的执行的效率么?
不可能,因为程序每次要处理的数据都不一样的。所以不可能在你写代码的时候就能够预测他准确的效率。

混屯理论还认为混屯是随机行为,即演化的过程回归到曾有的状态的附近。
然而,只是附近而已,也就是说每次都会有偏差。就是这些偏差,
会造成演化后的状态千姿百态。
程序是确定性的系统。在相对比较细的时间空间里,我们能够知道代码流执行的结果。
比如我们在debug程序的时候。我们总能精确的推测出有限的下几步是什么样子的,
但是如果再往下推测的时候,就会有越来越多的可能冒出来了。
每次我们面对不同数据的时候进行调试追踪的时候,可以看作是一个回归过程。
(如果强调每次数据都相同的情况下的调试,那么其中的干扰因子就不再是条件转移了,
而是别的了例如cpu执行的时序差磁盘io等等,在这么大的粒度下我们观测不到混屯)
所以,我们所谓的评估效率实际上是一些统计的平均值。不可能得到某个精确的值。
这也就是为什么很多算法例如搜索,排序会有最好情况,最坏情况的原因。


cache和workset理论的提出基于局部性原理。
----------------摘自 操作系统 开始
局部性原理(principle of locality):指程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,
分别局限于一定区域。还可以表现为:
时间局部性,即一条指令的一次执行和下次执行,一个数据的一次访问和下次访问都集中在一个较短时期内;
空间局部性,即当前指令和邻近的几条指令,当前访问的数据和邻近的数据都集中在一个较小区域内。

局部性原理的具体体现
程序在执行时,大部分是顺序执行的指令,少部分是转移和过程调用指令。
过程调用的嵌套深度一般不超过5,因此执行的范围不超过这组嵌套的过程。
程序中存在相当多的循环结构,它们由少量指令组成,而被多次执行。
程序中存在相当多对一定数据结构的操作,如数组操作,往往局限在较小范围内。
----------------摘自 操作系统 结束

根据局部定理设置了cache,减少了内存访问的频率,从而有效的提高了系统效率。
独立出一层cache之后,又势必涉及到被独立出来的层与其他层的通讯。
比如何时进行通讯,进行什么样的通讯(读取,同步更新),通讯内容(通讯量等),才是最有效的。
于是workset理论应命而生。给局部性定理的描述进行量化的指导方向。
但是即使是workset也存在颠簸的现象。
这不是一个程序应该考虑的问题。个人觉得它更应该是操作系统要考虑的问题。

并不是函数体小就一定程序效率就高。还需要考虑数据,以及cache的读写策略,淘汰策略有关。
诚然,小的函数体从进入到退出,在cache没有到达临界值时,代码指令必然会有极高的命中率,
但是并不代表他们不需要更新到内存。比如这段代码是更新一段链表。
代码足够简单了,但是数据的内存分布却不一定在一起,所以cache还是会很快的超过临界值的。


此外函数体长也不一定导致程序低效。如果这段函数体都是有一些极小的循环构成,那么它们的效率也可能会很高的。
例子就不举了。只能说效率和函数体的大小并非线性相关。


有些离题了,本来mark下来,偷空写点回复的,没想到不小心思考上了,
呵呵,帖子已经结了,思考的内容食之五味,弃之可惜,权当借个地方放放吧。

ox_thedarkness 2006-03-01
  • 打赏
  • 举报
回复
阿? 楼上不会是上次在群里讨论日文台词那个把....
CloudOfFly 2006-03-01
  • 打赏
  • 举报
回复
UP
Jiana 2006-03-01
  • 打赏
  • 举报
回复
好像
kinglytt 2006-02-28
  • 打赏
  • 举报
回复
那什么样的函数才算了大函数呢?
--------------------------------

一般函数体中含有循环,多个判断,或超过十几行的话,就算大函数,其调用开销是执行开销的高阶无穷小,可以忽略不计了.
strangerryf 2006-02-28
  • 打赏
  • 举报
回复
6.7.4
A function declared with an inline function specifier is an inline function. The function specifier may appear more than once; the behavior is the same as if it appeared only once. Making a function an inline function suggests that calls to the function be as fast as possible.118) The extent to which such suggestions are effective is implementation-defined.119)

118) By using, for example, an alternative to the usual function call mechanism, such as "inline substitution". Inline substitution is not textual substitution, nor does it create a new function.Therefore, for example, the expansion of a macro used within the body of the function uses the definition it had at the point the function body appears, and not where the function is called; and identifiers refer to the declarations in scope where the body occurs. Likewise, the function has a single address, regardless of the number of inline definitions that occur in addition to the external definition.
119) For example, an implementation might never perform inline substitution, or might only perform inline substitutions to calls in the scope of an inline declaration.

----------C99

7.1.2
2 A function declaration with an inline specifier declares an inline function. The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions defined by 7.1.2 shall still be respected.

3 A function defined within a class definition is an inline function. The inline specifier shall not appear on a block scope function declaration.79)

4 An inline function shall be defined in every translation unit in which it is used and shall have exactly the same definition in every case. [Note: a call to the inline function may be encountered before its definition appears in the translation unit. ] If a function with external linkage is declared inline in one translation unit, it shall be declared inline in all translation units in which it appears; no diagnostic is required. An inline function with external linkage shall have the same address in all translation units. A static local variable in an extern inline function always refers to the same object. A string literal in an extern inline function is the same object in different translation units.

-----------C++03
alen_ghl 2006-02-28
  • 打赏
  • 举报
回复
空间/时间的权衡问题吧
------------
同意
逸学堂 2006-02-28
  • 打赏
  • 举报
回复
既然inline可以免去函数的调用,那为什么不每个函数都用inline呢??
~~~~~~~~~~~~~~~
inline函数可以免去函数调用的损失,但是却大大增加了代码量(代码膨胀)
如果对所有函数使用inline,不可避免的是,一个编译文件将是是一个超大规模的
文件(所有函数都inline了),对于上大的工程文件,他的编译时间是不可忍受的!

inline对编译器只是一个提示说明,编译器可以对你的函数inline也可以不inline。
所以即使你的函数都写上inline,最后的结果,也可能一个inline也没有。如果有些函数确实
想采用inline,但是又怕编译给忽略掉,可以采用__inline,并在编译时刻选择
以__inline方式内联而不是默认方式内联就可。
cmoring 2006-02-28
  • 打赏
  • 举报
回复
inline是用于实现的,用于.cpp才有可能是内联的。声明的时候完全不需要的。
在林锐的《高质量C++编程指南》里面有详细的介绍
jinke1983 2006-02-28
  • 打赏
  • 举报
回复
那么函数实现必须与函数声明在同一个文件中,不能一个在.h,一个在.cpp
???
  • 打赏
  • 举报
回复
递归函数中很多简单的尾递归形式可以被很多编译器优化为递推形式, 这样的递归函数照样可以被 inline , 不过做得很好的编译器不多,唉.
现在的有些编译器很智能鸟, 即使没加 inline , 最后简单的函数照样被 inline 处理, 这个好像 icl 做得比较好, 像:

#include <stdio.h>

int foobar( int i )
{
return 1000 + i;
}

int main()
{
__asm int 3;
printf( "%d\n" , foobar(10) );
return 0;
}

用 icl -O3 -fast test.c 编译后为:
0040100F 8B EC mov ebp,esp
00401011 83 EC 08 sub esp,8
00401014 E8 2B 49 00 00 call 00405944
00401019 CC int 3
0040101A 68 F2 03 00 00 push 3F2h ; foobar(10)=1010=0x3f2 bof
0040101F 68 B0 60 40 00 push 4060B0h ; s "%d\n"
00401024 E8 0F 00 00 00 call 00401038 ; printf
00401029 83 C4 08 add esp,8
0040102C 33 C0 xor eax,eax
0040102E 8B E5 mov esp,ebp
00401030 5D pop ebp
00401031 8B E3 mov esp,ebx
00401033 5B pop ebx
00401034 C3 ret

ox_thedarkness 2006-02-28
  • 打赏
  • 举报
回复
钱能?真武断阿。

下面的函数,VC7.1,Release模式。你看,我们不加 inline , VC都给我们inline了...

int func ( int n){
switch( n ){
case 1:
return 10;
case 2:
return 20;
default:
return 0;
}
}

int main(){
cout<<func(2)<<endl;
}


看看 cout<<func(2)<<endl; 的输出:
-------------------------------------------
push 20 ; 00000014H
mov ecx, OFFSET FLAT:?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
call ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z ; std::basic_ostream<char,std::char_traits<char> >::operator<<
....

直接就替换为20了... V
caijize 2006-02-28
  • 打赏
  • 举报
回复
内联函数中,不能含有复杂的结构控制语句,如SWITCH和WHILE。如果内联函数有这些语句,刚编译将该函数视同普通函数那样产生函数调用代码。
另外,递归函数是不能被用来做内联函数的。
——摘自《C++程序设计教程》钱能主编
sjjf 2006-02-28
  • 打赏
  • 举报
回复
mark
晨星 2006-02-28
  • 打赏
  • 举报
回复
呵呵。
sinkileu 2006-02-28
  • 打赏
  • 举报
回复
如果长的函数也用内联,应用程序会变得笨重起来,因为他只是纯粹的代码拷贝!
ox_thedarkness 2006-02-28
  • 打赏
  • 举报
回复
切,当然不是函数了~~ 让编译器作者去做啊~~~

btw,说错了,动态申请栈内容。 ~~ 偶一直觉得C++ 几个主流编译器不厚道~~ 每个函数都一次把所有自动变量空间要着, 其实完全可以动态嘛~~
加载更多回复(17)

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧