|
Abstract: 本文繼續(xù)上文介紹CUDA編程模型關(guān)于核函數(shù)以及錯(cuò)誤處理部分 開(kāi)篇廢話今天的廢話就是人的性格一旦形成,那么就會(huì)成為最大的指向標(biāo),或者說(shuō)一個(gè)人的性格思維方式能夠決定這個(gè)人的全部生命軌跡,比如有人真的愛(ài)學(xué)習(xí)(比如我,嘻嘻嘻)有人真的不愛(ài)學(xué)習(xí),沒(méi)有優(yōu)劣,只是兩種生活態(tài)度,因?yàn)閷W(xué)習(xí)這個(gè)事你學(xué)一輩子也學(xué)不完人類智慧的九牛一毛,而不學(xué)習(xí)可以有更多的時(shí)間進(jìn)行社會(huì)實(shí)踐,融入社會(huì),榮華富貴,享受生命。這是兩種性格,沒(méi)有好壞,畢竟每個(gè)人評(píng)價(jià)生活的標(biāo)尺不一。努力追求自己想要的,不要嘲笑別人所追求的,能一起走的就一起走,不能一起走的就各自安好,這就是目前我理解的聲明的真諦。
核函數(shù)概述核函數(shù)就是在CUDA模型上諸多線程中運(yùn)行的那段串行代碼,這段代碼在設(shè)備上運(yùn)行,用NVCC編譯,產(chǎn)生的機(jī)器碼是GPU的機(jī)器碼,所以我們寫(xiě)CUDA程序就是寫(xiě)核函數(shù),第一步我們要確保核函數(shù)能正確的運(yùn)行產(chǎn)生正切的結(jié)果,第二優(yōu)化CUDA程序的部分,無(wú)論是優(yōu)化算法,還是調(diào)整內(nèi)存結(jié)構(gòu),線程結(jié)構(gòu)都是要調(diào)整核函數(shù)內(nèi)的代碼,來(lái)完成這些優(yōu)化的。 啟動(dòng)核函數(shù)啟動(dòng)核函數(shù),通過(guò)的以下的ANSI C 擴(kuò)展出的CUDA C指令:
其標(biāo)準(zhǔn)C的原型就是C語(yǔ)言函數(shù)調(diào)用
這個(gè)三個(gè)尖括號(hào)’
我們可以使用dim3類型的grid維度和block維度配置內(nèi)核,也可以使用int類型的變量,或者常量直接初始化:
上面這條指令的線程布局是: 我們的核函數(shù)是同時(shí)復(fù)制到多個(gè)線程執(zhí)行的,上文我們說(shuō)過(guò)一個(gè)對(duì)應(yīng)問(wèn)題,多個(gè)計(jì)算執(zhí)行在一個(gè)數(shù)據(jù),肯定是浪費(fèi)時(shí)間,所以為了讓多線程按照我們的意愿對(duì)應(yīng)到不同的數(shù)據(jù),就要給線程一個(gè)唯一的標(biāo)識(shí),由于設(shè)備內(nèi)存是線性的(基本市面上的內(nèi)存硬件都是線性形式存儲(chǔ)數(shù)據(jù)的)我們觀察上圖,可以用threadIdx.x 和blockIdx.x 來(lái)組合獲得對(duì)應(yīng)的線程的唯一標(biāo)識(shí)(后面我們會(huì)看到,threadIdx和blockIdx能組合出很多不一樣的效果) 接下來(lái)我們就是修改代碼的時(shí)間了,改變核函數(shù)的配置,產(chǎn)生運(yùn)行出結(jié)果一樣,但效率不同的代碼:
上述代碼如果沒(méi)有特殊結(jié)構(gòu)在核函數(shù)中,執(zhí)行結(jié)果應(yīng)該一致,但是有些效率會(huì)一直比較低。 上面這些是啟動(dòng)部分,當(dāng)主機(jī)啟動(dòng)了核函數(shù),控制權(quán)馬上回到主機(jī),而不是主機(jī)等待設(shè)備完成核函數(shù)的運(yùn)行,這一點(diǎn)我們上一篇文章也有提到過(guò)(就是等待hello world輸出的那段代碼后面要加一句) 想要主機(jī)等待設(shè)備端執(zhí)行可以用下面這個(gè)指令:
這是一個(gè)顯示的方法,對(duì)應(yīng)的也有隱式方法,隱式方法就是不明確說(shuō)明主機(jī)要等待設(shè)備端,而是設(shè)備端不執(zhí)行完,主機(jī)沒(méi)辦法進(jìn)行,比如內(nèi)存拷貝函數(shù):
這個(gè)函數(shù)上文已經(jīng)介紹過(guò)了,當(dāng)核函數(shù)啟動(dòng)后的下一條指令就是從設(shè)備復(fù)制數(shù)據(jù)回主機(jī)端,那么主機(jī)端必須要等待設(shè)備端計(jì)算完成。 所有CUDA核函數(shù)的啟動(dòng)都是異步的,這點(diǎn)與C語(yǔ)言是完全不同的 編寫(xiě)核函數(shù)我們會(huì)啟動(dòng)核函數(shù)了,但是核函數(shù)哪里來(lái)的?當(dāng)然我們寫(xiě)的,核函數(shù)也是一個(gè)函數(shù),但是聲明核函數(shù)有一個(gè)比較模板化的方法:
注意:聲明和定義是不同的,這點(diǎn)CUDA與C語(yǔ)言是一致的 在C語(yǔ)言函數(shù)前沒(méi)有的限定符global ,CUDA C中還有一些其他我們?cè)贑中沒(méi)有的限定符,如下:
而且這里有個(gè)特殊的情況就是有些函數(shù)可以同時(shí)定義為 device 和 host ,這種函數(shù)可以同時(shí)被設(shè)備和主機(jī)端的代碼調(diào)用,主機(jī)端代碼調(diào)用函數(shù)很正常,設(shè)備端調(diào)用函數(shù)與C語(yǔ)言一致,但是要聲明成設(shè)備端代碼,告訴nvcc編譯成設(shè)備機(jī)器碼,同時(shí)聲明主機(jī)端設(shè)備端函數(shù),那么就要告訴編譯器,生成兩份不同設(shè)備的機(jī)器碼。 Kernel核函數(shù)編寫(xiě)有以下限制
并行程序中經(jīng)常的一種現(xiàn)象:把串行代碼并行化時(shí)對(duì)串行代碼塊for的操作,也就是把for并行化。
并行:
這兩個(gè)簡(jiǎn)單的段不能執(zhí)行,但是我們可以大致的看一下for展開(kāi)并行化的樣子,沒(méi)吃過(guò)雞肉的時(shí)候可以先看看雞跑(我的博客是清真的)。 驗(yàn)證核函數(shù)驗(yàn)證核函數(shù)就是驗(yàn)證其正確性,下面這段代碼上文出現(xiàn)過(guò),但是同樣包含驗(yàn)證核函數(shù)的方法:
在開(kāi)發(fā)階段,每一步都進(jìn)行驗(yàn)證是絕對(duì)高效的,比把所有功能都寫(xiě)好,然后進(jìn)行測(cè)試這種過(guò)程效率高很多,同樣寫(xiě)cuda也是這樣的每個(gè)代碼小塊都進(jìn)行測(cè)試,看起來(lái)慢,實(shí)際會(huì)提高很多效率。
錯(cuò)誤處理所有編程都需要對(duì)錯(cuò)誤進(jìn)行處理,早起的編碼錯(cuò)誤,編譯器會(huì)幫我們搞定,內(nèi)存錯(cuò)誤也能觀察出來(lái),但是有些邏輯錯(cuò)誤很難發(fā)現(xiàn),甚至到了上線運(yùn)行時(shí)才會(huì)被發(fā)現(xiàn),而且有些厲害的bug復(fù)現(xiàn)會(huì)很難,不總出現(xiàn),但是很致命,而且cuda基本都是異步執(zhí)行的,當(dāng)錯(cuò)誤出現(xiàn)的時(shí)候,不一定是哪一條指令觸發(fā)的,這一點(diǎn)非常頭疼;這時(shí)候我們就需要對(duì)錯(cuò)誤進(jìn)行防御性處理了,例如我們代碼庫(kù)頭文件里面的這個(gè)宏:
就是獲得每個(gè)函數(shù)執(zhí)行后的返回結(jié)果,然后對(duì)不成功的信息加以處理,cuda c 的api每個(gè)調(diào)用都會(huì)返回一個(gè)錯(cuò)誤代碼,這個(gè)代碼我們就可以好好利用了,當(dāng)然在release版本中可以去除這部分,但是開(kāi)發(fā)的時(shí)候一定要有的。 編譯執(zhí)行接下來(lái)我們對(duì)上面那段向量加法的代碼編譯執(zhí)行,觀察結(jié)果,這里需要一張圖,我明天早上連接服務(wù)器后運(yùn)行給出,今晚網(wǎng)太差,所以請(qǐng)見(jiàn)諒,
總結(jié)這篇可以說(shuō)寫(xiě)的比前幾篇流暢很多,因?yàn)檫@篇知識(shí)很連貫,不想漆面的概覽,瑣碎的知識(shí),我們明天繼續(xù)。。。 machineln 交流群請(qǐng)掃碼加machinelp為好友: >>>\n',block.x,grid.x); check(cudamemcpy(res_from_gpu_h,res_d,nbyte,cudamemcpydevicetohost)); sumarrays(a_h,b_h,res_h,nelem); checkresult(res_h,res_from_gpu_h,nelem); cudafree(a_d); cudafree(b_d); cudafree(res_d); free(a_h); free(b_h); free(res_h); free(res_from_gpu_h); return 0; } 在開(kāi)發(fā)階段,每一步都進(jìn)行驗(yàn)證是絕對(duì)高效的,比把所有功能都寫(xiě)好,然后進(jìn)行測(cè)試這種過(guò)程效率高很多,同樣寫(xiě)cuda也是這樣的每個(gè)代碼小塊都進(jìn)行測(cè)試,看起來(lái)慢,實(shí)際會(huì)提高很多效率。
錯(cuò)誤處理所有編程都需要對(duì)錯(cuò)誤進(jìn)行處理,早起的編碼錯(cuò)誤,編譯器會(huì)幫我們搞定,內(nèi)存錯(cuò)誤也能觀察出來(lái),但是有些邏輯錯(cuò)誤很難發(fā)現(xiàn),甚至到了上線運(yùn)行時(shí)才會(huì)被發(fā)現(xiàn),而且有些厲害的bug復(fù)現(xiàn)會(huì)很難,不總出現(xiàn),但是很致命,而且cuda基本都是異步執(zhí)行的,當(dāng)錯(cuò)誤出現(xiàn)的時(shí)候,不一定是哪一條指令觸發(fā)的,這一點(diǎn)非常頭疼;這時(shí)候我們就需要對(duì)錯(cuò)誤進(jìn)行防御性處理了,例如我們代碼庫(kù)頭文件里面的這個(gè)宏:
就是獲得每個(gè)函數(shù)執(zhí)行后的返回結(jié)果,然后對(duì)不成功的信息加以處理,cuda c 的api每個(gè)調(diào)用都會(huì)返回一個(gè)錯(cuò)誤代碼,這個(gè)代碼我們就可以好好利用了,當(dāng)然在release版本中可以去除這部分,但是開(kāi)發(fā)的時(shí)候一定要有的。 編譯執(zhí)行接下來(lái)我們對(duì)上面那段向量加法的代碼編譯執(zhí)行,觀察結(jié)果,這里需要一張圖,我明天早上連接服務(wù)器后運(yùn)行給出,今晚網(wǎng)太差,所以請(qǐng)見(jiàn)諒,
總結(jié)這篇可以說(shuō)寫(xiě)的比前幾篇流暢很多,因?yàn)檫@篇知識(shí)很連貫,不想漆面的概覽,瑣碎的知識(shí),我們明天繼續(xù)。。。 |
|
|
來(lái)自: LibraryPKU > 《GPU并行計(jì)算》