sse指令集
SSE(Streaming SIMD Extensions,單指令多數(shù)據(jù)流擴(kuò)展)指令集是Intel在Pentium III處理器中率先推出的。其實(shí),早在PIII正式推出之前,Intel公司就曾經(jīng)通過各種渠道公布過所謂的KNI(Katmai New Instruction)指令集,這個(gè)指令集也就是SSE指令集的前身,并一度被很多傳媒稱之為MMX指令集的下一個(gè)版本,即MMX2指令集。究其背景,原來"KNI"指令集是Intel公司最早為其下一代芯片命名的指令集名稱,而所謂的"MMX2"則完全是硬件評(píng)論家們和媒體憑感覺和印象對(duì)"KNI"的 評(píng)價(jià),Intel公司從未正式發(fā)布過關(guān)于MMX2的消息。
而最終推出的SSE指令集也就是所謂勝出的"互聯(lián)網(wǎng)SSE"指令集。SSE指令集包括了70條指令,其中包含提高3D圖形運(yùn)算效率的50條SIMD(單指令多數(shù)據(jù)技術(shù))浮點(diǎn)運(yùn)算指令、12條MMX 整數(shù)運(yùn)算增強(qiáng)指令、8條優(yōu)化內(nèi)存中連續(xù)數(shù)據(jù)塊傳輸指令。理論上這些指令對(duì)目前流行的圖像處理、浮點(diǎn)運(yùn)算、3D運(yùn)算、視頻處理、音頻處理等諸多多媒體應(yīng)用起到全面強(qiáng)化的作用。S SE指令與3DNow!指令彼此互不兼容,但SSE包含了3DNow!技術(shù)的絕大部分功能,只是實(shí)現(xiàn)的方法不同。SSE兼容MMX指令,它可以通過SIMD和單時(shí)鐘周期并行處理多個(gè)浮點(diǎn)數(shù)據(jù)來有效地提高浮點(diǎn)運(yùn)算速度
SSE是英特爾提出的即MMX之后新一代(當(dāng)然是幾年前了)CPU指令集,最早應(yīng)用在PIII系列CPU上。現(xiàn)在已經(jīng)得到了Intel PIII、P4、Celeon、Xeon、AMD Athlon、duron等系列CPU的支持。而更新的SSE2指令集僅得到了P4系列CPU的支持,這也是為什么這篇文章是講SSE而不是SSE2的原因之一。另一個(gè)原因就是SSE和SSE2的指令系統(tǒng)是非常相似的,SSE2比SSE多的僅是少量的額外浮點(diǎn)處理功能、64位浮點(diǎn)數(shù)運(yùn)算支持和64位整數(shù)運(yùn)算支持。
SSE為什么會(huì)比傳統(tǒng)的浮點(diǎn)運(yùn)算更快呢?因?yàn)樗褂昧?28位的存儲(chǔ)單元,這對(duì)于32位的浮點(diǎn)數(shù)來講,是可以存下4個(gè)的,也就是說,SSE中的所有計(jì)算都是一次性針對(duì)4個(gè)浮點(diǎn)數(shù)來完成的,這種批處理當(dāng)然就會(huì)帶來效率的提升。我們?cè)賮砘仡櫼幌耂SE的全稱:Stream SIMD Extentions(流SIMD擴(kuò)展)。SIMD就是single instruction multiple data,連起來就是“數(shù)據(jù)流單指令多數(shù)據(jù)擴(kuò)展”,從名字我們就可以更好的理解SSE是如何工作的了。
? 雖然SSE從理論上來講要比傳統(tǒng)的浮點(diǎn)運(yùn)算會(huì)快,但是他所受的限制也很多,首先,雖然他執(zhí)行一次相當(dāng)于四次,會(huì)比傳統(tǒng)的浮點(diǎn)運(yùn)算執(zhí)行4次的速度要快,但是他執(zhí)行一次的速度卻并沒有想象中的那么快,所以要體現(xiàn)SSE的速度,必須有Stream做前提,就是大量的流數(shù)據(jù),這樣才能發(fā)揮SIMD的強(qiáng)大作用。其次,SSE支持的數(shù)據(jù)類型是4個(gè)32位(共計(jì)128位)浮點(diǎn)數(shù)集合,就是C、C++語言中的float[4],并且必須是以16位字節(jié)邊界對(duì)齊的(稍后會(huì)以代碼來進(jìn)行闡釋,關(guān)于邊界對(duì)齊的概念,讀者可以參考論壇上的其它文章,都會(huì)有很詳細(xì)的解答,我這里就恕不贅述了)。因此這也給輸入和輸出帶來了不少的麻煩,實(shí)際上主要影響SSE發(fā)揮性能的就是不停的對(duì)數(shù)據(jù)進(jìn)行復(fù)制以適用應(yīng)它的數(shù)據(jù)格式。
? 我是一個(gè)C++程序員,對(duì)匯編并不很熟,但我又想用SSE來優(yōu)化我的程序,我該怎么做呢?幸好VC++.net為我們提供了很方便的指令C函數(shù)級(jí)的封裝和C格式數(shù)據(jù)類型,我們只需像平時(shí)寫C++代碼一樣定義變量、調(diào)用函數(shù)就可以很好的應(yīng)用SSE指令了。
? 當(dāng)然了,我們需要包含一個(gè)頭文件,這里面包括了我們需要的數(shù)據(jù)類型和函數(shù)的聲明:
#include |
? SSE運(yùn)算的標(biāo)準(zhǔn)數(shù)據(jù)類型只有一個(gè),就是:
__m128,它是這樣定義的:
typedef struct __declspec(intrin_type) __declspec(align(16)) __m128 { ?? float m128_f32[4]; } __m128; |
? 簡(jiǎn)化一下,就是:
struct __m128 { ?? float m128_f32[4]; }; |
? 比如要定義一個(gè)__m128變量,并為它賦四個(gè)float整數(shù),可以這樣寫:
__m128 S1 = { 1.0f, 2.0f, 3,0f, 4,0f }; |
? 要改變其中第2個(gè)(基數(shù)為0)元素時(shí)可以這樣寫:
S1.m128_f32[2] = 6.0f; |
? 令外我們還會(huì)用到幾個(gè)賦值的指令,它可以讓我們更方便的使用這個(gè)數(shù)據(jù)結(jié)構(gòu):
S1 = _mm_set_ps1( 2.0f ); |
? 它會(huì)讓S1.m128_f32中的四個(gè)元素全部賦予2.0f,這樣會(huì)比你一個(gè)一個(gè)賦值要快的多。
S1 = _mm_setzero_ps(); |
? 這會(huì)讓S1中的所有4個(gè)浮點(diǎn)數(shù)都置零。
? 還有一些其它的賦值指令,但執(zhí)行起來還沒有自己逐個(gè)賦值來的快,只做為一些特殊用途,如果你想了解更多的信息,可以參考MSDN -> VisualC++參考 -> C/C++Language -> C++Language Reference -> Compiler Intrinsics -> MMX, SSE, and SSE2 Intrinsics -> Stream SIMD Extensions(SSE)章節(jié)。
? 一般來講,所有SSE指令函數(shù)都有3個(gè)部分組成,中間用下劃線隔開:
_mm_set_ps1 |
? mm表示多媒體擴(kuò)展指令集
? set表示此函數(shù)的含義縮寫
? ps1表示該函數(shù)對(duì)結(jié)果變量的影響,由兩個(gè)字母組成,第一個(gè)字母表示對(duì)結(jié)果變量的影響方式,p表示把結(jié)果做為指向一組數(shù)據(jù)的指針,每一個(gè)元素都將參與運(yùn)算,S表示只將結(jié)果變量中的第一個(gè)元素參與運(yùn)算;第二個(gè)字母表示參與運(yùn)算的數(shù)據(jù)類型。s表示32位浮點(diǎn)數(shù),d表示64位浮點(diǎn)數(shù),i32表示32位定點(diǎn)數(shù),i64表示64位定點(diǎn)數(shù),由于SSE只支持32位浮點(diǎn)數(shù)的運(yùn)算,所以你可能會(huì)在這些指令封裝函數(shù)中找不到包含非s修飾符的,但你可以在MMX和SSE2的指令集中去認(rèn)識(shí)它們。
? 接下來我舉一個(gè)例子來說明SSE的指令函數(shù)是如何使用的,必須要說明的是我以下的代碼都是在VC7.1的平臺(tái)上寫的,不保證對(duì)其它如Dev-C++、Borland C++等開發(fā)平臺(tái)的完全兼容。
? 為了方便對(duì)比速度,我會(huì)用常歸方法和SSE優(yōu)化兩種寫法寫出,并會(huì)用一個(gè)測(cè)試速度的類CTimer來進(jìn)行計(jì)時(shí)。
? 這個(gè)算法是對(duì)一組float值進(jìn)行放大,函數(shù)ScaleValue1是使用SSE指令優(yōu)化的,函數(shù)ScaleValue2則沒有。我們用10000個(gè)元素的float數(shù)組數(shù)據(jù)來測(cè)試這兩個(gè)算法,每個(gè)算法運(yùn)算10000遍,下面是測(cè)試程序和結(jié)果:
#include #include |
class CTimer
{
public:
?????? __forceinline CTimer( void )
?????? {
????????????? QueryPerformanceFrequency( &m_Frequency );
????????????? QueryPerformanceCounter( &m_StartCount );
?????? }
?????? __forceinline void Reset( void )
?????? {
????????????? QueryPerformanceCounter( &m_StartCount );
?????? }
?????? __forceinline double End( void )
?????? {
????????????? static __int64 nCurCount;
????????????? QueryPerformanceCounter( (PLARGE_INTEGER)&nCurCount );
????????????? return double( nCurCount * ( *(__int64*)&m_StartCount ) ) / double( *(__int64*)&m_Frequency );
?????? }
private:
?????? LARGE_INTEGER m_Frequency;
?????? LARGE_INTEGER m_StartCount;
};
void ScaleValue1( float *pArray, DWORD dwCount, float fScale )
{DWORD dwGroupCount = dwCount / 4;
?????? __m128 e_Scale = _mm_set_ps1( fScale );
?????? for ( DWORD i = 0; i < dwGroupCount; i++ )
?????? {
????????????? *(__m128*)( pArray + i * 4 ) = _mm_mul_ps( *(__m128*)( pArray + i * 4 ), e_Scale );
?????? }
}
void ScaleValue2( float *pArray, DWORD dwCount, float fScale )
{
?????? for ( DWORD i = 0; i < dwCount; i++ )
?????? {
????????????? pArray[i] *= fScale;
?????? }
}
#define ARRAYCOUNT 10000
int __cdecl main()
{
?????? float __declspec(align(16)) Array[ARRAYCOUNT];
?????? memset( Array, 0, sizeof(float) * ARRAYCOUNT );
?????? CTimer t;
?????? double dTime;
?????? t.Reset();?for ( int i = 0; i < 100000; i++ )
?????? {
????????????? ScaleValue1( Array, ARRAYCOUNT, 1000.0f );
?????? }
?????? dTime = t.End();
?????? cout << "Use SSE:" << dTime << "秒" << endl;
?????? t.Reset();
?????? for ( int i = 0; i < 100000; i++ )
?????? {
????????????? ScaleValue2( Array, ARRAYCOUNT, 1000.0f );
?????? }
?????? dTime = t.End();
?????? cout << "Not Use SSE:" << dTime << "秒" << endl;
?????? system( "pause" );
?????? return 0;
}
Use SSE:0.997817
Not Use SSE:2.84963
? 這里要注意一下,我使用了__declspec(align(16))做為數(shù)組定義的修釋符,這表示該數(shù)組是以16字節(jié)為邊界對(duì)齊的,因?yàn)镾SE指令只能支持這種格式的內(nèi)存數(shù)據(jù)。
? 我們?cè)谶@里看到了SSE算法的強(qiáng)大,相信它會(huì)成為多媒體程序員手中用來對(duì)付無窮盡流媒體數(shù)據(jù)的一把利劍。我后面還會(huì)寫一些關(guān)于SSE算法更復(fù)雜應(yīng)用的文章,敬請(qǐng)關(guān)注,感謝您抽時(shí)間閱讀!
評(píng)論