在C语言中函数调用是基于程序堆栈机制,函数调用会进行压栈操做,堆栈被用来保存函数返回地址、寄存器值、入参、局部变量等信息,在函数调用过程当中须要在栈上分配空间并对栈空间初始化,一个典型的程序堆栈入下图所示: 函数调用时,首先分配被调用函数的参数空间将其初始化,将其压入调用者的堆栈中,而后将返回地址压入调用者的堆栈。被调函数的堆栈开始依次保存了ebp寄存器、其它一些寄存器的值(用于存放上一个函数的部分局部变量)、对于不能存放在寄存器中的局部变量信息将会保存在栈空间中。了解了函数栈机制,经过对函数进行适当的调整就能够显著的优化性能。html
1:经过修改入参的传递方式的优化,一个典型的例子是做为结构体的入参,以下所示:编程
void Fun1(struct St st);性能优化
void Fun2(struct St *pSt);ide
Fun1须要在被调函数的栈上分配对应大小的空间并将结构体总体拷贝过去。入下图:函数
Fun2只须要分配一个地址空间而且保存结构体的地址信息,入下图:性能
2:经过使用非本地跳转来提高异常返回效率。优化
对于正常的函数调用返回须要一层层的解开函数调用栈,在异常状况下能够经过非本地跳转直接返回到调用点处理,避免解开多层的调用栈。spa
3:经过使用全局变量来替换函数的局部变量能够避免每次函数调用局部变量空间分配和初始化。设计
4:经过宏来替换函数调用能够避免栈的开销。3d
1:推迟计算
在程序中可能存在批量的资源申请,若是批量申请十分耗时而且存在部分资源不会立刻被使用或者可能不会用到,能够把资源分配的过程推迟到真正须要的时候。一个典型的例子是Linux的写时拷贝技术,其核心思想就是父子进程共享相同的物理空间,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。下面是一个经过引用记数模拟写时拷贝的例子:
#include "stdlib.h" typedef struct { int cnt; int data; }DATA; DATA* newData(int data) { DATA* pData = NULL; pData = (DATA*)malloc(sizeof(DATA)); pData->cnt = 1; pData->data = data; return pData; } void unReferData(DATA **ppData) { DATA* pData = NULL; pData = *ppData; if(NULL == pData) { return; } if (1 == pData->cnt) { free(pData); } else { pData->cnt--; } *ppData = NULL; return; } DATA* referData(DATA* pData) { pData->cnt++; return pData; } //copy-on-write void writeValue(DATA **ppData, int value) { DATA* pData = NULL; pData = *ppData; if (1 == pData->cnt) { pData->data = value; } else { pData->cnt--; *ppData = newData(value); } return; } int main() { DATA *pData1 = NULL; DATA *pData2 = NULL; unReferData(&pData1); pData1 = newData(1); unReferData(&pData2); pData2 = referData(pData1); writeValue(&pData2,2); return 0; }
2:预先计算
对于某些要求操做的能够预测性、低延迟的系统,要提升运行的性能,咱们经过能够提早获取资源,在系统启动过程当中就获取全部资源而且高效的存放起来,后期直接使用。好比运行过程当中动态内存的申请,其开销很大而且时间不固定,经过预测系统内存使用量,在启动期间申请全部动态内存并经过自定义的高效内存池来进行管理。
3:避免计算
一般一些好的编程习惯,可能会致使性能的恶化,好比数据块的初始化,在代码中常常能够看到malloc后立刻memset,而后再对数据块赋值,若是操做的内存块很大,对性能影响很明显。变量初始化是一个好的编程习惯,但若是跟性能冲突尽量避免这样的操做或者只对关键的数据进行初始化,避免大块数据的操做。
为了能使 CPU 对变量进行高效快速的访问,变量的起始地址应该具备某些特性,即所谓的 “ 对齐 ” 。例如对于 4 字节的 int 类型变量,其起始地址应位于 4 字节边界上,即起始地址可以被 4 整除。变量的对齐规则以下( 32 位系统):
Type |
Alignment( 默认的天然对齐 ) |
char |
在字节边界上对齐 |
short(16-bit) |
在双字节边界上对齐 |
int long (32-bit) |
在 4 字节边界上对齐 |
float |
在 4 字节边界上对齐 |
double |
在 8 字节边界上对齐 |
structures |
单独考虑结构体的每一个成员,它们在不一样的字节边界上对齐。 其中最大的字节边界数就是该结构的字节边界数 |
若是访问未对齐的地址须要两个总线周期来访问两次内存,而对齐的地址只须要一个总线周期来访问一次内存。编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种状况:第1、若是n大于等于该变量所占用的字节数,那么偏移量必须知足默认的对齐方式,第2、若是n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用知足默认的对齐方式。结构的总大小也有个约束条件,分下面两种状况:若是n大于全部成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;不然必须为n的倍数。
入下例所示当指定4字节对齐时:
#pragma pack(4)
struct A
{
short a;
int b;
short c;
};
#pragma pack()
假设a的初始化地址为0,则b的地址为4,c的地址为8,总共占用12个字节空间,为了对齐浪费了4个字节空间。
入下例所示当指定1字节对齐时:
#pragma pack(1)
struct A
{
short a;
int b;
short c;
};
#pragma pack()
假设a的初始化地址为0,则b的地址为2,c的地址为6,数据紧凑存放节省了存储空间,但因为b并无对齐致使对b的操做须要两次访问内存。
以上两种声明方式分别针对时间和空间性能的优化,但若是咱们合理的设计结构体中的成员位置,则能够兼顾时间和空间的性能。入下例所示:
#pragma pack(1)
struct A
{
short a;
short c;
int b;
};
#pragma pack()
假设a的初始化地址为0,则c的地址为2,b的地址为4,数据紧凑存放并且各个变量都是对齐的。