不知道這是個啥的看這裏:Parallel Thread Execution ISA Version 5.0.
簡要來說,PTX就是.cu代碼編譯出來的一種東西,然後再由PTX編譯生成執行代碼。如果不想看網頁版,cuda的安裝目錄下的doc文件夾裏有pdf版本,看起來也很舒服。
ps:因爲文檔是英文的(而且有二百多頁= =),鑑於博主英語水平有限並且時間也有限(主要是懶),因此只意譯了一些自以爲重點的內容,如想要深入學習,還是乖乖看文檔去吧
第一章 介紹
1.1. 使用GPU進行可擴展數據並行計算
介紹了一波並行計算的知識。
1.2. PTX的目標
PTX爲提供了一個穩定的編程模型和指令集,這個ISA能夠跨越多種GPU,並且能夠優化代碼的編譯等等。
1.3. PTX ISA 5.0版本
就是PTX ISA5.0的一些新特性
1.4. 文檔結構
- 編程模型:編程模型的概要
- PTX 機器模型:大致介紹PTX虛擬機
- 語法:描述PTX語言的基礎語法
- 狀態空間、類型和變量:就是描述這些玩意
- 指令操作數
- 應用二進制接口:描述了函數定義和調用的語法,以及PTX支持的應用二進制接口
- 指令集
- 特殊的寄存器
- 版本更新介紹
第二章 編程模型
2.1. 一個高並行度的協處理器
繼續科普GPU。
2.2. 線程層級
2.2.1 合作線程陣列
2.2.2 線程陣列網格
上邊這兩節主要就是講一些基本的GPU的block啊grid啊之類的東西,想了解的可以看我的另一篇文章:《GPU高性能編程 CUDA實戰》(CUDA By Example)讀書筆記-第五章。這裏的圖就用了這個手冊裏的。
2.3. 內存層級
這個圖實在是太好了:
第三章 PTX機器模型
3.1. 一組帶有片上共享內存的SIMT多處理器
主要講一下硬件層級結構,果然圖還是最好的:
第四章 語法
PTX語言是由操作指令和操作數組成。
4.1. 代碼格式
使用\n換行,空格木有意義,#這個符號和C差不多,就是預編譯指令,而且大小寫敏感,每個PTX代碼都是由.version打頭,表示PTX的版本。
4.2. 註釋
和C一樣
4.3. 語句
以一個可選的標記開始,以分號結束,就像這樣:
start: mov.b32 r1, %tid.x;
4.3.1. 指示
提供了PTX的指示
4.3.2. 指令
提供了PTX的指令:
ps:關於directive和 instruction這兩個詞的區別涉及一些彙編上的知識,前者這裏翻譯爲指示,後者這裏翻譯成指令,因爲一般directive並不會產生代碼而是指示編譯器的一些行爲,而instruction則會產生實際的代碼,想了解的可以看這裏:What-is-the-difference-between-an-instruction-and-a-directive-in-assembly-language
4.4. 標識符
這個大概就是變量名的命名規則吧,基本就和C一樣啦,然後系統預定義的變量都是以%開頭的大佬變量。
4.5. 常量
這個,我猜,大概是是標號標錯了,應該是包含下面各種常量的大標題纔對。
4.6. 整型常量
每個整型常量都是64噠,分爲有符號和無符號,由.s64和.u64定義,其中各個進制的數是如下定義的:
X進制 | 表示方式 |
---|---|
十六進制 | 0[xX]{十六進制數}+U? |
十進制 | 0{octal 十進制數}+U? |
二進制 | 0[bB]{0/1}+U? |
小數 | {非零數}{十進制數}*U? |
4.6.1. 浮點常量
浮點數都是64位的,除了用一個32位十六進制去精確表達一個單精度浮點數(黑人問號臉???),具體表達方式如下:
精度 | 表達方式 |
---|---|
單精度 | 0[fF]{十六進制數}{8} |
雙精度 | 0[dD]{十六進制數}{16} |
4.6.2. 判斷值常量
0就是false,非零就是true
4.6.3. 常量表達式
這個大概是可以對常量能夠使用的表達式,也和C基本一致啦:
4.6.4 整型常量表達式求值
和C語言一樣一樣的
4.6.5 表達式求值規則總結
C語言+1
第五章 狀態空間、類型和變量
5.1. 狀態空間
這個狀態空間就我理解吧,就是在哪塊內存上操作。
5.1.1. 寄存器狀態空間
利用.reg來聲明寄存器狀態空間,該空間可以使用幾乎形式的數據,但是不同於其他狀態空間的是寄存器是沒有地址的。
5.1.2. 特殊寄存器狀態空間
用.sreg來聲明,存的主要是系統預定義的一些變量,比如grid的維數之類的數據。
5.1.3. 常量狀態空間
常量狀態空間使用.const來表示,被限制在64KB之內。並且被組織成10個區域,驅動要在這十個區域中申請空間,然後可以將這些申請到的空間用指針傳遞給核函數。
5.1.3.1. 存儲體常量寄存器(棄用)
以前這種是需要指定確定的區域號纔可以的,就像這樣:
.extern .const[2] .b32 const_buffer[];
5.1.4. 全局狀態空間
使用ld.global,st.globle和atom.global來訪問全局狀態空間。而且,訪問全局變量空間是沒有順序的,是需要使用bar.sync來同步的。
5.1.5. 本地狀態空間
.local聲明本地狀態空間,而且只能在線程內部使用。
5.1.6. 參數狀態空間
參數狀態空間被用於1.將輸入的參數從主機傳遞給核函數。2.爲在覈函數內調用的設備函數聲明形式化輸入和返回參數。3.聲明作爲函數調用參數的本地數組,特別是用來傳遞大的結構體給函數。
5.1.6.1. 核函數參數
.entry foo ( .param .b32 N, .param .align 8 .b8 buffer[64])
{
.reg .u32 %n;
.reg .f64 %d;
ld.param.u32 %n, [N];
ld.param.f64 %d, [buffer];
...
5.1.6.2. 核函數參數屬性
5.1.6.3. 核函數參數屬性: .ptr
使用這個相當於一個指針,還可以指定內存對齊的大小。
.entry foo (
.param .u32 param1,
.param .u32 .ptr.const.align 8 param3,
.param .u32 .ptr.align 16 param4
) { .. }
5.1.6.4. 設備函數參數
這個最常用於傳遞大小和寄存器大小不一樣的變量,比如結構體。
.func foo ( .reg .b32 N, .param .align 8 .b8 buffer[12] ) {
.reg .s32 %y;
ld.param.f64 %d, [buffer];
ld.param.s32 %y, [buffer+8];
...
}
5.1.7. 共享狀態空間
用.shared定義,共享內存有一個特點是可以廣播,並且能夠順序訪問(有某種一致性機制?)
5.1.8. 紋理狀態空間(棄用)
紋理內存也是全局內存的一部分,被上下文的所有線程共享並且是隻讀的。使用.tex應該被.global裏的.texref來代替。就像:
.tex .u32 tex_a;
//轉換成下面這樣
.global .texref tex_a;
5.2. 類型
5.2.1. 基本類型
這些基本類型就好像C語言中的int,float之類的,用來定義變量的:
5.2.2. 使用子字段的尺寸限制
像.u8, .s8,和.b8這種類型僅限於在ld,st和cvt中使用。.f16只能被轉換成並且只能從.f32,.f64類型。.f16×2這種浮點類型只允許被用在浮點數算法指令和紋理獲取指令上。
5.3. 紋理採集器和表面類型
下面這段話是從專家手冊裏摘錄的關於表面引用的解釋:
讀寫紋理和表面的指令相對於其他指令涉及了更多隱祕狀態。參數,例如基地址、維度、格式和紋理內容的解釋方式,都包含在一個header頭結構中。header是一箇中間數據結構,它的軟件抽象被稱爲紋理引用(texture reference)或表面引用(surface reference)。
這裏有個表用來講專門爲紋理狀態空間提供的不透明類型:
5.3.1. 紋理和表面設置
像上表中所提到的width, height, 和 depth都用來說明紋理內存的大小之類的特性。
5.3.2. 採集器設置
它有各種模式,看CUDA C Programming Guide獲取更多細節。
5.3.3. 頻道數據類型和頻道指令字段
以前之後OpenCL能用,現在都能用了。
講真,由於對紋理內存瞭解太少,這節看得很勉強。
5.4. 變量
5.4.1. 變量聲明
變量聲明需要同時聲明狀態空間和數據類型比如:
.global .u32 loc;
.reg .s32 i;
.const .f32 bias[] = {-1.0, 1.0};
.global .u8 bg[4] = {0, 0, 0, 0};
.reg .v4 .f32 accel;
.reg .pred p, q, r;
5.4.2. 向量
這裏的向量的長度是被ptx固定的,只能是2或者4,也不能是判斷值(true of false),定義同普通變量:global .v4 .f32 V;
5.4.3. 數組聲明
數組的定義和C差不多,可以指定長度也可以不指定然後初始化:
.local .u16 kernel[19][19];
.shared .u8 mailbox[128];
.global .u32 index[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
.global .s32 offset[][2] = { {-1, 0}, {0, -1}, {1, 0}, {0, 1} };
5.4.4. 初始化器
對於初始化,是這樣的:
.const .f32 vals[8] = { 0.33, 0.25, 0.125 };
.global .s32 x[3][2] = { {1,2}, {3} };
//相當於
.const .f32 vals[4] = { 0.33, 0.25, 0.125, 0.0, 0.0 };
.global .s32 x[3][2] = { {1,2}, {3,0}, {0,0} };
當前,變量的初始化只對常量和global狀態空間支持,默認的初始化值是0。對於數組,還可以採用以下神奇的方法來初始化:
.const .u32 foo = 42;
.global .u32 p1 = foo; // offset of foo in .const space .global .u32 p2 = generic(foo); // generic address of foo
// array of generic-address pointers to elements of bar .global .u32 parr[] = { generic(bar), generic(bar)+4, generic(bar)+8 };
5.4.5. 內存對齊
就是可以在定義數組什麼的時候指定內存對齊的大小:
// allocate array at 4-byte aligned address. Elements are bytes. .const .align 4 .b8 bar[8] = {0,0,0,0,2,0,0,0};
5.4.6. 參數化的變量名稱
這裏提供了一種快捷聲明變量的方法:.reg .b32 %r<100>; //就相當於聲明瞭 %r0, %r1, ..., %r99
5.4.7. 變量屬性
參見下一節
5.4.8. 變量屬性指示: .attribute
變量有個.manage屬性,這個屬性只能在.global狀態空間上使用,使用了這個屬性之後能召喚神龍可以將變量放置在一個虛擬空間上,這個空間主機和設備都能夠訪問。具體是這樣使用的:.global .attribute(.managed) .s32 g;
第六章 指令操作數
6.1. 操作數類型信息
每個指令裏的操作數都要聲明其類型,而且類型必須符合指令的模板,並沒有自動的類型轉換。
6.2. 源操作數
PTX描述的是一個存儲讀取機,因此對於ALU指令的操作數必須在.reg寄存器狀態空間。cvt指令可以的參數有多種類型和大小,可以轉換一種類型(或者大小)到另一種類型(或大小)。ld,st,mov和cvt指令從一個地址拷貝數據到另一個地址。ld,st將內容拷貝到寄存器或者從寄存器中拷貝出來,mov指令把數據從一個寄存器換到另一個寄存器。大多數指令有個可選的判斷操作,一些指令有附加的判斷類型的源操作數,這些經常被定義名爲p,q,r,s.
6.3. 目的操作數
用來得到一個結果,一般都在寄存器中。
6.4. 使用地址,數組和向量
6.4.1. 地址作爲操作數
就類似各種類型的定義:
.shared .u16 x;
.reg .u16 r0;
.global .v4 .f32 V;
.reg .v4 .f32 W;
.const .s32 tbl[256];
.reg .b32 p; .reg .s32 q;
ld.shared.u16 r0,[x];
ld.global.v4.f32 W, [V];
ld.const.s32 q, [tbl+12];
mov.u32 p, tbl;
6.4.2. 數組作爲操作數
數組的使用也基本上和C語言是一樣的:
ld.global.u32 s, a[0];
ld.global.u32 s, a[N-1];
mov.u32 s, a[1]; // move address of a[1] into s
6.4.3. 向量作爲操作數
向量的感覺更像是一個結構體或者數組,使用向量可以快速地給多個數複製,很強:
.reg .v4 .f32 V;
.reg .f32 a, b, c, d;
mov.v4.f32 {a,b,c,d}, V;
//也可以反過來
ld.global.v4.f32 {a,b,c,d}, [addr+offset];
ld.global.v2.u32 V2, [addr+offset2];
6.4.4. 標記和函數名作爲操作數
這個主要是用來獲得標記或者函數名,在分支語句中做跳轉使用。
6.5. 類型轉換
6.5.1. 標量轉換
sext:符號擴展。zext:零擴展。chop:只保留低位。s是有符號整數,f是浮點數,u是無符號整數。2就是轉換成
6.5.2. 取整修改器
這裏是表示取整的標誌,有什麼向下取證向上取整之類的。(最低有效位(英語:Least Significant Bit,lsb)是指一個二進制數字中的第0位(即最低位),權值爲2^0,可以用它來檢測數的奇偶性。)
6.6. 操作數耗時
不同狀態空空間的操作數會影響一個操作的速度。寄存器最快,全局變量最慢,而多線程可以掩蓋這種延遲,或者讓取值指令越簡單越好。下面是從這些地方取值的延遲:
第七章 抽象ABI
ABI是Application Binary Interface的縮寫,翻譯過來是二進制程序接口。直白點講就是系統提供的一系列函數。
7.1. 函數的聲明和定義
話不多說就看代碼好了
//定義了一個結構體
struct {
double dbl;
char c[4];
};
//有返回值和傳入參數
.func (.reg .s32 out) bar (.reg .s32 x, .param .align 8 .b8 y[12])
{
.reg .f64 f1;
.reg .b32 c1, c2, c3, c4;
...
ld.param.f64 f1, [y+0];
ld.param.b8 c1, [y+8];
ld.param.b8 c2, [y+9];
ld.param.b8 c3, [y+10];
ld.param.b8 c4, [y+11];
... ... // computation using x,f1,c1,c2,c3,c4;
}
{
.param .b8 .align 8 py[12];
...
//通過位移來使用參數
st.param.b64 [py+ 0], %rd;
st.param.b8 [py+ 8], %rc1;
st.param.b8 [py+ 9], %rc2;
st.param.b8 [py+10], %rc1;
st.param.b8 [py+11], %rc2;
// scalar args in .reg space, byte array in .param space
call (%out), bar, (%x, py);
...
要注意,對於參數的st.param和對返回值的ld.out都必須緊跟着函數調用call。這樣才能讓編譯器優化是的.param不佔用多餘的空間。並且這個.param允許簡單的映射將有多個地址的結構映射到能夠傳給函數的變量上。
7.1.1. PTX ISA Version 1.x的改變
1.x只支持.reg,後來開始支持.param。
7.2. 列表函數
現在的ptx並不支持列表函數。(不支持說個毛,下一位!)
7.3. Alloca
同上同上