Carved Marker

SSE 介紹 [Page 7]

前面的向量內積的測試,是以 1,024 個三維向量,對一個固定的三維向量進行內積得到的結果。它的資料量算是相當小的,甚至可以整個放到 L1 cache 裡面。但是,實際情形常常不是這樣。我們常常需要對一大堆資料進行運算,因此資料是無法放在 cache 裡面的,而需要從主記憶體中取得。這樣一來,速度會變得相當的慢。下面是對 102,400 個三維向量進行同樣動作的結果:

SSE test with large data set

可以看到效率差了很多,這是因為資料都需要從主記憶體中讀取,因而讀取的時間成為瓶頸,而不是計算的時間。在這種情形下,SSE 浮點運算指令速度雖快,也沒有什麼幫助。而且,因為內積的計算是相當簡單的,所以這也讓 SSE 和 x87 的結果差距變得很小。

不過,現在我們會討論到 SSE 的 "streaming" 部份。SSE 除了運算的指令之外,還支援了一些 cache 控制指令。我們在這裡先介紹兩個較簡單的指令:prefetchmovntpsprefetch 指令實際上有四個不同的指令,包括 prefetch0prefetch1prefetch2、和 prefetchnta。不過,它們都是用同一個 intrinsic 表示的,也就是 _mm_prefetch

prefetch 指令的主要目的,是提前讓 CPU 載入稍後運算所需要的資料。通常是在對目前的資料進行運算之前,告訴 CPU 載入下一筆資料。這樣就可以讓目前的運算,和載入下一筆資料的動作,可以同時進行。如果運算的複雜度夠高的話,這樣可以完全消除讀取主記憶體的 latency。不同的 prefetch 指令則是告訴 CPU 將資料載入不同層次的 cache。不過,最常用的還是 prefetchnta,這個指令會把資料載入到離 CPU 最近的 cache 中(通常是 L1 cache 或 L2 cache),適用於資料在短時間內就會用到的情形。

另外 prefetch 指令不會產生任何 exception。它本質上只是一個 hint,CPU 並不一定會真的進行載入的動作。所以,即使 prefetch 一個不合法的記憶體位址,也不會產生錯誤。這讓程式可以不用處理討厭的邊界問題。

除了 prefetch 之外,另一個指令是 movntps,它的 intrinsics 是 _mm_stream_ps。這個指令的用途,是要求 CPU 在寫入資料的時候,不要把資料寫到 cache 中,而是直接將資料寫到主記憶體中。實際上它以 write combining 的方式寫入的。為什麼要這樣做呢?這是因為,很多時候計算的結果並不是立刻需要用到的,通常是很久以後才會用到。所以,這些資料如果被放在 cache 中,完全是浪費空間。而且,更糟的是,它們可能會把 cache 中有用的資料擠掉,而使得這些資料常常需要重新從主記憶體中載入。因此,如果讓這些資料不要被放在 cache 中,就可以避免這種問題。

對上面的程式,加上適當的 prefetch,並利用 movntps 指令,可以修改成類似下面的程式片斷:

  • __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);
  • _mm_prefetch((const char*)(vec1_x + next), _MM_HINT_NTA);
  • _mm_prefetch((const char*)(vec1_y + next), _MM_HINT_NTA);
  • _mm_prefetch((const char*)(vec1_z + next), _MM_HINT_NTA);
  • _mm_prefetch((const char*)(vec2_x + next), _MM_HINT_NTA);
  • _mm_prefetch((const char*)(vec2_y + next), _MM_HINT_NTA);
  • _mm_prefetch((const char*)(vec2_z + next), _MM_HINT_NTA);
  • __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_stream_ps(output, t1);

同樣對 102,400 個三維向量進行測試,結果為:

SSE streaming test result with large data set

由於 x87 無法利用 movntps 指令,所以效率較差。可以看到,加上 prefetchmovntps 指令後,SSE 浮點運算的效率提高了約 50%。事實上,它所使用的記憶體頻寬達到約 500MB/s,已經是理論頻寬(1,066MB/s)的一半左右了。

[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