Carved Marker

SSE 介紹 [Page 6]

現在我們來看看一個實際的例子,是一個 3D 繪圖運算中很常需要進行的動作,也就是三維向量的內積。

兩個三維向量 <x1, y1, z1><x2, y2, z2> 的定義為:

x1x2 + y1y2 + z1z2

但是 SSE 浮點運算指令是以四維向量為單位的,而且也沒有計算內積的指令。要直接對一個三維向量計算出內積,會需要相當多的資料重排動作,反而會浪費很多時間。

而且,如果三維向量在記憶體中是以 x1, y1, z1, x2, y2, z2, ... 的方式排列的話,還會有對齊的問題。因為一個三維向量只需要 12 bytes,所以無法讓每個向量都對齊在 16 bytes 的邊上。這也會降低效率。

一個簡單的方法是改變資料的排列方式。如果我們把資料改成以下面的方式排列:

x1, x2, x3, x4, ...
y1, y2, y3, y4, ...
z1, z2, z3, z4, ...

那我們就同時解決了兩個問題。首先,因為一次讀入四個 x 值(及 y 值和 z 值),所以不會有對齊的問題。另外,運算也變得容易。下面的程式片斷一次可以算出四個向量內積:

  • __m128 x1 = _mm_load_ps(vec1_x);
  • __m128 y1 = _mm_load_ps(vec1_y);
  • __m128 z1 = _mm_load_ps(vec1_z);
  • __m128 x2 = _mm_load_ps(vec2_x);
  • __m128 y2 = _mm_load_ps(vec2_y);
  • __m128 z2 = _mm_load_ps(vec2_z);
  • __m128 t1 = _mm_mul_ps(x1, x2);
  • __m128 t2 = _mm_mul_ps(y1, y2);
  • t1 = _mm_add_ps(t1, t2);
  • t2 = _mm_mul_ps(z1, z2);
  • t1 = _mm_add_ps(t1, t2);
  • _mm_store_ps(output, t1);

由於 SIMD 指令的特性,很多時候,不要把工作當成向量來考慮。而應該是把多個工作,結合到一個向量中來進行。以上面的例子來說,一個三維向量的內積,是很難有效率地用 SSE 指令來完成的。事實上,配合 SSE 的資料重排指令(後面會介紹),一個四維向量的內積,需要八個 SSE 浮點運算指令才能完成。這並不是很有效率。但是,如果我們不要以向量為單位,而改成考慮一次對四個向量進行運算的話(如同上面的例子),四個三維向量的內積,由五個指令就可以完成了。四個四維向量的內積也只需要七個指令。

以下是以 1,024 個三維向量,以上述的資料排列方式,在 Intel Pentium III 1.0B Ghz 上測試的結果(使用 Intel C++ Compiler 5.0 for Windows 編譯):

SSE DOT3 test result

可以看到使用 SSE 浮點運算指令,得到的效率比使用 x87 浮點數要高出甚多,幾乎達到三倍。

[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