|
在其他论坛上看来的,觉得比还不错;大家看看吧。
原帖由tpy显卡论坛的:开普勒,完成。赞~~~
(本帖欢迎技术讨论,谢绝漫骂)
今天看到不少人在讨论x87和SSE指令,各种观点满天飞。例如有认为x87是古老技术,现在已经是SSE大行其道,x87已经绝迹,也有人说某物理引擎用x87是故意为之,其实用SSE开发更简单。也有认为用x87也很正常,不过是没有针对SSE做优化。本人根据自己的一些编程经验来谈谈x87和SSE。
开发环境就选VS2008吧,这个应该是x86平台C++开发环境中使用最广泛的,有普遍意义。
1、x87
物理引擎是计算物体运动的,相信一定少不了矢量计算。举个简单例子,例如有两个4D矢量A和B,我们需要把它们相加,放到矢量C中。C语言中是这样写的:
C.x = A.x + B.x;
C.y = A.y + B.y;
C.z = A.z + B.z;
C.w = A.w + B.w;
(变量定义我就不写了)
也就是说各元素单独相加。看看用VS2008在Release模式下用默认选项编译出来的汇编代码:
嗯,一点SSE的影子都没有,编译器笨得可以啊,全用x87。
2、SSE/SSE2
既然编译器那么笨,我只好亲自动刀子了。xyzw四个浮点数一起算加法,可以打包在一个XMM寄存器里用SSE2指令计算嘛,代码改成:
__m128 result = _mm_add_ps(_mm_load_ps((float *)&A), _mm_load_ps((float *)&B));
_mm_store_ps((float *)&C, result);
看看编译后的汇编:
嗯,不错,一个addps指令(属于SSE2指令集)搞掂4个浮点数加法,比起之前调用4次fadd要好多了(理论上吞吐量可以达到4倍)。
不过,貌似很多计算不是那么简单,能直接打包成SSE2的,例如以下两个很常用的计算:
矢量点乘(求功、求夹角等):
r = a.x * b.x + a.y * b.y + a.z * b.z;
矢量叉乘(求法线):
r.x = a.y * b.z - a.z * b.y;
r.y = a.z * b.x - a.x * b.z;
r.z = a.x * b.y - a.y * b.x;
交叉相乘或相加,无法直接打包,如果硬要打包计算,宽度又不够,而且要浪费很多打包指令,效率低下。
不过聪明的网友应该会想到,很多情况下需要计算的矢量是很多个的,如果放在数组里的话,完全可以每4个矢量打包在一起计算(把循环向量化)。例如计算叉乘,完全可以把a[0].x到a[3].x放到一个XMM寄存器,a[0].y到a[3].y放到一个XMM寄存器……那样a[0].y*b[0].z、a[1].y*b[1].z、a[2].y*b[2].z、a[3].y*b[3].z就可以用一条SSE2指令计算,以此类推。
不过,这样做的话属于带stride访存,而SSE访存模式不支持Gather/Scatter,需要借助中间操作把需要打包的数据排列成连续128位,访存效率较低。而为了优化访存效率,可以在数组存储形式上做优化,这就是 Array of Structures (AoS) 和 Structure of Arrays (SoA) 的问题了,呵呵,再引申下去就扯远了。
不管怎样,使用SSE、SSE2指令集总的来说肯定比x87性能要高一些。例如本人之前写过一个SHA-1编码的纯SSE版本,比非SSE版本快1倍左右。不过效率是以牺牲可读性为代价的,而且这种直接写成SSE Intrinsics的方式,需要花大量时间去做人工打包的工作。还有就是某些程序员的优化水平实在厉害,优化出来完全变了个样,在榨干性能的同时,代码也非常让人费解。
当然如果有人直接把一些常用的计算过程写成SSE优化库,直接提供给开发者使用,那就最好。
3、VS2008对SSE/SSE2的优化
刚才用默认编译选项编译C代码,没有发现编译出SSE指令。不过其实VS2008有SSE指令集选项,只是默认是关闭的:
例如我在这里指定使用SSE2指令集,再重新编译刚才那个C代码:
嗯,VS2008编译出SSE2指令了。不过仔细看看,杯具啊,用是用了,但全是带ss的,即它用128位的XMM寄存器来放32位的单精度数据,而每条SSE2指令仅仅计算1个数而非4个数,打包失败。不过XMM寄存器的访问怎么也应该比x87的浮点寄存器快一点,效率应该还是有一丁点提升的。
再来测试一下下面这个代码:
看看会不会展开循环到SSE2指令:
同样杯具啊,看来VS的编译器比较笨,那么明显的循环也不会展开并向量化,仅仅是拿XMM寄存器当x87浮点寄存器来用了。我记得好像ICC的编译器是有这能力的,看来还是Intel牛一些,不过现在ICC限制在AU上SSE的性能也不太厚道。
再用2个我以前写的其它程序来测试一下VS2008对SSE指令的优化能力,结果是:1、我的一个以计算sin/log函数为主的程序,打开SSE2选项,效率没有提升,看代码,只有少数语句被编译成SSE2指令。2、我以前写的一个DCT2D 8x8计算程序(用于JPEG编码),打开SSE2选项,效率提升18%。
嗯,看来使用比较广泛的VS2008对SSE指令集的支持还是比较一般。
个人对SSE等SIMD指令集的看法:其实我们惯常的C语句的写法与SIMD打包的计算方式还是有多少出入的,如果想既使用常规C语句去写,保证可读性,又要求能高效编译出SIMD指令集,我觉得还是得摒弃现有的程序写法,采用新的编程模型,一种能支持细粒度并行的编程模型。这方面CUDA编程模型和CT编程模型都不错。
结论:
1) 调用指令集由编译器决定,用VS的编译器使用:X87指令集很正常;用Intel的ICC,会尽可能调用SEE指令集,但也有可能没有啥优化,同时这个由Intel完成的编译器,直接会限制在AU上SSE的性能。
2) 优化程度 和 代码的可读性 是一对矛盾,相互制约。我也觉得这PhysX估计也是用高级语言写的,C,C++; 总不会NV为了降低CPU上的表现,用汇编写这个物理引擎吧~~~?!?! |
|