( http://www.cortstratton.org/articles/OptimizingForSSE.php ),终于知道了一些东西。自以为此文堪称经典,难怪GOOGLE上排第一。
使用SSE优化程序的几个要点:
1、要使用SSE指令和XMM寄存器,一次做4个SPFP(Single Precision Floating Point) 数的加减乘除。
2、Batch Processing。如果一次处理很多数据,把数据指针传到asm中,把循环移到asm code中去,尽量避免在循环中重复的从内存中取同一些数据。
3、16-byte memory alignment。要有效使用SSE指令的重要要求。
4、Instruction Pairing。把相同的指令如MOV,ADD等放到一起,CPU可以一个cycle运行3条(现在说不定更多)!但要注意,指令之间不能有相关,即,这些放在一起的指令没有先后顺序,可以随便放不影响程序运行。
5、Prefetching。利用CPU的cache.不十分明白,反正就是加了条prefetchnta 指令。
6、Increase Temporal Locality of Memory I/O。尽量把访问内存的操作放在一起连续进行,减少访问内存的次数。取一个数据,在CPU中处理,然后在取一个,再处理,不如连续取2个数据再处理。
7、Application-Specific Specialization。根据实际情况优化算法,尽量减少不必要的计算。比如,知道自己的程序中某矩阵算完了某一项一定为1,就不要用通用的矩阵运算,而是专门写一个不算那一项的。话说回来,这样代码的重用性肯定受影响,coding的人要受累了。当然如果工作按时间算钱就无所谓了 :)。
另外,文章中用来测试性能的函数很不错,短小实用,精确到cycles。原理不懂,抄在这里备查。
__forceinline int GetPentiumTimer()
{
__asm
{
xor eax,eax // VC won't realize that eax is modified w/out this
// instruction to modify the val.
// Problem shows up in release mode builds
_emit 0x0F // Pentium high-freq counter to edx;eax
_emit 0x31 // only care about low 32 bits in eax
xor edx,edx // so VC gets that edx is modified
}
}
用法:
start = GetPentiumTimer();
BatchTransform1(m, testIn, testOut, VECLEN);
end = GetPentiumTimer();
cout << "BatchTransform1 (+w equals 1.0): "
<< ((end-start-timerOverhead) / VECLEN) << " cycles/vec\n";
以上得到的是程序运行所花的时钟周期。如果要知道更直观的时间秒数,可以用 clock() 函数(可能没有前面精确)。MSDN中的例子如下:
// crt_clock.c// This example prompts for how long// the program is to run and then continuously// displays the elapsed time for that period.// #include <stdio.h>#include <stdlib.h>#include <time.h> void sleep( clock_t wait ); int main( void ){ long i = 6000000L; clock_t start, finish; double duration; // Delay for a specified time. printf( "Delay for three seconds\n" ); sleep( (clock_t)3 * CLOCKS_PER_SEC ); printf( "Done!\n" ); // Measure the duration of an event. printf( "Time to do %ld empty loops is ", i ); start = clock(); while( i-- ) ; finish = clock(); duration = (double)(finish - start) / CLOCKS_PER_SEC; printf( "%2.1f seconds\n", duration );} // Pauses for a specified number of milliseconds.void sleep( clock_t wait ){ clock_t goal; goal = wait + clock(); while( goal > clock() ) ;}
还有一个好东西:
IEEE-754 Calculators
http://babbage.cs.qc.edu/IEEE-754/
DEBUG的时候如果看不懂REGISTER里的数,用这个计算器转换一下吧。FLOATING POINT的数32BIT怎么表示,手工搞还是蛮复杂的哦。
2008-9-12
根据最近优化程序的经验,补充几条:
*尽可能少访问内存,开销较大. 比如可以用多余的REGISTER一次多读点数据,然后从REGISTER往REGISTER转(mov, shift, pextrw, pinsrw).
*尽可能少jump. 每次循环中多处理一些数据.
*充分利用SIMD指令的处理能力.比如phaddsw能同时算8个值,要尽可能把它们都填上有效数据, 不要只利用低64BIT或32BIT.
*写C程序时,尽量少声明临时变量,特别是非BUILT-IN的,都是开销.在LOOP里面积少成多也很厉害. 有可能的话, 把某些变量用REGISTER做,减少内存访问.