关于内联函数有时候反而影响性能的说法——我的观点
我的观点就是:不要过分夸大代码膨胀的弊端!
我是看了这个帖子忍不住要说一下的:
http://www.vchelp.net/cndevforum/subject_view.asp?subject_id=170499&forum_id=55
我不理解楼主为什么要把帖子锁住;
代码膨胀在理论上的确会影响性能,正如有的人解释的,代码多了那么程序执行时copy代码的吞吐量也会增加,似乎很有道理;但我想问两个问题:
第一,这个吞吐量到底有多大?你们大家都去看看,有谁编译出超过1MB的dll或exe,如果有那么这个dll是否包含了大量的数据(如unicode的转码dll就有1MB多),或者包含了许多其它的静态lib(比如它是基于MFC的);如果是exe,那么它是否有界面,是否包含了许多界面资源;一个程序真正的执行代码到底能有多少?排除了这些,我想你们编译出来的真正是你们自己的东西不会超过1MB;
除非你用内联将指令从1MB膨胀到2MB,可能吗?除非你是白痴,写个几百行代码的函数,然后内联;另外,机器从磁盘copy的是你的代码吗?不是,是代码编译出的机器指令,C++又不是Java,一个函数经过编译后的指令数可能很少,绝对没有你写的代码那么多,否则你写个几万行代码,为什么编译出来的东西还不到1MB?所以,使用内联到底能增加多少指令数,而一个函数的总指令数又有多少?绝对不足以影响到磁盘吞吐;
第二,无论是否使用内联,你函数的功能不变吧,那么函数执行的总指令数不变吧,那么copy的总指令数应该也一样吧,那么是否使用内联的吞吐量应该一样,怎么会更多?相反,使用内联,函数调用次数少了,那么吞吐的次数也少了,在吞吐量不变的情况下,吞吐次数减少,是性能降低还是提升?举个例子,从磁盘读100个字节,是一次读100字节快;还是每次读1个字节,读100次来得快?
所以,代码膨胀不会增加单次函数调用的吞吐,只会增加整个编译输出模块的大小,而我前面也已经分析了,在实际应用中,这个模块大小的增幅也是有限的;还有,是不是模块越大程序执行就越慢?恰恰相反,你编译时选择优化(Max Speed)的输出肯定比选择不优化来得大;我用INTEL的编译器输出的结果也比VC6略大(INTEL有优化);
不要拿着老外的书当圣经,写书的人怎么比得过具体做事的人,因为他们的主要时间都是在说,而不是在做!一切从实际出发,具体问题具体分析,我们民族本身就有很好的方法论;马克斯万岁!毛主席万岁!呵呵;
其实使用内联不需要考虑那么多,有的人说内联里不能有循环,不能有递归,不能有异常,我说全是放屁!内联的含义就是通过代码静态替换(静态编联)来去掉函数调用的一层栈操作,你可以把它当作“高级的宏”,不需要栈的函数,都可以;循环、递归、异常都是代码中的东西,根本就和内联没有关系;
不过,我是没有想出来带有递归的内联函数如何实现代码替换的,我想实际上这种函数即便被修饰为内联,也不会被替换,知道的人请告诉我;(其实做个实验也是满简单的。)
我最不理解的是,为什么有的人说如果函数体内有循环就不能用内联,内联没有什么能与不能的,只要你加上inline编译器不报错就可以了,最多函数实际的性能与不内联一样咯;
其实,是否使用内联的原则很简单,你只要把整个函数的工作量想象成是一次栈操作和你的代码功能之和,如果你的代码工作量小于栈操作,或与栈操作差不多,那么就应该使用内联;如果栈操作远不如你的代码工作量,那就没有必要使用内联;
那么一次函数栈操作的工作量到底有多大?应该和你自己写个堆栈的一次压栈和退栈差不多,但绝对要比自己写的来得慢;或者也可以这么估计,和一个空的循环差不多(如:while(begin++ < end){});(这完全是我的个人经验和感觉。)
内联的唯一好处:减少了一次栈操作;
内联的唯二坏处:代码膨胀和代码公开;
最后我再说说我个人是否使用内联的原则:
1.不想公开的代码,不用内联;
选择了内联,就是选择了别人能完全看见你的原始代码,因为使用内联函数必须连带它的代码一起被包含,即便你的代码写在cpp里,那就也得包含这个cpp,否则就不能跨模块使用;所以我都是把内联的函数体直接写在头文件里;
2.已经是虚函数了,不用内联;
如从虚函数继承过来的函数,这是基本概念;
3.直接返回成员
inline int get_count(){ return m_count; }
4.对已有函数的再封装
inline int find_string(const char *str)
{
char *p = strstr(m_buffer, str);
return NULL != p ? p – m_buffer : -1;
} // 返回的是查找到的位置偏移量,函数本身没做什么事,就是把strstr的结果转换了一把;
5.函数代码少于5行(每行100句话,呵呵)
inline void func()
{
func1();
func2();
func3();
func4();
} // 不要管func1~4是不是内联,把它们都当作普通函数看!
6.带循环,但循环不复杂,循环个数也不多
inline int func()
{
int i = 0, j = 0, sum = 0;
for(i = 0; i < m_count; i++)
If(isalnum(m_data[i]))
continue;
else
break;
for(j = 0; j < i; j++)
sum += im_data[j] – ‘
return sum;
} // 返回起始连续数字之和,两个循环,都满简单的;
简化后其实它也是少于5行:
inline int func()
{
int i = 0, j = 0, sum = 0;
for(i = 0; i < m_count && isalnum(m_data[i]); i++);
for(j = 0; j < i; sum += m_data[j++] – ‘
return sum;
} // 呵呵,顺便学习一下代码简化吧!
总结3~6条,道理很简单,从代码上直观去看,简单明了,或函数自己没做什么实际工作的就用内联;如果函数本身就是个完整的算法,有步骤,有过程,那这个计算开销可能就会超过一次栈操作了,就不需要用内联了;
7.不确定要不要用内联的,就用内联;
8.会被频繁使用的函数,考虑使用内联;
考虑一个函数完成后,它将会被使用的场合,比如它是否会被用在循环体内,如:
while(begin < end)
func(begin++);
// 考虑将func修饰为内联;
我实际遇到的一个例子,是实现16位RGB图象与24位RGB图象之间的转换:
for(int i = 0; i < height; i++)
for(int j = 0; j < width; j++)
img16[i][j] = rgb24_2_rgb16(img24[i][j]);
其中的rgb24_2_rgb16是实现一个象素点的转换,里面的内容全是位移操作;
这个函数是不是内联对性能很有影响,因为它是被使用在双重循环中的;
实际我最后使用的是汇编,呵呵;








评论人