2010年11月30日 星期二

撰寫 Cell 的 PPU 之組語所需的知識及技巧。

這次的文章,
Shady 將分享一些組語程式的知識及技巧。

首先寫組語或其他語言之程式時,
若只要求能運作、能有正確結果,
那程式就不會快了,當然,
以上要求也是寫程式時的第一要求,
但當為了程式最佳化的話,
就必須針對您所控制之晶片的架構有所熟悉才行。

所以我們有必要知道 Cell 的 PPU 之架構,
而它的架構敘述如後。

在 Linux 使用者空間下,
能使用的暫存器有 32 個整數暫存器 (GPR)、
32 個浮點暫存器 (FPR)、32 個向量暫存器 (VR) 和連結暫存器 (LR)、
計數暫存器 (CTR)、整數例外暫存器 (XER)、條件暫存器 (CR)、
浮點狀態控制暫存器 (FPSCR)、向量狀態控制暫存器 (VSCR)、
向量保存暫存器 (VRSAVE) 各 1 個,
至於每個暫存器寬度 (所能容納的 bit 數量),
按以上介紹順序為 64、64、128、64、64、64、32、32、32、32。
由於 PPU 只支援大端記憶體排序,
所以以 GPR 來說,其最大的 bit 在最左端,編號為 0,
最小則在右端,編號為 63,
故十六進位數值 0x800000010 在暫存器中,
bit0 之數值為 1,之後的 bit1 ~ bit61 皆為 0,
而後的 bit62、bit63 各為 1、0。
其餘暫存器的詳細介紹就請各位參考 IBM 的 Cell 網頁之文件了。

不要以為知道暫存器後就高枕無憂囉。
接著要介紹的是 PPU 對指令的處理過程。

PPU 是個循序單核心、雙執行緒且管線深度很深的 CPU,
所以在程式撰寫上,盡量以執行緒撰寫,
雖然執行緒不如雙核心之效能,
但也能盡量將其管線填滿。
故 Shady 推薦在 C 以上的高階語言,
盡量以此方式撰寫,如 Linux 上的 POSIX 所提供的 pthread。
可是 "雙執行緒" 對組合語言適用嗎?
Shady 的回答是對小函式是不適用,
而大函式亦不適用。

為什麼大、小函式都不適用?
因為小函式太過於殺雞用牛刀了,
而大函式則是開發困難,
所以通常以 C 等高階語言主程式的 thread 來呼叫組語函式為佳。

所以我們需將焦點轉移到單一執行緒上的指令處理,
首先要注意的是 "指令相依性",
"指令相依性" 指的是第一個指令的結果為第二個指令的運算元,
遇到此狀況會導致第二個指令延遲,
若第一個指令為微碼或複雜指令,
將會導致 6 ~ 11 個以上的 CPU 週期且避免 "雙發射",
所以最好以二個獨立指令交互排列且盡量不使用微碼和複雜指令,
其例子如:

add  ra,rb,rc
add  rd,ra,re  /* 此行與前一行相依 */
add  rf,rg,rh
add  ri,rj,rf  /* 此行與前一行相依 */

改成:

add ra,rb,rc
add rf,rg,rh
add rd,ra,re
add ri,rj,rf

會比較好。

再來介紹 "雙發射",
它的意思是單一 CPU 週期能發送二個不同執行單元之指令,
而這也是 "單執行緒" 用來加強 IPC (Instruction Per cycle) 的機制,
雖然在單一執行緒程式很難全都 "雙發射",但不無小補。
至於 PPU 有多少執行單元且哪些指令是對應什麼執行單元,
就請各位多參考 IBM 的文件了。
以下為 "雙發射" 的典型例子:

add  ra,rb,rc  /* 整數單元 */
ldx  rd,re,rf  /* 加載 / 存儲單元 */

此二個指令中間並沒有其它指令,
這樣子的指令排序方式就能令 "雙發射" 成立。

最後 Shady 要介紹最後的一項技巧:"分支演算法"。
"分支演算法" 被提出的原因為分支的代價對 PPU 效能的影響很重大,
因為分支的成功與否都會因分支後的指令不在指令快取中,
而且分支的預測也會對循序架構的 PPU 有效能衝擊,
因而導致慢長的 CPU 週期延遲,
所以為了提升整體程式之效率,
減少分支指令是勢在必行的。
雖然 "分支演算法"  不會比分支指令的最好例子快,
但平均下來,"分支演算法" 會較好。
以下為 C 的 if 例子:

if ( a >= b ) {
    c = b;
} else {
    c = a;
}

其組語 "分支演算法":

sub  rd,ra,rb  /* a - b */
srdi  rd,rd,63  /* 取出正負號 bit */
neg  rd,rd  /* 取正負號 bit 的 "二捕數" */
and  re,ra,rd  /* a 和正負號 bit 的 "二補數" 做 and 運算 */
andc  rf,rb,rd  /* b 和正負號 bit 的 "二補數" 的 "一補數" 做 and 運算 */
or  rc,re,rf  /* c = a 或 c = b,使用 c = a | b 運算式運算 */

雖說 "指令相依性" 不少,
但只要有更多的 "分支演算法" 或獨立指令交互穿插,
就能掩蓋 "指令相依性" 造成的延遲。

下篇文章中,
Shady 將會提供功能相當於 bzero 函式的組語函式,
而且會說明組語撰寫的一些注意事項。

2010年11月8日 星期一

轉戰 Cell 的 PPU 組合語言或彙編語言(對岸的說法)中...

為什麼 Shady 會這麼突然要轉向組語呢?
理由很簡單...

因為編譯器的最佳化好麻煩,
有多麼麻煩呢?看以下分析就知道了。

雖然目前使用 C 語言就能碰到很底層的指令操作,
使用如 intrinsic 這類利用 C 行內組合語言做成的巨集或函式就能辦到,
但還是很難滿足最佳化的需求,因為還有很多指令是沒有 intrinsic 的...
就算自己製作,但 register 就很難用 C 控制。
最後交給編譯器的選項做最佳化,但魚與熊掌有時無法兼顧...
因此在需要了解很多編譯器選項和 C 程式中的編譯器指令下,
麻煩就出現了...

我只是弄個簡單的函式,
有需要搞得如此複雜嗎?

前篇 memcpy 的結果很清楚,
在 Shady 的編譯器選項中有最高的最佳化選項和避免某些微碼選項,
但效果之慘,後來 Shady 參考別人的程式,
有組語和 C 的程式,Shady 已將自己的 C 能做好的已做好了,
但 Shady 的 C 是用 VMX 撰寫的,卻還是輸給別人的 scalar 組語,
甚至使用別人比 Shady 還簡潔的 VMX 的 C 還是輸五倍。

以下是 memcpy 的最新測試結果,
4KB 為測試之容量且對齊記憶體 128 Bytes 位址(裡頭有更好的頻寬算法):

再次告知 PS3 有一秒 79800000 ticks,
時脈為 3.192GHz,所以 1 tick 有 40 個 CPU 週期。

別人的 scalar 組語:4096Bytes / (62 ticks * 40) * 3.192GHz = 5.272GB/s
別人的 VMX 的 C:4096Bytes / (318 ticks * 40) * 3.192GHz = 1.028GB/s
Shady 的 VMX 的 C:只有 ~400MB/s ... ,說真的 Shady 的程式也很簡潔了。

所以總結就是 Shady 需要更簡潔的 C 撰寫和更多更適當的編譯器選項,
因此簡單的函式能用組語寫就用組語寫吧!

2010年11月2日 星期二

libfreevec 的 memcpy 函式測結果。

先聲明 PS3 的記憶體頻寬有 25.6GB/s。
因為 PS3 的時間測量是以 tick 為單位,
而 PS3 有 79,800,000 ticks per second,
所以 Shady 會把比較結果換算成 GB/s 來比較。
以下為 Shady 的測試結果對比,
記憶體拷貝容量以 16MBytes 做測試。

Linux memcpy:
約 8,000,000 ticks,16*1024*1024/(8000000/79800000)/1000000000 = 0.167GB/s

freevec memcpy:
約 1,590,000 ticks,16*1024*1024/(1590000/79800000)/1000000000 = 0.842GB/s


Shady loop prefech freevec memcpy:
約 597,500 ticks,16*1024*1024/(597500/79800000)/1000000000 = 2.24GB/s

結果只能說悽慘...,
還有 Cell 模擬器是個"快樂模擬器",
因為它的 tick 為 25,000,000 ,且換算後的頻寬又較高,
更以 freevec memcpy 的測試數值最高,有超過 25.6GB/s 的 50%,
但 Shady 猜想此模擬器應該是對應 PowerXCell8i 版的 Cell,所以會有所不同。
看樣子得重寫個沒有 loop 版的 prefech memcpy 才行了...XD

訂正
以上的測試與計算方式有誤,
因為三者是在同一程式按順測試,
所以順序越後者,其速度較快。
在 Shady 重頭一一測試後,
"Shady loop prefech freevec memcpy"項之 Tick 值約為原本的四倍不到,
而其餘二者則高出 10000ticks 多以上。
而計算頻寬之方式,
Shady 還在尋找其他方法。

撰寫程式來讓 PS3 顯示 BMP 圖 (程式篇)。

這次的程式篇,說實話是沒什麼好說明的,
因為大部分的說明,Shady 已經放進 Shady 的程式原始碼的註釋中了。
但在程式下載連結之前,Shady 要說說這程式的運作慨要。
這程式最多只能顯示與 PS3 的 YDL 上所設定的解析度大小的 BMP 圖,
所以若照 Shady 之前 YDL 瘦身文設定的話,
那麼 BMP 圖的解析度最大為 1280 * 720。
還有這程式只能將圖片顯示在螢幕正中央。

程式原始碼連結

在這建議 Cell Performance 這英文 Blog,
當中有不錯的文章,也有 Blog 主人自製的 PS3 FB 函式。

2010年11月1日 星期一

撰寫程式來讓 PS3 顯示 BMP 圖 (VMX 篇)。

記得之前有關 libfreevec 的文章吧?
若不記得,請翻閱 Shady 之前的文章。

這次並不是像之前一樣,
VMX 只是約略簡介而已,
這次 Shady 要講解在"撰寫程式來讓 PS3 顯示 BMP 圖 (程式篇)。"中,
有使用的VMX指令。
這次要介紹的 VMX 指令是 vec_perm,其使用詳細請行如下:

#include< altivec.h >
vector unsigned char va, vb, vc, vd;
vd = vec_perm(va, vb, vc);

它的實際運作如下:

若 va = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
而 vb = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
且 vc = {0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23},
所以 vd 的結果會是 {0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}。

為何如此,解釋如下:

內容:va va va va ... va va va va, vb vb vb vb ... vb vb vb vb
位置:00 01 02 03 ... 12 13 14 15, 16 17 18 19 ... 28 29 30 31
以上是 va 和 vb 之內容與位置對應的表示圖,
所以 vc 的內容是 va 和 vb 之內容所對應的位置,
也就是說 vc 代表為索引用的變數。
因此這個範例之結果為 vd 的第一元素為 va 第一元素,
而 vd 第二元素為 vb 第一元素,
再來 vd 第三元素為 va 第二元素...之後以此類推。
vc 的每個元素的索引值範圍為 0 ~ 31,不能低於 0,也不能高於 31。

OK!Shady 就只介紹這個 VMX 指令,
其它指令請參考此處(小心,當中有些指令是 Cell 的 PPU 所沒有的)。

為什麼 Shady 只介紹 vec_perm 呢?
因為 BMP 的顏色保存順序為 alpha、藍、綠、紅,
所以 Shady 才會才用 vec_perm 來搬運資料,
以至於顏色會對應成 PS3 的 FB 的 alpha、紅、綠、藍順序。

下一篇就是"程式篇"了。

撰寫程式來讓 PS3 顯示 BMP 圖 (函式篇,下)。

在上篇文章的最後,Shady 介紹了 ioctl 函式,
它會視裝置而有不同用途的第三個參數,
接下來 Shady 將解釋如何使用 ioctl 控制 FB。

首先 Shady 先略提 Linux Frame Buffer I/O,
然後接著介紹  PS3 Frame Buffer I/O,它可以說是很底層的控制了。

如果有大略觀看過 linux/fb.h 標頭檔,
將會看到很多有關 Linux Frame Buffer 之內容,
當中會有可成為 ioctl 的第二參數 cmd 的要求碼,
還有 bit mask,它是用來過濾 ioctl 的輸出資料用的。
在這 Shady 只介紹一個重要的要求碼 FBIOGET_VBLANK,
因為它會告訴我們有關系統的掃描操作,
如果 ioctl 使用此要求碼會回傳一個 fb_vblank 結構,
fb_vblank 結構的內容與解釋如下:

○ unsigned int flags:用來辨識掃描狀態。
○ unsigned int count:自啟動來的 traces 數量。
○ unsigned int vcount:目前垂直掃描位置。
○ unsigned int hcount:目前水平掃描位置。
○ unsigned int reserved[4]:保留給以後之功能使用。

以上的 fb_vblank 的內容中我們會用 flags 來辨識是否有支援垂直同步信號,
所以只要將 FB_VBLANK_HAVE_VSYNC 此一 bit mask 與 flags 作 AND 運算就行了。
使用 ioctl 實作的例子入下:

#include< linux/fb.h >
struct fb_vblank date;/*宣告 fb_vblank 結構變數 date*/
ioctl(fd, FBIOGET_VBLANK, &date);/*取得 Linux FB 之資訊*/
if(!(date.flags & FB_VBLANK_HAVE_VSYNC))
      /*做無垂直同步的處理*/

至此 Linux Frame Buffer 就到此打住,
因為以 Shady 對 YDL 的設置是 8MB 的 FB,
這樣的設置對 720p 的解析度而言,會有兩個 FB,
又垂直同步對多個 FB 較重要,所以 Shady 只介紹到這,
想多了解的朋友可觀看 linux/fb.h 這一標頭檔。

再來就是 PS3 Frame Buffer 了。
首先是 Shady 感覺較重要的要求碼,它們被定義在 asm/ps3fb.h 中:

●PS3FB_IOCTL_SCREENINFO:用來獲取 FB 的環境資訊。
●FBIO_WAITFORVSYNC:停止 FB 輸出動作且等待垂直同步信號。
●PS3FB_IOSTL_FSEL:發送 FB 的操作,等於垂直同步的發送。
●PS3FB_IOCTL_ON:開啟讓使用者程式操控 FB 的權利。
●PS3FB_IOCTL_OFF:關閉使用者程式操控 FB 的權利。

以上的要求碼 Shady 只會再介紹第一個,
剩下的四個會在"撰寫程式來讓 PS3 顯示 BMP 圖 (程式篇)。"中解釋。
PS3FB_IOCTL_SCREENINFO 於 ioctl 使用後,
會回傳 ps3fb_ioctl_res 的結構,其內容為:

○unsigned int xres:水平解析度數目。
○unsigned int yres:垂直解析度數目。
○unsigned int xoff:水平 Margin數目。
○unsigned int yoff:垂直 Margin數目。
○unsigned int num_frames:FB 的數量。

至於如何利用這些要求碼,
Shady 將會在"撰寫程式來讓 PS3 顯示 BMP 圖 (程式篇)。"解說。