游戏开发论坛

 找回密码
 立即注册
搜索
查看: 20694|回复: 20

C语言基本功教程系列(3) - 快速的函数调用

[复制链接]

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
发表于 2006-10-11 15:23:00 | 显示全部楼层 |阅读模式
我又来了,今天坎坎函数调用的问题。函数哪里都有,小的程序一两个函数,大的程序成百上千个函数。即使在游戏的关键循环中,调用几十个函数也是很常见的。所以函数调用代码的质量,在很大程度上影响着游戏的质量。

还是先说最基本的代码风格问题。首先,对于函数的参数(特别是指针),如果函数内部不会修改其指针的内容,一定要用const来定义参数类型
=========不好的风格==========
void function(char * ServerName)
{
   // 内部不允许对ServerName的内容进行修改
}

=========好的风格===========
void function(const char * ServerName)
{
   // 内部不允许对ServerName的内容进行修改
}

为什么这么做呢? 举个简单的例子: 在团队开发中程序员A写好了displayFunction,传了一个数据结构给displayFunction做图象显示,然后在接下来的程序中对数据进行计算。A认为displayFunction不会对数据进行修改,所以在以后的数据运算中,没有进行一致性检测。过了几天程序员B被派过来优化A的程序,因为不知道不能改数据,结果改了下,在displayFunction中改变了数据结构的内容,当时测试通过。但是在产品发布的Alpha测试阶段,用real data的时候出了问题。我想通宵debug去差这么点个小问题,不是很值得吧。只要稍微留点心,就可以避免了

==================分割线==================
下面谈谈函数的调用问题。我们都知道,在调用的一个函数的时候,传给函数的参数是要压到栈里,然后才能被函数访问。我们来看一下函数调用的汇编代码.(汇编代码是用Visual Studio .net 2003 编译, release version。优化参数 /0t /02)

=======printf("%s%d%d%d%d",haha,m,n,p,i);======
00401000        push ecx
00401001        push ebx
00401002        mov ebx, dword ptr [esp+04]
00401003        push ebp
00401004        mov ebp, dword ptr [esp+08]
00401005        push esi
00401006        push edi
00401007        mov edi, dword ptr [esp+10]
00401008        xor esi, esi
00401009        push esi
0040100A        push edi
0040100B        push ebx
0040100C        push ebp
0040100D        push 00408040
0040100E        push 004060FC
0040100F        call 00401054

我的天哪,这是多少代码,只不过为了把参数push到栈里就用了15条。看我们看看另一段代码

===========printf("%s",haha);============
00401010        push 00408040
00401011        push 004060FC
00401012        call 00401054

现在我不用说大家都明白了吧。传递给函数的参数越少越好,最好就是一个指针,指向一个structure。这就是为什么大部分的directX的函数就是一个指针的大structure传过去。里边的参数好几十个。当然了 void fucntion(void)是最快的函数调用,也可以用inline来优化关键循环内的函数。不过在每一个frame的执行代码中,有成百上千个函数,不可能所有的都inline吧。所有能快点就快点喽。当然了,传递structure的reference也是同样的效果,只要不把structure当参数就好。
============错误的方式===========
void function(struct OneStructure Parameter);
============正确的方式===========
void function(struct OneStructure & Parameter);
or
void function(struct OneStructure * pParameter);


==================分割线==================
这个例子不是很好,因为降低了代码的可读性,不过做为参考。。。。
很多人喜欢写代码的时候这么写:
char szName[] = "Aear";
int length;

length = strlen(szName);
if(length > 0)   // 这行的效率不考虑
{
   // do something
}
粗一看没什么问题,不过如果length在以后用不到的话,那么就浪费了。因为length占用了内存,而且浪费了cpu资源。让我们看带汇编代码(汇编代码是用Visual Studio .net 2003 编译, release version。优化参数 /0t /02)

length = strlen(szName);
if(length > 0)   {...}

0040101F       sub eax, edx
00401021       mov dword ptr [esp+4], eax // 把返回值存到length中
00401025       je 00401039                       // 判断跳转

========更快速的写法的代码========
if(strlen(szName)) {...}

0040101F       sub eax, edx
00401021       mov esi, eax   //把返回值放在个临时寄存器中
00401023       je 00401037

大家都知道寄存器之间进行数据操作是非常快的,而且是稳定的一个cpu clock cycle,至于00401021       mov dword ptr [esp+4], eax 到底要花多少个clock cycle,那只有天知道了。因为这种从内存中读数据的指令,最少也是2个clock cycle,即使在L2 cache中,也不会比 mov esi, eax 快,而且浪费了栈空间。


==================再分割下吧,虽然不是很喜欢==================
最后说说一种类告诉的分枝判断参数传递。在有些情况下,我们经常要传很多参数,比如pixel shader等等,这些函数根据参数的设置,进行不同的操作。举个例子:

struct Parameter{
     bool bDrawWater;
     bool bDrawSkybox;
     bool bDrawTerrain;
     bool bDrawSepcialEffects;
} DrawParamter;

void DrawEnvironment( struct Parameter * pPara)
{
     if(pPara->bDrawWater) {....};
     if(pPara->bDrawSkybox) {....};
     if(pPara->bDrawTerrain) {....};
     if(pPara->bDrawSpecialEffects) {....};
}

对于这样的代码,还有更快速, 更节省内存的方法,那就是位操作。
const static UINT32 DRAW_WATER_FLAG               = 1;
const static UINT32 DRAW_SKYBOX_FLAG              = 1 << 1;
const static UINT32 DRAW_TERRAIN_FLAG             = 1 << 2;
const static UINT32 DRAW_SPECIALEFFECTS_FLAG  = 1 << 3;

void DrawEnvironment(UINT32 DrawFlag)
{
    //注意了,这里不需要 pPara->,也就是节省了内存访问,速度至少提高了1到2个clock cycle
     if( DrawFlag & DRAW_WATER_FLAG ) {.....};
     if( DrawFlag & DRAW_SKYBOX_FLAG) {.....};

    //甚至还可以进行各种不同组合的判断,比如
     if( DrawFlag & (DRAW_WATER_FLAG | DRAW_SKYBOX_FLAG) ) {....};
}

在调用的时候,代码更加简洁明了:
DrawEnvironment( DRAW_WATER_FLAG | DRAW_TERRAIN_FLAG );

就说这么多,累了,下次见






106

主题

743

帖子

745

积分

高级会员

Rank: 4

积分
745
QQ
发表于 2006-10-11 20:07:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

楼主能不能写DX教程,这个更令俺期待。

121

主题

2029

帖子

2034

积分

金牌会员

Rank: 6Rank: 6

积分
2034
QQ
发表于 2006-10-11 22:07:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

有功底的人。不过我平时写程序还是不太会考虑这样。。。。

这里的版面风格不太好,楼主想办法到Blog里面发个连载吧。

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
 楼主| 发表于 2006-10-11 23:24:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

DX的教程哪里都有哦,没必要再写了吧。要写就以后写点高级shader的东西,不过先把这个系列搞定再说。。。。。。。偶时间也不是很富裕,慢慢来吧。

这些技巧平时写写code是不用太考虑到的,不过如果是写engine或者游戏的mainloop,还是多多考虑下好。如果平时写code的时候经常会考虑到,那么写关键代码的时候就不会有所遗忘了。

3

主题

79

帖子

79

积分

注册会员

Rank: 2

积分
79
发表于 2006-10-12 09:59:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

这叫浪费时间做无用功,有的人捡西瓜有的人费尽力气捡芝麻

8

主题

130

帖子

156

积分

注册会员

Rank: 2

积分
156
发表于 2006-10-12 10:08:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

帮楼主指出一下:
const 和引用、指针是比较推荐的,细节上对于原始数据类型直接传值更好。

后面的想当然的优化手法,楼主不用怀疑现在编译器的优化能力,<B>一般来说</B>只有你想不到的优化小技巧,编译器都可以做到。用release编译,然后看汇编,你所做的这小技巧,编译器都给你做了。举个例子,a*10,你可以考虑你的小技巧,然后看看编译器怎么为你生成代码,然后看看哪个更优。
顺便,很多人常常提汇编优化。在现在的机器上和编译器上,就大多数人而言,建议先多了解语言和你的工具,然后再做优化,不信可以试试看你的汇编优化,然后试试看编译器的优化。

也就是说:对于const和引用属于语法层面,是应该掌握的;对于代码编写还是首要以可读性考虑。

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
发表于 2006-10-12 10:19:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

谢谢楼主,又长见识了!

18

主题

971

帖子

982

积分

高级会员

Rank: 4

积分
982
发表于 2006-10-12 11:19:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

楼说所说的const和非const应该是做为一个c++程序员应该注意的,但如果是写夸语言的库,大概连const也不能用,只能用c式的__stdcall返回和标准的char*.
对于后面所谓的优化,个人比较赞同cn_zhangJW同志的说法,一是不同的编译器做的优化不同,二是不同平台优化也不同。

27

主题

179

帖子

259

积分

中级会员

Rank: 3Rank: 3

积分
259
 楼主| 发表于 2006-10-12 11:33:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

所有给出的汇编代码都是 release的,优化参数见原文。目前流行的编译器是gcc和vc的cl,我想基于windows的平台,不用再给出其他编译器的例子了吧。

还有,我这里谈的不是优化策略,只是在说写程序的一些设计和基本习惯。

121

主题

2029

帖子

2034

积分

金牌会员

Rank: 6Rank: 6

积分
2034
QQ
发表于 2006-10-12 11:33:00 | 显示全部楼层

Re:C语言基本功教程系列(3) - 快速的函数调用

const仅仅在语法层面上是这样,而对汇编来说基本没影响。它不同于volatile,所以即使是跨语言的库,const也是可以使用的。

还有就是编译器的优化现在已经很好了。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2026-1-25 15:27

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表