CUDA 線程執行模型分析(一)招兵------ GPU的革命

CUDA 線程執行模型分析(一)招兵
------ GPU的革命
 序:或許看到下面的內容的時候,你會覺得和傳統的講解線程,和一些講解計算機的書的內容不是很相同。我倒覺得有關計算機,編程這些方面的內容,並不都是深奧難懂的,再深奧難懂的事情,其實本質上也是很簡單的。一直以爲計算機編程就像小時候搭建積木一樣,只要知道遊戲規則,怎麼玩就看你自己了。或許是從小學那會,就喜歡在做數學題的時候用一些簡便方法來解題,養成了一些習慣,喜歡把複雜的問題都會嘗試用最簡單的額方法來解決,而不喜歡把簡單的問題弄得很複雜。不再多說了,有的朋友已經看得不耐煩了……ps:再羅嗦一句,如果下面看不明白的,就當小說看了,要是覺得不像小說,那就當故事看,要是覺得故事不完整,寫得太亂,那就當笑話看,在各位學習工作之餘能博得大家一笑,也倍兒感榮幸……ps2:想好再說……突然想到了,確實是了一段時間再想到的,既然叫GPU革命,那就得招集隊伍啊,下面我就開始招兵了。
正題:
要真正進入CUDA並行化開發,就必須先了解CUDA的運行模型,才能在這個基礎上做並行程序的開發。
CUDA在執行的時候是讓host裏面的一個一個的kernel按照線程網格(Grid)的概念在顯卡硬件(GPU)上執行。每一個線程網格又可以包含多個線程塊(block),每一個線程塊中又可以包含多個線程(thread)。
在這裏我們可以拿古時候的軍隊作爲一個例子來理解這裏的程序執行模型。每一個線程,就相當於我們的每一個士兵,在沒有當兵之前,大家都不知道自己做什麼。當要執行某一個大的軍事任務的時候,大將軍發佈命令,大家來要把對面的敵人部隊的n個敵人消滅了。然後把隊伍分成M個部分,每一個部分完成自己的工作,有的是做偵查的工作,有的是做誘敵的工作,有的是做伏擊的工作,有的是做後備的工作,有的是做後勤的工作……反正把一個大任務按照不同的類別,不同的流程不同,分別由M個部分來完成。
這裏我們可以把大將軍看着是Host,它把這次軍事行動分解成一個一個的kernelkernel_1kernel_2……kernel_M,每一個kernel就交給每一個Grid(副將?千戶?就看管的人多人少了,如果GPU硬件支持少一點,那就是千戶;要是GPU硬件高級一些,管理的人多一些,那就副將?戚家軍也不過四五千人,咱也不能太貪心,一下子就想統軍百萬,再說了,敢問世上韓信一樣的將才又哪有那麼多啦?)來完成。當要執行這些任務的時候,每一個Grid又把任務分成一部分一部分的,畢竟人太多,他一個官不過來,他只要管理幾個團隊中間的高級軍官就可以了。Grid又把任務劃分爲一個個的Block(百戶?),這裏每一個Grid管理的Block也是有限的,(人就那麼多……想管多少得看硬件的支持)。畢竟顯卡上的GPU硬件還是很少,Thread(線程)相對於真正的軍隊來說人還是少了很多。所以到Block這個層的時候,就直接管理每一個Thread(士兵)。
由於古代通信不是很方便(從GPU的發展史來看,如果按照中國的歷史,現在的GPU也就還處在戰國時代吧……),所以每一個Block(百戶)內部的Thread(士兵)才能方便的通信,按照既定的規則進行同步;而各個block之間就沒那麼方便了,大家不能互相通訊。不過同一個(千戶)Grid管理的block之間是共享同一個任務分配的資源的。每一個Grid都可以從大將軍那裏分配到一些任務,和一些糧食,同一個Gridblock都可以分到這個Grid分配到的糧食。而每一個(千戶)Grid本身的任務就不一樣,所以Grid除了知道自己做的事情外,其他的Grid他都不會知道了。----這差不多就是一個運行模型。下面讓我們來看看在GPU中東圖例說明:
看到這張圖,我們可以對應來講解我們的Thread部隊。一個大將軍Host,分配了任務中的兩個任務(Kernel1, Kernel2)給了千戶(Grid1Grid2)來完成。千戶Grid1裏面把自己的隊伍分成了6個百戶Block,然後每一個百戶又把任務分配給了自己的士兵(Thread)來具體完成。這裏得說明的是,由於千戶拿到的任務Kernel是定了的,所以到每個士兵(Thread)也就那裏就只會埋頭做同樣的事情(就像戚繼光招的兵:在胡宗憲的幕僚鄭若曾所著的《江南經略》中,有着這樣一份詳細的招生簡章,如果不服氣,大可以去對照一下:凡選入軍中之人,以下幾等人不可用,在市井裏混過的人不能用,喜歡花拳繡腿的人不能用,年紀過四十的人不能用,在政府機關幹過的人不能用。以上尚在其次,更神奇的要求還在下面:喜歡吹牛、高談闊論的人不能用,膽子小的人不能用,長得白的人不能用,爲保證隊伍的心理健康,性格偏激(偏見執拗)的人也不能用。……概括起來戚繼光要找的是這樣一羣人四肢發達,頭腦簡單,爲人老實,遵紀守法服從政府,敢打硬仗,敢衝鋒不怕死,具備二愣子性格的肌肉男----《明朝那些事兒》)。
 
爲了方便統一管理,大家都去掉了自己的名字,按照Grid1Blockxy),Threadxy)這樣的編號來稱呼每一個Thread士兵。如果你要找到某一個Thread,你就跑到軍營裏面大叫:喂,Grid1手下的Block1管理的三排第二個Thread12)出來。對於每個士兵自己來說,他要知道自己的位置,就得知道自己的長官都是誰。Thread12)要知道自己再整個Grid手下算第幾個兵(鋼七年第……個兵),當Grid1叫到他的號了,他得馬上回答:我在Block11)的編號是:   
unsigned int xIndex = blockDim.x * blockIdx.x + threadIdx.x;
 unsigned int yIndex = blockDim.y * blockIdx.y + threadIdx.y;
如果要得到線性的編號,我們可以自己算一下:這個士兵是在Grid部隊下的第xIndex行,yIndex列站着(這裏我們必須注意:blockDim.x (這裏爲5),blockDim.y(這裏爲3)不是block的座標,這裏是block的size,切記!)。如果從第一個士兵哪裏算是線性編號0,那他的線性編號就是
unsigned int index = xIndex(6) + size_x * yIndex(5); 這裏的size_x就是一行一共有多少個士兵(Thread),例如上圖,這裏一行有3個block每一個block裏面的每一行有5個Thread,所以size_x就應該爲3×5=15,一個Grid的一行有15個士兵,那剛纔叫道的那個人的線性編號就應該是……還要我算嗎?如果不得81,自己再算一下……(編號是從0,開始的)計算過程:index = 6+5×15=81
說了那麼多,咱也不能光說不練假把式。下面給一個簡單的Thread 測試的Demo。本來打算把整個代碼都copy過來,但是考慮到又會被別人copy出去,這樣copycopy去,在編程中很容易出現錯誤,所以作者也不提倡在做編程中直接copy代碼,這樣很危險的,很多自己都不知道的bug就隱藏在copy的代碼當中……so,截圖……
作者這裏做了一個簡單的測試,測試了512個線程。這裏只有一個grid,從這一個grid裏面也只分了一個bock
dim3    grid(size_x / BLOCK_DIM, 1);--》    dim3 grid(1, 1);一個Grid裏面一個blcok
dim3    block(BLOCK_DIM, 1, 1);--》dim3 block(512, 1, 1);一個Block裏面分配512個Thread;
這裏的每一個任務kernel就是:
__global__ static void ThreadDemo1(unsigned int* ret)
{
 unsigned int xIndex = blockDim.x * blockIdx.x + threadIdx.x;
 unsigned int yIndex = blockDim.y * blockIdx.y + threadIdx.y;
 
 if(xIndex < size_x && yIndex < size_y)
 {
        unsigned int index = xIndex + size_x * yIndex;
        ret[index] = xIndex;
        ret[index + size_x*size_y] = yIndex;
 }
}
計算自己的線性id然後把自己的座標寫入到線性id對應的數組裏面。Ps:說明一下,這個記錄id座標的數組ret[],ret的前一半記錄的是線程的x座標,後一般是記錄的y座標。PS2:題外話,cu是C的擴展,這裏的const定義的常量的用法在ANSIC C裏面是行不通的,但是在C++中是可用的。
 
每個任務kernel都說好了,然後就是host下達命令
 ThreadDemo1<<<grid,block>>>(ret);
來運行程序。所有的士兵都開始工作了,把自己的座標x,y寫入到ret數組裏面。
下面是得到的結果:
(0,0) (1,0) (2,0) (3,0) (4,0) (5,0) (6,0) (7,0) (8,0) (9,0) (10,0) (11,0) (12,0) (13,0) (14,0) (15,0) (16,0) (17,0) (18,0) (19,0) (20,0) (21,0) (22,0) (23,0) (24,0) (25,0) (26,0) (27,0) (28,0) (29,0) (30,0) (31,0) (32,0) (33,0) (34,0) (35,0) (36,0) (37,0) (38,0) (39,0) (40,0) (41,0) (42,0) (43,0) (44,0) (45,0) (46,0) (47,0) (48,0) (49,0) (50,0) (51,0) (52,0) (53,0) (54,0) (55,0) (56,0) (57,0) (58,0) (59,0) (60,0) (61,0) (62,0) (63,0) (64,0) (65,0) (66,0) (67,0) (68,0) (69,0) (70,0) (71,0) (72,0) (73,0) (74,0) (75,0) (76,0) (77,0) (78,0) (79,0) (80,0) (81,0) (82,0) (83,0) (84,0) (85,0) (86,0) (87,0) (88,0) (89,0) (90,0) (91,0) (92,0) (93,0) (94,0) (95,0) (96,0) (97,0) (98,0) (99,0) (100,0) (101,0) (102,0) (103,0) (104,0) (105,0) (106,0) (107,0) (108,0) (109,0) (110,0) (111,0) (112,0) (113,0) (114,0) (115,0) (116,0) (117,0) (118,0) (119,0) (120,0) (121,0) (122,0) (123,0) (124,0) (125,0) (126,0) (127,0) (128,0) (129,0) (130,0) (131,0) (132,0) (133,0) (134,0) (135,0) (136,0) (137,0) (138,0) (139,0) (140,0) (141,0) (142,0) (143,0) (144,0) (145,0) (146,0) (147,0) (148,0) (149,0) (150,0) (151,0) (152,0) (153,0) (154,0) (155,0) (156,0) (157,0) (158,0) (159,0) (160,0) (161,0) (162,0) (163,0) (164,0) (165,0) (166,0) (167,0) (168,0) (169,0) (170,0) (171,0) (172,0) (173,0) (174,0) (175,0) (176,0) (177,0) (178,0) (179,0) (180,0) (181,0) (182,0) (183,0) (184,0) (185,0) (186,0) (187,0) (188,0) (189,0) (190,0) (191,0) (192,0) (193,0) (194,0) (195,0) (196,0) (197,0) (198,0) (199,0) (200,0) (201,0) (202,0) (203,0) (204,0) (205,0) (206,0) (207,0) (208,0) (209,0) (210,0) (211,0) (212,0) (213,0) (214,0) (215,0) (216,0) (217,0) (218,0) (219,0) (220,0) (221,0) (222,0) (223,0) (224,0) (225,0) (226,0) (227,0) (228,0) (229,0) (230,0) (231,0) (232,0) (233,0) (234,0) (235,0) (236,0) (237,0) (238,0) (239,0) (240,0) (241,0) (242,0) (243,0) (244,0) (245,0) (246,0) (247,0) (248,0) (249,0) (250,0) (251,0) (252,0) (253,0) (254,0) (255,0) (256,0) (257,0) (258,0) (259,0) (260,0) (261,0) (262,0) (263,0) (264,0) (265,0) (266,0) (267,0) (268,0) (269,0) (270,0) (271,0) (272,0) (273,0) (274,0) (275,0) (276,0) (277,0) (278,0) (279,0) (280,0) (281,0) (282,0) (283,0) (284,0) (285,0) (286,0) (287,0) (288,0) (289,0) (290,0) (291,0) (292,0) (293,0) (294,0) (295,0) (296,0) (297,0) (298,0) (299,0) (300,0) (301,0) (302,0) (303,0) (304,0) (305,0) (306,0) (307,0) (308,0) (309,0) (310,0) (311,0) (312,0) (313,0) (314,0) (315,0) (316,0) (317,0) (318,0) (319,0) (320,0) (321,0) (322,0) (323,0) (324,0) (325,0) (326,0) (327,0) (328,0) (329,0) (330,0) (331,0) (332,0) (333,0) (334,0) (335,0) (336,0) (337,0) (338,0) (339,0) (340,0) (341,0) (342,0) (343,0) (344,0) (345,0) (346,0) (347,0) (348,0) (349,0) (350,0) (351,0) (352,0) (353,0) (354,0) (355,0) (356,0) (357,0) (358,0) (359,0) (360,0) (361,0) (362,0) (363,0) (364,0) (365,0) (366,0) (367,0) (368,0) (369,0) (370,0) (371,0) (372,0) (373,0) (374,0) (375,0) (376,0) (377,0) (378,0) (379,0) (380,0) (381,0) (382,0) (383,0) (384,0) (385,0) (386,0) (387,0) (388,0) (389,0) (390,0) (391,0) (392,0) (393,0) (394,0) (395,0) (396,0) (397,0) (398,0) (399,0) (400,0) (401,0) (402,0) (403,0) (404,0) (405,0) (406,0) (407,0) (408,0) (409,0) (410,0) (411,0) (412,0) (413,0) (414,0) (415,0) (416,0) (417,0) (418,0) (419,0) (420,0) (421,0) (422,0) (423,0) (424,0) (425,0) (426,0) (427,0) (428,0) (429,0) (430,0) (431,0) (432,0) (433,0) (434,0) (435,0) (436,0) (437,0) (438,0) (439,0) (440,0) (441,0) (442,0) (443,0) (444,0) (445,0) (446,0) (447,0) (448,0) (449,0) (450,0) (451,0) (452,0) (453,0) (454,0) (455,0) (456,0) (457,0) (458,0) (459,0) (460,0) (461,0) (462,0) (463,0) (464,0) (465,0) (466,0) (467,0) (468,0) (469,0) (470,0) (471,0) (472,0) (473,0) (474,0) (475,0) (476,0) (477,0) (478,0) (479,0) (480,0) (481,0) (482,0) (483,0) (484,0) (485,0) (486,0) (487,0) (488,0) (489,0) (490,0) (491,0) (492,0) (493,0) (494,0) (495,0) (496,0) (497,0) (498,0) (499,0) (500,0) (501,0) (502,0) (503,0) (504,0) (505,0) (506,0) (507,0) (508,0) (509,0) (510,0) (511,0)
一目瞭然,正是我們定義的512個線程。
現在大家應該知道CUDA是怎麼運行了?0 0 要是還不清楚,跟貼問我吧 - - !
 
好了,現在招兵的任務已經完成了,兵是招來了,下面就改訓練了,不過天時已晚,大家還是吃飽喝足,好好的休息一下吧~
明天繼續GPU的革命。
且聽:《沙家浜》裏胡傳魁已經出場了:十幾個人來七八條槍,不是很威風嗎?----我可要說十幾個人怎麼來分這七八條槍。
士兵們執行任務了,是一哄而上,還是……且聽SIMD- -!
 
PS:上面在輸出結果的時候用了一個小技巧,或許有的人知道,就不用看了,有不知道的,接着看下去,或許可以方便你以後的調試:)
再輸出的時候,在系統debugging信息裏面的command Arguments裏面用一個管道輸出到一個1.txt文件裏面。管道用法大家自己可以到網上查~我就不用講解了。也很簡單,就是在你的哦xx.exe 後面加一個 “>” 管道,對了還有 “<” 管道,呵呵:) 一個是輸出,一個是輸入。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章