2011.11.15,阅读《大话处理器》第6章《编写高效的代码—时间就是生命》,主要内容笔记如下。
Profile在美剧《犯罪心理》中的意思是剖析犯罪分子的心理。在软件开发中,就叫软件性能剖析。性能剖析工具分析每个函数(有的工具能分析到每个循环)的执行时间。
常用的性能剖析软件有:IBM的Rational Quantify、Intel的VTune、AMD的CodeAnalyst,DSP的软件集成开发环境中也自带有这种工具。
(一) 减少指令数;
1. 使用更快的算法—算法,程序设计的灵魂;(高斯的故事)
2. 选用合适的指令—合适的人做合适的事;(主要是处理器的专用汇编指令)
3. 降低数据精度—比特也要省着用;(单精度浮点数绝对值函数fabsf()比双精度fabs()快)
4. 减少函数调用—不要老打断我;
a) 将小函数直接写成语句;
b) 将小函数写成宏;
c) 将函数声明为内联函数;
5. 空间交换时间;(Fibonacci数列某项值的计算、Google百度搜索引擎算法)
6. 减少过保护—打不破的部门墙;(函数入参检查、保护可适当去掉)
(二) 减少处理器不擅长的操作—不要逼我做我不喜欢的事情;
1. 少用乘法;(可转化为移位运算)
2. 少用除法、求余;
3. 在精度允许的条件下,将浮点数定点化;
《反恐精英》中扔了烟雾弹之后的半透明烟雾效果,使用图像的alpha混合:将两张图像进行半透明的混合来实现,混合后新图像的每个像素的颜 {MOD}值为:
Pixel_C =(int)(Pixel_A * alpha + Pixel_B * (1 – alpha));
alpha为透明度,是介于0和1之间的小数,随着之间的推移,烟雾图像的alpha值越来越小。当alpha为0时,烟雾效果完全消失。
由于浮点运算耗时,可以将alpha定点化为0~32之间的一个整数,0表示完全显示B图像,32表示完全显示A图像,中间分为32个等级,代码变为
Pixel_C =(Pixel_A * alpha + Pixel_B * (32 – alpha) + 16) >> 5);
由于全部都是定点语句,执行时间大幅减少。
4. 尽量减少分支;(if和switch跳转语句会打乱流水线执行,影响效率)
5. 将最有可能进入的分支放在if中,而不是else中;
(三) 优化内存访问
1. 少使用数组,少使用指针;(用局部变量代替)
2. 少使用全局变量;(用局部变量代替)
3. 一次多访问一些数据;
4. 数据对齐访问;对2字节变量,它的起始地址应该为2的整数倍;对于4字节变量,它的起始地址应该为4的整数倍;对于8字节变量亦是如此;
5. 大数据结构时的Cacheline对齐;(Intel处理器的Cache line大多为64 byte,在对大数据结构分配内存时,起始地址最好为64 byte的整数倍,这样Cachemiss的次数最少。)
6. 程序、数据访问符合Cache的时间、空间局部性;(两重for循环处理二维数组例子;程序组织、存储符合Cache局部性原则)
7. 多线程编程时,避免falsesharing;
8. 自己管理内存的动态分配;
9. 隐藏数据搬移时间;(根据处理器支持,用DMA将SRAM中数据搬移到处理器、Cache预取机制)
(四) 充分利用编译期进行优化—编译器:我才是优化第一高手;
1. 编译器提供了分级优化选项;
2. 简化表达式;(消除重复计算)
3. 提取公共代码;(把两个分支中的公共代码提到外面)
4. 循环展开、软件流水;(比如可以使用编译期的预编译指令)
5. 自动向量化;(使用关键字restrict)
6. 高效的数据组织;(编译器对变量、函数分配存储空间,减少Cache miss)
7. 指令并行化;
(五) 利用多核加速程序—人多力量大;
1. 并行计算;(分工:任务划分、数据划分、数据流划分;Amdahl’s law,阿姆达尔定律:串行与并行分开;多线程编程)
2. OpenMP;(并行编程框架,适合共享内存系统、多核处理器)
1. 减少重复计算
for (i =0; i < strlen(pswd); i++)
{
if (isNum(pswd[i]))
{
j++;
}
}
...
2. 减少分支
for (i =0; i < 100; i++)
{
if (i % 2 == 0)
{
a[i] = x;
}
else
{
a[i] = y;
}
}
修改后:
for (i =0; i < 100; i+=2)
{
a[i] = x;
a[i+1] = y;
}
3. 程序、数据访问符合Cache的时间、空间局部性:
for (j< 0; j < 500; j++)
{
for (i < 0; i < 500; i++)
{
sum += a[i][j];
}
}
优化后:
for (i< 0; i < 500; i++)
{
for (j < 0; j < 500; j++)
{
sum += a[i][j];
}
}
4. 自动向量化:
void add(float *restrict a, float *restrict b, float *restrict c)
{
int i;
for (i = 0; i < 4; i++)
{
c[i] = a[i] + b[i];
}
}
retrict关键字告诉编译期a、b、c三个指针指向的空间是没有交叠的,这样编译器可对它们做合适的优化。编译器能使用SIMD指令来优化。
/* VC 6.0帮助文档 */
i = 100;
while (i< 0)
{
i += x + y;
*p = i;
}
如果使用Assume No aliasing编译选项,编译期能将x+y放到循环外面;否则不会,因为指针p可能指向x或y;
5. 指令并行化:
x = a +b;
y = x +c;
z = y +d;
优化为:
x = a +b;
y = c +d;
z = x +y;