首先介绍下编译器所提供的编译优化功能,这些都比较简单:
普通优化
Linux |
功能 |
-O0 |
禁止优化 |
-g |
生成标记 |
-O1 |
优化二进制代码 |
-O2 |
优化速度(默认) |
-O3 |
优化数据缓存 |
过程间优化
Linux |
功能 |
-ip |
优化编译单个文件 |
-ipo |
通过内联函数优化交叉编译多个文件 |
自动并行优化
Linux |
功能 |
-parallel |
对某些代码做自动并行优化 |
-par_report[n] |
记录优化过程,汇报结果 |
基于CPU的矢量化优化
Linux |
功能 |
-Xw |
为Pentium4等支持MMX、SSE和SSE2指令的处理器做专门优化 |
-xP -axP |
为Core等支持MMX、SSE、SSE2和SSE3指令的处理器做专门优化 |
OpenMP优化
Linux |
功能 |
-openmp |
打开OpenMP优化功能 |
-openmp-report |
提供优化报告、错误 |
支持Intel线程检查器的编译
Linux |
功能 |
-tcheck |
支持线程检查器检测线程 |
前面介绍的都是利用编译器的优化选项开关来进行性能的优化,一般这些优化选项是针对所有代码或者针对某个源文件的所有函数时,有的时候我们可能只希望只对某个热点函数或者热点的代码块进行优化,这个时候就可以使用Intel编译器提供的语言扩展功能。关键字pragma是属于C语言中的关键字,但是具体的pragma的含义与作用是由具体的编译器来解释的,因此值得注意的是采用pragma进行优化可能会影响应用的移植性。要使用pragma,只要在源代码中的相应位置增加一行:
#pragma
Pragma |
描述 |
#pragma optimize("",on|off) |
打开和关闭优化支持 |
#pragma optimization_level n |
控制采用的优化级别 |
#pragma loop count min=n,max=n,avg=n |
告诉编译器循环估计的执行次数 |
#pragma nounroll |
告诉编译器不要展开循环 |
#pragma unroll |
告诉编译器循环展开的次数 |
#pragma distribute point |
控制循环分割 |
#pragma ivdep |
告诉编译器没有数据依赖 |
#pragma novector |
告诉编译器不要把循环自动矢量化 |
#pragma vector |
怎么样自动矢量化,哪些影响矢量化选择的因素可以忽略掉 |
#pragma vector nontemporal |
自动矢量化的代码中采用流式存储 |
上面的表格列出一些常用的pragma。首先要介绍的第一类pragma是控制函数是否要进行优化,要进行哪种程度的优化。要控制函数是否进行优化,可以通过optimize来说明,#pragma optimize("",on|off)用于打开和关闭优化支持。#pragma optimization_level n用于控制采用的优化级别,n取值为0到3,分别对应着自动化编译选项中的/Od、/O1、/O2、/O3。考虑下面代码,func1不进行优化,而func2要进行/O1级别的优化,func3进行/O2级别的优化:
1. #pragma optimize("", off) 2. 3. func1() { 4. 5. ... 6. 7. } 8. 9. #pragma optimize("", on) 10. 11. #pragma optimization_level 1 12. 13. func2() { 14. 15. ... 16. 17. } 18. 19. #pragma optimization_level 2 20. 21. func3() { 22. 23. ... 24. 25. } 26. #pragma optimize("", off) func1() { ... } #pragma optimize("", on) #pragma optimization_level 1 func2() { ... } #pragma optimization_level 2 func3() { ... } |
对循环的优化对于应用的性能至关重要,Intel编译器也提供了pragma来对于循环优化进行控制。首先是#pragma loop_count min=n,max=n,avg=n,用于告诉编译器紧接着的循环估计的最小执行次数、最大执行次数和平均执行次数,显然这个pragma有助于帮助编译器是否要进行循环展开,是否要进行自动矢量化等。其次是对循环展开的控制,#pragma nounroll用于告诉编译器不要对紧接着这个pragma的循环进行循环展开,而#pragma unroll(n)用于告诉编译器紧接下来的这个循环可以进行循环展开,循环展开的次数最多为n次。如果n等于0,则表示不进行循环展开,而采用#pragma unroll表示由编译器决定循环展开的次数。编译器的选项开关中也提供了循环展开的控制(/Qunroll,/Qunroll:n),不过如果循环之前包含#pragma unroll,则pragma优先。下面的代码中,for循环体会展开4次:
1. void unroll(int a[], int b[]) 2. 3. { 4. 5. #pragma unroll(4) 6. 7. for (int i = 1; i < 100; i++) { 8. 9. b[i] = a[i] + 1; 10. 11. } 12. 13. } 14. void unroll(int a[], int b[]) { #pragma unroll(4) for (int i = 1; i < 100; i++) { b[i] = a[i] + 1; } } |
另外一个循环控制的pragma是#pragma distribute point,它用于控制循环分割,该pragma可以放在循环之前也可以放在循环体中,如果放在循环体之前,表示由编译器来决定如何对循环进行分割,而放在循环体中,则告诉编译器循环体从pragma所在的位置分割为两个循环。考虑下面的代码
1. int i; 2. 3. for (i=0; i< NUM; i++) { 4. 5. a[i] = a[i] +i; 6. 7. b[i] = b[i] +i; 8. 9. #pragma distribute point 10. 11. x[i] = x[i] +i; 12. 13. y[i] = y[i] +i; 14. 15. } 16. int i; for (i=0; i< NUM; i++) { a[i] = a[i] +i; b[i] = b[i] +i; #pragma distribute point x[i] = x[i] +i; y[i] = y[i] +i; } |
上面的循环就变成:
1. for (i=0; i< NUM; i++) { 2. 3. a[i] = a[i] +i; 4. 5. b[i] = b[i] +i; 6. 7. } 8. 9. for (i=0; i< NUM; i++) { 10. 11. x[i] = x[i] +i; 12. 13. y[i] = y[i] +i; 14. 15. } 16. for (i=0; i< NUM; i++) { a[i] = a[i] +i; b[i] = b[i] +i; } for (i=0; i< NUM; i++) { x[i] = x[i] +i; y[i] = y[i] +i; } |
如果一个循环有数据依赖或者编译器无法确定是否具有数据依赖,为了安全起见编译器会选择不进行自动矢量化。为了处理这种情况,Intel编译器提供了几个pragma来控制是否进行自动矢量化。#pragma novector用于告诉编译器紧接着的循环即便可以自动矢量化也不要进行;#pragma ivdep用于告诉编译器紧接着的循环没有数据依赖,比如下面的代码片断,因为不知道k的取值是否会小于0,从而可能会出现数据依赖,如果你知道在调用func时k一定为会大于0,则可以通过ivdep来告诉没有数据依赖,从而可以把该循环自动矢量化。
1. void func(int *a, int k, int c) 2. 3. { 4. 5. int i ; 6. 7. #pragma ivdep 8. 9. for (i = 0; i < MAX; i++) 10. 11. a[i] = a[i + k] * c; 12. 13. } 14. void func(int *a, int k, int c) { int i ; #pragma ivdep for (i = 0; i < MAX; i++) a[i] = a[i + k] * c; } |
一个循环没有数据依赖并不代表其一定会被自动矢量化,可能还要考虑到自动矢量化的开销、内存访问是否步长、数据是否对齐等情况,#pragma vector用于帮助编译器来决定是否对于没有数据依赖的循环进行自动矢量化。其中#pragma vector always表示只要可以就进行自动矢量化,而不考虑性能方面的因素。#pragma vector {aligned | unaligned}用于告诉紧接着的循环体对于内存的访问是否是对齐的,如果是对齐的,则在其他条件(没有数据依赖)满足的情况下可以进行矢量化。另外_assume_aligned(var,n)也可用来告诉变量var是n比特对齐的。
1. #pragma vector aligned // assume aa and bb are aligned by 16 2. 3. //Line above can be replaced as: 4. 5. // __assume_aligned(aa,16); __assume_aligned(bb,16); 6. 7. for (i = 0; i < MAX; i++) { 8. 9. aa[i] = bb[i] + 2.0f; 10. 11. } 12. #pragma vector aligned // assume aa and bb are aligned by 16 //Line above can be replaced as: // __assume_aligned(aa,16); __assume_aligned(bb,16); for (i = 0; i < MAX; i++) { aa[i] = bb[i] + 2.0f; } |
#pragma vector nontemporal用于告诉编译器在生成自动矢量化的代码时进行流式存储,即把数据直接写到内存中而不用通过缓存。
#pragma vector nontemporal 1. 2. for (i = 0; i < MAX; i++) 3. 4. a[i] = 1; 5. |