原创  优化报告3-算法篇 收藏

拜读了众选手的优化报告,大家对各个优化步骤已经解释得非常详细了,一
路读过,颇有收获。在此,我对优化的过程不再赘述,仅仅就特色之处做一
些说明。

首先要强调,虽然我使用了汇编语言,但是,起决定性作用的不是汇编,而
是算法。单纯把高级语言翻译成汇编是无法写出高效的程序的,使用汇编意
味着要根据指令设计量体裁衣的算法。希望大家别过分关注汇编,而是要留
心一些原理上的东西。

//////////////////////////////////////////////////////////////////
优化ComputePot的算法。

算法设计最终要达到的目的是:
1 降低指令延迟。
2 减少指令数目。
3 增加并发程度。

因此,有如下几个参考因素:
1 优先使用低延迟指令,比如使用rsqrt进行迭代。
2 合理利用寄存器,减少内存读写次数。
3 精心排列指令,减少指令依赖关系。
4 调整运算顺序,减少CPU运算单元竞争。

对于后两条,我想有必要解释一下。CPU的执行单元进行一次计算是需要多
个周期的,比如一次mulpd需要6个时钟周期。因此,依赖该运算结果的指令
是无法执行的。虽然目前的处理器能够乱序执行,但是过多的依赖关系还是
会影响效率。执行单元的吞吐量很有限,比如两次乘法操作就必须间隔一个
时钟周期,而一个乘法和一个加法操作却可以在同一个周期执行,合理利用
运算单元才可以让CPU并发执行指令。

有一个关键性的优化手段,很多人都这样做了,即:使用rsqrtps指令计算
近似值,用单精度进行第一次迭代,用双精度进行第二次迭代。

在确定了迭代运算这个大前提后,我们便需要再次设计算法,使得性能得到
进一步提升。在我的算法里,每次循环要进行8组数据的运算,8组数据的每
一行需要四个寄存器来存放。一共有八个寄存器可以使用,因此要很好地考
虑如何充分利用有限的寄存器资源。

求和阶段,八个寄存器存放数据如下:
xmm0~xmm3 : 求和之后的四对结果。
xmm4~xmm6 : 逐行读取数据,使用这三个寄存器来进行运算。
xmm7 : 存放r[0][i],r[1][i],r[2][i]。用到哪行数据就提前读取。

单精迭代,八个寄存器存放数据如下:
xmm0~xmm3 : 求和之后的四对结果乘上“-0.5”。
xmm4~xmm7 : 单精度运算和第一次迭代。

双精迭代,这个阶段寄存器使用就比较简单
xmm0~xmm3 : 求和之后的四对结果乘上“-0.5”。
xmm4~xmm7 : 双精度近似值。

双精度迭代后,便可以求和。

在每个循环的开始和结束处,都会有较大损耗。循环开始需要读取数据,内
存操作较多;循环结束需要计算数据,指令依赖较多。通过调整指令的顺
序,使循环首尾相接,可以减少指令的依赖关系,减少运算单元的竞争,从
而增加指令并发度。

不足八组的数据就无法使用这个算法了,因此还需要设计四组数,两组数和
一组数的算法,这一部分虽然是个零头,不过也轻视不得。这部分在算法上
并无特色,略过。

//////////////////////////////////////////////////////////////////
优化updatePositions的算法。

这部分的优化点在于rand函数的实现。由于运行库是线程安全的,这个函数
包含了很多系统调用,自行实现rand的函数会使效率稍有提升。公开的源码
里,也有多人这样做了(HouSisong,flyingdog)。

我实现了一个汇编版本的updatePositions,这个函数的特点是:一次性生
成两个随机数。有兴趣可以参考源码。

//////////////////////////////////////////////////////////////////
针对多核进行优化。

多线程运算需要解决两个问题:线程同步和运算分块。线程同步要尽可能
少,并且尽量使用轻量的操作;运算块不可太小,否则会加重线程同步的负
担。因此,优化分成以下几个步骤:
1 把运算重新分块,减少循环次数,使得每个循环的运算时间增长。
2 使用局部变量代替全局变量,以免不必要的线程同步操作。
3 使用一个数组来记录各分块的计算结果,待运算结束后再把数组累加到pot
变量里,这样可以进一步减少各线程的同步操作。
4 用_InterlockedDecrement来索取计算块,这是非常轻量的线程同步方法。

先使用OpenMP进行多线程化,分别对Intel编译器和VS2005的OpenMP进行了测
试。通过比较发现VS2005自带的OpenMP在后台有其它程序运行时,性能非常不
稳定,Intel编译器的OpenMP则无此问题。OpenMP对线程数比较敏感,跟处理
器个数相同的线程数会获得最佳性能。

看到论坛上有人说,“使用三个线程会非常慢,很多时间都在线程切换上了”。
实际上,这有可能是VS2005的OpenMP线程调度有问题,IntelC++的编译出来的
程序就不会有这么大的差别。建议用四个线程试试看,比三线程要高效很多,
这说明线程切换的开销不会很大。

最终的程序自行实现了多线程代码,替代OpenMP。这个实现并没有效率改变,
但是在其它程序竞争CPU资源的时候依然能够保障稳定的速度,并且对于线程
数不敏感,对计算代码的分块方案不敏感。

发表于 @ 2007年01月21日 15:48:00 | 评论( loading... ) | 编辑| 举报| 收藏

旧一篇:C++,C# 效率研究 | 新一篇:优化报告2-源码篇

  • 发表评论
  • 评论内容:
  •  
Copyright © dwbclz
Powered by CSDN Blog