使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx

上传人:b****1 文档编号:3249641 上传时间:2023-05-01 格式:DOCX 页数:23 大小:24.33KB
下载 相关 举报
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第1页
第1页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第2页
第2页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第3页
第3页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第4页
第4页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第5页
第5页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第6页
第6页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第7页
第7页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第8页
第8页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第9页
第9页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第10页
第10页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第11页
第11页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第12页
第12页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第13页
第13页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第14页
第14页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第15页
第15页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第16页
第16页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第17页
第17页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第18页
第18页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第19页
第19页 / 共23页
使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx_第20页
第20页 / 共23页
亲,该文档总共23页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx

《使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx》由会员分享,可在线阅读,更多相关《使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx(23页珍藏版)》请在冰点文库上搜索。

使用gcc和glibc来优化程序转载共20页文档Word文档格式.docx

if(sizeof(int)==sizeof(longint)||(type==0))

sizeof运算总是在编译时进行,因此增加的条件表达式总是在编译时计算.

如果longint和int确实相同,那么这个函数就可以被编译器优化.

进一步优化,利用limits.h中定义的宏

#includelimits.hlongintadd(longinta,void*ptr,inttype)

#ifLONG_MAX!

=INT_MAXif(type==0)

else

#endifreturna+*(longint*)ptr;

这样,即便在longint不同于int的平台上,该函数也被优化了

2.2节省函数调用(SavingFunctionCalls)

很多函数很短小,相对函数执行的时间,函数调用的代价不可忽视.例如

标准库中的字符串函数和数学函数.解决办法有两个:

使用宏代替函数,

或者用inline函数.

一般而言,inline函数和宏一样快,但是更安全.但是如果用到alloca和

__builtin_constant_p的时候,可能要考虑用优先使用宏了

但是,如果函数被声明为extern,inline并不总是有效了.另外,当gcc的

编译优化选项没有打开时,gcc不会展开inline函数.

如果inline函数是static的,那么编译器总是会展开该函数,不考虑是否

真的值得.尤其是当使用-Os(optimizeforspace)选项时,staticinline函数是否值得使用就是个问题了.

编写正确而又安全的宏并不容易.要注意

a)正确使用括号括起参数,

例如

#definemult(a,b)(a*b)//错误

#definemult(a,b)((a)*(b))

b)宏定义中的大括号引入新的block,这有时侯会导致问题.

#definescale(result,a,b,c)\

intc__=(c);

\

*(result)=(a)*c__+(b)*c__;

下面的代码编译会出现问题:

if(.)

scale(r,a,b,c);

///多余的分号导致编译错误elseelse

正确的写法应该是:

do{\

}while(0)

c)如果参数是表达式并且在宏定义中出现多次,尽量避免重复计算.

这也是上面例子中要引入变量c__的原因.但这会限制变量c__的类型.

d)宏缺乏返回值

2.3编译器内部函数(CompilerIntrinsics)

绝大部分C编译器都知道内部函数(Intrinsicfunctions).它们是特殊的

inline函数,由编译器提供使用.这些函数用外部实现来代替.

gcc2.96的内部函数有

*__builtin_alloca:

动态分配栈上内存

dynamicllyallocatememoryonthestack

*__builtin_ffs:

findfirstbitset

*__builtin_abs,__builtin_labs:

absolutevalueofaninteger

*__builtin_fabs,__builtin_fabsf,__builtin_fabslabsolutevalueoffloating-pointvlaue

*__builtin_memcpycopymemoryregion

*__builtin_memsetsetmemoryregiontogivevalue

*__builtin_memcmpcomparememoryregion

*__builtin_strcmp

*__builtin_strcpy

*__builtin_strlen

*__builtin_sqrt,__builtin_sqrtf,__builtin_sqrtl

*__builtin_sin,__builtin_sinf,__builtin_sinl

*__builtin_cos,__builtin_cosf,__builtin_cosl

*__builtin_div,__builtin_ldivintegerdivisionwithrest

*__builtin_fmod,__builtin_fremmoduleandremainderoffloating-pointvalue

不能保证所有内部函数在所有平台上都定义了.

关于intrinsicfunction,有一个很有用的特性:

如果参数在编译时是

常数,那么可以在编译时计算其值.

例如strlen("

foobar"

)有可能在编译时就计算好.

2.4__builtin_constant_p__builtin_constant_p并不属于intrinsicfunction,它是一个类似于

sizeof的操作符.

__builtin_constant_p接收一个参数,如果该参数在运行时是固定不变

的(constantatruntime),那么就返回非0值,表示这是一个常量.

例如,前面的add函数可以在进一步优化:

#defineadd(a,ptr,type)\

(__extension__\

(__buildtin_constant_p(type)\

?

((a)+((type)==0\

*(int*)(ptr):

*(longint*)(ptr)))\

add(a,ptr,type)))

如果第三个参数为constant,那么这个宏将改变add函数的行为;

否则

就调用真正的add函数.这样尽量在编译时计算,从而提高了效率.

2.5type-genericmacro

有时侯我们希望宏对不同的参数数据类型,能正确处理不同数据类型并表现

相同的行为,可以借助__typeof__

例如前面的scale

#definetgscale(result,a,b,c)\

__externsion____typeof__((a)+(b)+(c))c__=(c);

这里,c__自动拥有返回值类型,而不是前面固定写的int类型.

__typeof__(o)定义了与o相同的类型.

__typeof__的另外一个用途:

被ISOC9x用于tgmath中,从而

实现一些对任意数据类型(包括复数)都适用的数学函数.

错误示例:

#definesin(val)\

(sizeof(__real__(val))sizeof(double)?

(sizeof(__real__(val))==sizeof(val)?

sinl(val):

csinl(val))\

(sizeof(__real__(val))==sizeof(double)?

sin(val):

csin(val))\

sinf(val):

csinf(val))))

上面这个宏的意思是:

如果val是虚数(即sizeof(__real__(val))!

=sizeof(val)),

那么对val调用csinl,csin和csinf

如果val是实数,且比double精度高,即

sizeof(__real__(val))sizeof(double)),那么对val调

用sinl,就longdouble,否则调用sin或者sinf.

sinl:

相当于sin(longdouble)

sin:

相当于sin(double)

sinf:

相当于sin(float)

csin:

对应的复数sin函数

但是这个宏是有错误的,由于整个宏是一个表达式,表达式是有静态

的类型的,能代表该表达式的数据类型必须有足够的精度来表示各种值,

所以这个表达式的最终数据类型就是complelongdouble,这并不是我们

期望的.

正确的实现方法是:

({__typeof__(val)__tgmres;

if(sizeof(__real__(val))sizeof(double))\

if(sizeof(__real__(val))==sizeof(val))\

__tgmres=sinl(val);

else\

__tgmres=csinl(val);

elseif(sizeof(__real__(val))==sizeof(double))\

__tgmres=sin(val);

__tgmres=csin(val);

__tgmres=sinf(val);

__tgmres=csinf(val);

__tgmres;

}))

上面对__tgmres赋值的6个分支中,真正会执行的那个分支是不存在精度

损失的;

其他分支都会作为deadcode被编译器优化掉

3.helpthecompiler

GNUC编译器提供一些扩展来更清晰的描述程序,从而帮助编译器生成代码.

3.1不返回的函数(FunctionsofNoReturn)

大项目一般都至少有一个用于严重错误处理的函数,这个函数体面的结束应用

程序.这个函数一般情况下不会被编译器优化,因为编译器不知道它不返回.

voidfatal(.)__attribute__((__noreturn__));

voidfatal(.)

//printsomemessageexit

(1);

//applicationcode

if(d==0)

fatal(.);

elsea=b/d;

函数fatal保证不会返回,exit函数也不返回.因此可以在

函数原型上加上__attribute__((__noreturn__)).

如果没有noreturn的标记,gcc会把上面的代码翻译成

下面的形式(伪代码):

1)comparedwithzero2)ifnotzerojumpto5)

3)callfatal4)jumpto6)

5)computeb/dandassigntoa

6).

如果有noreturn标记,gcc可以优化代码,省略4).对应

的源代码为

a=b/d;

3.2常值函数(constantvaluefunctions)

有些函数的值仅仅取决于传入的参数,这种函数没有副作用,我们称之

为purefunction.对于相同的参数,这种函数有相同的返回值.

举例说明:

htons函数要么返回参数(如果是big-endian计算机),要么

交换字节顺序(如果计算机是little-endian).这个函数没有副作用,是

一个purefunction.那么下面的代码可以被优化:

shortintserver=.

while

(1)

structsockaddr_ins_in;

memset(&

s_in,0,sizeofs_in);

s_in.sin_port=htons(serv);

优化后的结果为:

serv=htons(serv);

s_in.sin_port=serv;

从而减少循环中执行的代码,节省CPU.

但是编译器并无法知道函数是否是purefunction.我们必须给

purefunction显著的标记:

externuint16_thtons(uint16_t__x)__attribute__((__const__));

__const__可以用来标记purefunction.

3.3DifferentCallingConventions

每种平台都支持特定的callingconventions以便由不同语言和编译器写的

程序/库能够一起工作.

但是,有时侯在某些平台上,编译器支持一种更高效的callingconvention.

在项目内部使用这种callingconvention不会影响系统的其他部分.

尤其是在Intelia32平台上,编译器支持多种不同于标准Unixx86的callingconvention,这有时侯会大大提高程序速度.GNUC编译器手册有更详细解释.

本节只讨论x86平台.

改变函数的callingconvention的两个办法:

1)命令行选项(commandlineoption):

这种方法不安全,所有函数(包括

exportedfunction)都受到影响

2)对单个函数设置functionattribute.

3.3.1__stdcall__

一般情况下,函数参数是通过栈来传递的,因此需要在某个位置调整栈指针.

ia32unix平台上标准的callingconvention是让调用方(caller)调整栈

指针;

因此可以延迟调整操作,一次同时调整多个函数的栈指针.

如果函数被标记为__stdcall__,这意味这个函数自己调整栈指针.在ia32

平台上,这不算是坏注意,因为ia32体系结构提供一个指令,能同时从函数

调用返回并调整栈指针.

示例:

int__attribute__((__stdcall__))

add(inta,intb)

returna+b;

intfoo(inta)

returnadd(a,42);

intbar(void)

returnfoo(100);

上面的代码翻译成汇编大致如下:

8add:

900008B442408movl8(%esp),%eax10000403442404addl4(%esp),%eax110008C20800ret

17foo:

1800106A2Apushl190012FF742408pushl8(%esp)

200016E8E5FFFFcalladd20FF21001bC3ret

27bar:

2800206A64pushl0290022E8E9FFFFcallfoo29FF30002783C404addl,%esp31002aC3ret

从上面的例子可以看出,add函数被标记为__stdcall__,foo

函数在调用add后直接返回,不需要调整栈指针,因为add函数

已经调整来指针(ret指令完成返回和调整指针操作);

bar函数调用foo函数,调用结束后必须调整栈指针.

由此可见,使用__stdcall__是有好处的;

但是,现代编译器都已经

很智能,能作到一次性为多个函数调用调整栈指针,从而使得生成的

代码更少速度更快.此外,以后的发展可能会出现更快的调用方式,

所以使用__stdcall__必须非常谨慎.

3.3.2__regparm____regparm__只能在ia32平台上使用,它能指明有多少个(最多3个)整数

和指针参数是通过寄存器来传递的,而不是通过栈传递.当函数体比较

短小,而且参数立刻就能使用时,这种方式效果很显著.

假设有下面的例子:

int__attribute__((__regparm__(3)))

{returna+b;

}

经过编译优化后,生成的代码时

9000001D0addl%edx,%eax100002C3ret

这个代码比起3.3.1中add的代码更高效.用寄存器传参数总是

很快.

3.4SiblingCalls

经常有这样的代码:

一个函数最后结束时是在调用另外一个函数.这种

情况下生成的伪代码如下:

//thisisinfunctionf1ncallfunctionf2n+1executecodeoff2n+2getreturnaddressfromcallinf1n+3jumpbackintofunctionf1n+4optionallyadjuststackpinterfromcalltof2n+5getreturnaddressfromcalltof1n+6jumpbacktocalleroff1

经过优化,f1在调用f2结束后可以直接返回.

3.5使用gotogoto有时侯提高效率

4.了解库(KnowingtheLibraries)

4.1strcpyvs.memcpystrcpy:

两个参数src和dest,逐个byte拷贝

memcpy:

三个参数,src,dest和size,按word拷贝

strncpy:

3个参数:

src,dest和length

退出条件:

遇到NUL字符或达到拷贝长度

逐个检查byte是否为NUL

追加NUL字符

非gcc内部函数

3个参数

达到拷贝长度

按word检查长度

不必追加NUL字符

gcc内部函数,特殊优化

类似的,mem*和对应的str*函数都存在差别.

mem*函数参数多些,一般情况下这不是问题,可以通过寄存器传参数;

但是当函数被inline的时候,寄存器可能不够,生成的代码可能稍微

复杂一些.

建议如下:

*尽量别使用strncpy,而使用strcpy

*如果要拷贝的字符串很短,用strcpy

*如果字符串可能很长,用memcpy4.2strcat和strncat

关于字符串操作的一个金口玉言(goldrule)是:

绝对不要使用strcat和strncat.

要使用这两个函数,必须知道长度,并准备足够的空间.定型

代码如下:

char*buf=.;

size_tbufmax=.;

if(strlen(buf)+strlen(s)+1bufmax)

buf=(char*)realloc(buf,(bufmax*=2));

strcat(buf,s);

上面的代码中,已经调用了strlen,strcat中会重复执行strlen

操作,因此更高效的作法是:

size_tslen;

size_tbuflen;

slen=strlen(s)+1;

buflen=strlen(buf);

if(buflen+slenbufmax)

memcpy(buf+buflen,s,slen);

4.3内存分配

malloc和calloc:

分配堆内存.

alloca分配栈内存.

malloc的实现:

从内核申请内存,可能会调用sbrk系统调用;

在某些系统上

如果申请的内存很多,可能会调用mmap来分配内存.malloc的内部实现会用

相关的数据结构来管理好申请内存,以便释放或者重新申请.因此调用malloc

的代价并不低.

alloca的实现相对简单得多,起码编译器能直接把它作为inline来编译,

alloca只是简单修改一下栈指针就可以了.而且,调用alloca后不需要调用

free函数来释放内存.free函数的代价也是不小的.

但是,alloca申请的内存只能用在当前函数中,而且alloca不适合用来申请

大量内存,很多平台系统出于安全考虑对栈的大小有限制.malloc的实现和

内核相关,能更好的处理大内存申请.

alloca总是成功的,因为它只是执行修改栈指针操作而已.因此alloca非常

适合在函数内部申请局部使用的内存,不比检查申请释放成功,也不必调用

free来释放内存,不仅提高性能还简化来代码.

示例如下:

inttmpcopy(constint*a,inta)

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 语文

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2