本文是帮助用户移植应用程序到Intel® Xeon Phi™处理器的最佳化方法的汇总,用户在移植现有应用程序时,利用下面的代码改动建议及Intel® 编译器的开关使用建议将有助于得到更好的程序运行性能。
1.有效地利用编译器的开关
- 使用编译器选项 -mmic 来编译运行于协处理器上的本地程序。
- 选择编译器多个开关来最大化性能,尤其是当程序对最低浮点精度有要求时。
- 浮点计算
- 当单精度运算满足计算要求时,优先考虑单精度浮点计算,再考虑双精度浮点计算;
- 利用不同的精度控制开关:-imf-*, -[no-]prov-*;
- 编译器仅在明确指明低运算精度编译器选项时才生成低精度运算代码,否则使用缺省的运算精度来生成代码,在使用当前的编译器时,你可以使用 -fimf* 之类的选项,例如:-fimf-domain-exclusion=<n1> -fimf-accuracy-bits=<n2> -fimf-precision=low -fimf-max-error=<n3_ulps>;
- 某些编译器选项的组合有利于更好地控制生成的浮点精度,比如:
- -fimf-precision=low -fimf-domain-exclusion=15(编译生成单精度/双精度可用的最低精度代码序列)
- -fimf-domain-exclusion=15 -fimf-accuracy-bits=22(比缺省的双精度浮点配置生成较低一些的精度)
- -fimf-domain-exclusion=15 -fimf-accuracy-bits=11(比缺省的双精度浮点配置生成更低的精度以及比缺省的单精度浮点配置生成较低一些的精度)
- -fimf-precision=low
- -fimf-max-error=2048 -fimf-domain-exclusion=15(比缺省的最大误差为4的ulp-最后一位的进退位-产生更低的精度)
- 这些选项会对编译器在生成向量化代码及标量化代码时起作用。对于选项的详细描述请参照编译器参考手册中的 "Floating-Point Options"部分(Compiler Reference > Compiler Option Categories and Descriptions > Floating-Point Options),此文档可以在http://software.intel.com/en-us/articles/intel-c-composer-xe-documentation 处找到。
- 浮点计算
2.代码的局部优化调整
- 在比较两个数值的大小时,对数值进行平方比较而不要比较二者的平方根;
- 用乘某数值倒数的形式来替代除以这个数值。
3.对应用程序线程化
- 利用编程模型及编译器丰富的特性支持来增加线程的并行度:
- 可以利用消息传递接口MPI库来实现多进程模型,并且在每个进程中利用OpenMP*库来实现线程的并行化,需要的时候也可以使用Intel® Cilk™、Intel® Threading Building Blocks (Intel® TBB)或Pthread库来更好地改动代码;
- 不建议使用编译器的自动向量化特性来期待达到很优的性能,具体可参考编译器的使用手册http://software.intel.com/en-us/articles/intel-parallel-studio-xe-for-linux-documentation/#ccomposer。
- 在程序需要时,可以使用MPI来增加程序的并行度:
- 利用选项 -n 来设置MPI进程的数量:mpiexec.hydra -n ./a.out ;
- 利用选项 -host 将MPI程序在主机及协处理器上运行:mpiexec.hydra –host ./a.host : -host ./a.mic;
- 利用环境变量OMP_NUM_THREADS设置每个进程中OpenMP线程的数量;
- 利用环境变量I_MPI_PIN_DOMAIN控制MPI进程的affinity;
- 用I_MPI_DEBUG=5的设置来查看MPI进程的affinity map;
- 例如:mpiexec.hydra -env I_MPI_PIN_DOMAIN 12 -env KMP_AFFINITY balanced -env OMP_NUM_THREADS 9 -env I_MPI_DEBUG 5 -n 20 a.out
- 用Intel® Trace Analyzer and Collector来发现程序运行时的负载不均衡等性能问题。
- 控制亲和度及防止线程数目过多:
- 使用环境变量KMP_AFFINITY控制MPI/OpenMP的亲和度,从而OpenMP线程将不会迁移出所属的进程所在的CPU核:
- 每一个OpenMP线程也可以决定在哪一个操作系统进程上执行并且利用kmp_set_affinity API 接口在运行时与之绑定,对应的环境变量是KMP_AFFINITY,在编译器手册中的"Thread Affinity Interface"话题下可以看到更多的描述(http://software.intel.com/en-us/articles/intel-parallel-studio-xe-for-linux-documentation;
- Compact的亲和度设置会在映射多个线程时按照顺序依次将各个CPU核分配满再分给下一个核,Scatter的亲和度设置会将多个线程平均地分配到连续的各个核上,然后将余数的线程继续绑定到相同的核上,Balanced的亲和度设置仅可用于协处理器,当多个相邻线程之间有locality共享时,此策略可以帮助达到比Scatter设置更优的性能;
- 亲和度的设置于算法和数据结构的实现有很大关联,所以可以通过预试的方式来找到最适合的线程分散方式。用户可以通过API调用在代码中直接改的affinity的设置,尽管这会有运行时的性能开销。
- 使用环境变量KMP_AFFINITY控制MPI/OpenMP的亲和度,从而OpenMP线程将不会迁移出所属的进程所在的CPU核:
4.代码的进一步优化调整
- 类型转换
- 在C/C++语言中,浮点常数在未加f或F后缀时默认定义为双精度,这会使仅需要单精度运算的程序损失很大的性能,因为一来这会导致很多不必要的单精度与双精度之间的转换代码的开销,而且也会由于双精度数值的引入导致仅有一半的向量寄存器的宽度有效可用;
- 在可能的时候尽量多用函数的单精度版本,比如sinf() vs. sin(),原因同上。
- 有符号 vs. 无符号类型
- 无符号类型的数值可能会有overflow的处理开销而有符号数就没有此类问题,所以在有符号数可以满足数值计算要求的情况下尽量多使用前者。
- 浮点精度
- 将浮点数的除法改为浮点数乘以另一个数倒数的形式,尽管编译器有时会自动进行此类优化,尤其是在被除数是常数时。
5.系统性调优
禁用Red Hat* Enterprise Linux 6.2上的 intel_idle 驱动以解决总线速度的问题,因为在使用offload pragma时,此驱动的作用会限制运行时协处理器与Host之间PCI Express* 的数据传输性能。
通过传递 intel_idle.max_cstate=0 为内核启动参数(从而使系统使用 acpi_idle 配置)并且重启系统,即可解决此数据传输性能下降的问题,如果此改动并未使性能有所提升的话,系统的BIOS可能也需要升级到最新的版本,Period。
Optimization Notice
http://software.intel.com/en-us/articles/optimization-notice/