Carved Marker

SSE 介紹 [Page 4]

現在我們就先以一個很簡單的例子,來說明 SSE 浮點運算指令的使用。下面是一個簡單的程式片斷:

  • float input1[4] = { 1.2f, 3.5f, 1.7f, 2.8f };
  • float input2[4] = { -0.7f, 2.6f, 3.3f, -0.8f };
  • float output[4];
  • for(int i = 0; i < 4; i++) {
    • output[i] = input1[i] + input2[i];
  • }

這個程式片斷的動作非常簡單,只是把 input1input2 中的四組數字兩兩相加,並把結果寫到 output 中。在執行完後,output 的內容應該是 { 0.5f, 6.1f, 5.0f, 2.0f }

上面的程式很適合用 SSE 浮點運算指令來做。它所進行的四個加法運算,剛好可以用一個 SSE 浮點運算指令,也就是 addps 來完成。以下是修改後的程式:

  • float input1[4] = { 1.2f, 3.5f, 1.7f, 2.8f };
  • float input2[4] = { -0.7f, 2.6f, 3.3f, -0.8f };
  • float output[4];
  • __m128 a = _mm_load_ps(input1);
  • __m128 b = _mm_load_ps(input2);
  • __m128 t = _mm_add_ps(a, b);
  • _mm_store_ps(output, t);

上面的程式中,先用兩個 _mm_load_ps 把浮點數資料載入 __m128 的變數中。要注意的是,這裡是假設這兩個浮點數陣列都是對齊在 16 bytes 的邊上。如果不是的話,就不能用 _mm_load_ps 這個 intrinsic 來載入,而要改用 _mm_loadu_ps 這個 intrinsic。它是專門用來處理沒有對齊在 16 bytes 邊上的資料的。但是,它的速度會比較慢。

另外,因為 x86 的 little endian 特性,位址較低的 byte 會放在暫存器的右邊。也就是說,若以上面的 input1 為例,在載入到 XMM 暫存器後,暫存器中的 DATA0 會是 1.2,而 DATA1 是 3.5,DATA2 是 1.7,DATA3 是 2.8。如果需要以相反的順序載入的話,可以用 _mm_loadr_ps 這個 intrinsic。當然,在這個例子中,順序並不影響結果,所以不需要利用這個 intrinsic。

一般來說,宣告一個 float 的陣列,並不會對齊在 16 bytes 的邊上。如果希望它會對齊在 16 bytes 的邊上,以便利用 SSE 指令的話,Visual C++ 6.0 Processor Pack 和 Intel C++ compiler 都可以指定對齊方式。指定的方式如下:

  • __declspec(align(16)) float input1[4];

在上例中,input1 會被對齊在 16 bytes 的邊上。這樣就可以直接用較快的 _mm_load_ps 來載入資料了。因為 SSE 浮點數指令常常需要資料對齊在 16 bytes 的邊上,所以在 xmmintrin.h 也定義了一個巨集 _MM_ALIGN16,是同樣的意義。因此,上面的程式也可以寫成:

  • _MM_ALIGN16 float input1[4];

利用 SSE 浮點數指令會帶來多大的差別呢?對 1,024 個浮點數進行測試的結果,結果如下(使用 Intel C++ Compiler 5.0 for Windows 編譯,在 Intel Pentium III 1.0B Ghz 上執行):

SSE Performance Test #1

可以看到利用 SSE 浮點運算可以得到大約兩倍於 x87 浮點運算的效率。事實上,因為 Pentium III 架構上的因素,所以,如果同時使用加法和乘法運算,最多可以得到四倍的效率。在後面會有一個例子。

[Part 1] [Page 2] [Part 3] [Part 4] [Part 5] [Part 6] [Part 7] [Part 8]

11/7/2001, Ping-Che Chen


Sorry, Traditional Chinese only. This page is encoded in UTF-8.

Copyright© 2000, 2001 Ping-Che Chen