我經(jīng)歷過在IBM大型機(jī)上編寫匯編語言來開發(fā)高性能程序的日子。用穿孔卡片編寫程序,編譯需要一天時(shí)間;你要留下在穿孔卡片上編寫的程序,第二天再來拿結(jié)果。如果出現(xiàn)錯(cuò)誤,你需要重復(fù)這些操作。在那些日子里,一位優(yōu)秀的程序員必須理解底層的機(jī)器硬件才能編寫出好的代碼。當(dāng)我看到現(xiàn)在的計(jì)算機(jī)科學(xué)專業(yè)的學(xué)生只學(xué)習(xí)抽象層次較高的內(nèi)容以及像Ruby這樣的語言時(shí),我總會感到有些焦慮。盡管抽象是一件好事,因?yàn)樗梢员苊庥捎诓槐匾募?xì)節(jié)而使程序開發(fā)陷入困境,但當(dāng)你嘗試開發(fā)高性能代碼時(shí),抽象就變成了一件壞事。
自第一個(gè)CPU出現(xiàn)以來,計(jì)算機(jī)架構(gòu)師在CPU硬件中添加了令人難以置信的功能來“容忍”糟糕的編程技巧。20年前,你必須手動設(shè)置機(jī)器指令的執(zhí)行順序,而如今在硬件中CPU會為你做這些(例如,亂序執(zhí)行)。在GPU世界中也能清晰地看到類似的趨勢。由于GPU架構(gòu)師正在改進(jìn)硬件功能,5年前我們在GPU編程中學(xué)習(xí)的大多數(shù)性能提升技術(shù)(例如,線程發(fā)散、共享存儲體沖突以及減少原子操作的使用)正變得與改進(jìn)的GPU架構(gòu)越來越不相關(guān),甚至5~10年后,即使是一名非常馬虎的程序員,這些因素也會變得無關(guān)緊要。當(dāng)然,這只是一個(gè)猜測。GPU架構(gòu)師可以做的事取決于晶體管總數(shù)及客戶需求。當(dāng)說晶體管總數(shù)時(shí),是指GPU制造商可以將多少個(gè)晶體管封裝到集成電路(IC)即“芯片”中。當(dāng)說客戶需求時(shí),是指即使GPU架構(gòu)師能夠?qū)崿F(xiàn)某個(gè)功能,但如果客戶使用的應(yīng)用程序不能從中受益,就意味著浪費(fèi)了部分的晶體管數(shù)量。
從編寫教科書的角度出發(fā),我考慮了所有的因素,逐漸明確講授GPU編程的最佳方式是說明不同系列GPU(如Fermi、Kepler、Maxwell和Pascal)之間的不同并指明發(fā)展趨勢,這可以讓讀者準(zhǔn)備好迎接即將到來的下一代GPU,再下一代,……我會重點(diǎn)強(qiáng)調(diào)那些相對來說會長期存在的概念,同時(shí)也關(guān)注那些與平臺相關(guān)的概念。也就是說,GPU編程完全關(guān)乎性能,如果你了解程序運(yùn)行的平臺架構(gòu),編寫出了與平臺相關(guān)的代碼,就可以獲得更高的性能。所以,提供平臺相關(guān)的解釋與通用的GPU概念一樣有價(jià)值。本書內(nèi)容的設(shè)計(jì)方式是,越靠后的章節(jié),內(nèi)容越具有平臺特定性。
我認(rèn)為本書最獨(dú)特的地方就是通過第一部分中的CPU多線程來解釋并行。第二部分介紹了GPU的大規(guī)模并行(與CPU的并行不同)。由于第一部分解釋了CPU并行的方式,因此讀者在第二部分中可以較為容易地理解GPU的并行。在過去的6年中,我設(shè)計(jì)了這種方法來講授GPU編程,認(rèn)識到從未學(xué)過并行編程課程的學(xué)生并不是很清楚大規(guī)模并行的概念。與GPU相比,“并行化任務(wù)”的概念在CPU架構(gòu)中更容易理解。
本書的組織如下。第一部分(第1章至第5章)使用一些簡單的程序來演示如何將大任務(wù)分成多個(gè)并行的子任務(wù)并將它們映射到CPU線程,分析了同一任務(wù)的多種并行實(shí)現(xiàn)方式,并根據(jù)計(jì)算核心和存儲單元操作來研究這些方法的優(yōu)缺點(diǎn)。本書的第二部分(第6章至第11章)將同一個(gè)程序在多個(gè)Nvidia GPU平臺(Fermi、Kepler、Maxwell和Pascal)上并行化,并進(jìn)行性能分析。由于CPU和GPU的核心和內(nèi)存結(jié)構(gòu)不同,分析結(jié)果的差異有時(shí)很有趣,有時(shí)與直覺相反。本書指出了這些結(jié)果的不同之處,并討論了如何讓GPU代碼運(yùn)行得更快。本書的最終目標(biāo)是讓程序員了解所有的做法,這樣他們就可以應(yīng)用好的做法,并避免將不好的做法應(yīng)用到項(xiàng)目中。
盡管第一部分和第二部分已經(jīng)完全涵蓋了編寫一個(gè)好的CUDA程序需要的所有內(nèi)容,但總會有更多需要了解的東西。本書的第三部分為希望拓寬視野的讀者指明了方向。第三部分并不是相關(guān)主題的詳細(xì)參考文檔,只是給出了一些入門介紹,讀者可以從中獲得學(xué)習(xí)這些內(nèi)容的動力。這部分主要介紹了一些流行的CUDA庫,比如cuBLAS、cuFFT、Nvidia Performance Primitives和Thrust(第12章);OpenCL編程語言(第13章);使用其他編程語言和API庫進(jìn)行GPU編程,包括Python、Metal、Swift、OpenGL、OpenGL ES、OpenCV和微軟HLSL(第14章);深度學(xué)習(xí)庫cuDNN(第15章)。
書中代碼的下載地址為:https://www.crcpress.com/GPU-Parallel-ProgramDevelopment-Using- CUDA /Soyata/p/book/9781498750752。
Tolga Soyata
Tolga Soyata于1988年在伊斯坦布爾技術(shù)大學(xué)電子與通信工程系獲得學(xué)士學(xué)位,1992年在美國馬里蘭州巴爾的摩的約翰·霍普金斯大學(xué)電氣與計(jì)算機(jī)工程系(ECE)獲得碩士學(xué)位,2000年在羅切斯特大學(xué)電氣與計(jì)算機(jī)工程系獲得博士學(xué)位。2000年至2015年間,他成立了一家IT外包和復(fù)印機(jī)銷售/服務(wù)公司。在運(yùn)營公司的同時(shí),他重返學(xué)術(shù)界,在羅切斯特大學(xué)電氣與計(jì)算機(jī)工程系擔(dān)任研究員。之后,他成為助理教授,并一直擔(dān)任電氣與計(jì)算機(jī)工程系教職研究人員至2016年。在羅切斯特大學(xué)電氣與計(jì)算機(jī)工程系任職期間,他指導(dǎo)了三名博士研究生。其中兩人在他的指導(dǎo)下獲得博士學(xué)位,另一位在他2016年加入紐約州立大學(xué)奧爾巴尼分校擔(dān)任電氣與計(jì)算機(jī)工程系副教授時(shí)留在了羅切斯特大學(xué)。Soyata的教學(xué)課程包括大規(guī)模集成電路、模擬電路以及使用FPGA和GPU進(jìn)行并行編程。他的研究興趣包括信息物理系統(tǒng)、數(shù)字健康和高性能醫(yī)療移動云計(jì)算系統(tǒng)等。
Tolga Soyata從2009年開始從事GPU編程的教學(xué),當(dāng)時(shí)他聯(lián)系Nvidia將羅切斯特大學(xué)認(rèn)證為CUDA教學(xué)中心(CTC)。在Nvidia將羅切斯特大學(xué)認(rèn)證為教學(xué)中心后,他成為主要負(fù)責(zé)人。之后,Nvidia還將羅切斯特大學(xué)認(rèn)證為CUDA研究中心(CRC),他也成為項(xiàng)目負(fù)責(zé)人。Tolga Soyata在羅切斯特大學(xué)擔(dān)任這些計(jì)劃的負(fù)責(zé)人直到他于2016年加入紐約州立大學(xué)奧爾巴尼分校。這些計(jì)劃后來被Nvidia命名為GPU教育中心和GPU研究中心。在羅切斯特大學(xué)期間,他講授了5年GPU編程和高級GPU項(xiàng)目開發(fā)課程,這些課程同時(shí)被列入電氣與計(jì)算機(jī)工程系以及計(jì)算機(jī)科學(xué)與技術(shù)系的課程體系。自2016年加入紐約州立大學(xué)奧爾巴尼分校以來,他一直在講授類似的課程。本書是他在兩所大學(xué)講授GPU課程的經(jīng)驗(yàn)結(jié)晶。
譯者序
前言
關(guān)于作者
第一部分 理解CPU的并行性
第1章 CPU并行編程概述 2
1.1 并行編程的演化 2
1.2 核心越多,并行性越高 3
1.3 核心與線程 4
1.3.1 并行化更多的是線程還是核心 5
1.3.2 核心資源共享的影響 6
1.3.3 內(nèi)存資源共享的影響 6
1.4 第一個(gè)串行程序 7
1.4.1 理解數(shù)據(jù)傳輸速度 8
1.4.2 imflip.c中的main( )函數(shù) 9
1.4.3 垂直翻轉(zhuǎn)行:FlipImageV( ) 10
1.4.4 水平翻轉(zhuǎn)列:FlipImageH( ) 11
1.5 程序的編輯、編譯、運(yùn)行 12
1.5.1 選擇編輯器和編譯器 12
1.5.2 在Windows 7、8、10平臺上開發(fā) 12
1.5.3 在Mac平臺上開發(fā) 14
1.5.4 在Unix平臺上開發(fā) 14
1.6 Unix速成 15
1.6.1 與目錄相關(guān)的Unix命令 15
1.6.2 與文件相關(guān)的Unix命令 16
1.7 調(diào)試程序 19
1.7.1 gdb 19
1.7.2 古典調(diào)試方法 20
1.7.3 valgrind 22
1.8 第一個(gè)串行程序的性能 22
1.8.1 可以估計(jì)執(zhí)行時(shí)間嗎 23
1.8.2 代碼執(zhí)行時(shí)OS在做什么 23
1.8.3 如何并行化 24
1.8.4 關(guān)于資源的思考 25
第2章 開發(fā)第一個(gè)CPU并行程序 26
2.1 第一個(gè)并行程序 26
2.1.1 imflipP.c中的main( )函數(shù) 27
2.1.2 運(yùn)行時(shí)間 28
2.1.3 imflipP.c中main( )函數(shù)代碼的劃分 28
2.1.4 線程初始化 30
2.1.5 創(chuàng)建線程 31
2.1.6 線程啟動/執(zhí)行 32
2.1.7 線程終止(合并) 33
2.1.8 線程任務(wù)和數(shù)據(jù)劃分 34
2.2 位圖文件 35
2.2.1 BMP是一種無損/不壓縮的文件格式 35
2.2.2 BMP圖像文件格式 36
2.2.3 頭文件ImageStuff.h 37
2.2.4 ImageStuff.c中的圖像操作函數(shù) 38
2.3 執(zhí)行線程任務(wù) 40
2.3.1 啟動線程 41
2.3.2 多線程垂直翻轉(zhuǎn)函數(shù)MTFlipV( ) 43
2.3.3 FlipImageV( )和MTFlipV( )的比較 46
2.3.4 多線程水平翻轉(zhuǎn)函數(shù)MTFlipH(?) 47
2.4 多線程代碼的測試/計(jì)時(shí) 49
第3章 改進(jìn)第一個(gè)CPU并行程序 51
3.1 程序員對性能的影響 51
3.2 CPU對性能的影響 52
3.2.1 按序核心與亂序核心 53
3.2.2 瘦線程與胖線程 55
3.3 imf?lipP的性能 55
3.4 操作系統(tǒng)對性能的影響 56
3.4.1 創(chuàng)建線程 57
3.4.2 線程啟動和執(zhí)行 57
3.4.3 線程狀態(tài) 58
3.4.4 將軟件線程映射到硬件線程 59
3.4.5 程序性能與啟動的線程 60
3.5 改進(jìn)imf?lipP 61
3.5.1 分析MTFlipH( )中的內(nèi)存訪問模式 62
3.5.2 MTFlipH( )的多線程內(nèi)存訪問 63
3.5.3 DRAM訪問的規(guī)則 64
3.6 imf?lipPM:遵循DRAM的規(guī)則 65
3.6.1 imflipP的混亂內(nèi)存訪問模式 65
3.6.2 改進(jìn)imflipP的內(nèi)存訪問模式 65
3.6.3 MTFlipHM( ):內(nèi)存友好的MTFlipH( ) 66
3.6.4 MTFlipVM( ):內(nèi)存友好的MTFlipV( ) 69
3.7 imflipPM.C的性能 69
3.7.1 imflipP.c和imflipPM.c的性能比較 70
3.7.2 速度提升:MTFlipV( )與MTFlipVM( ) 71
3.7.3 速度提升:MTFlipH( )與MTFlipHM( ) 71
3.7.4 理解加速:MTFlipH( )與MTFlipHM( ) 71
3.8 進(jìn)程內(nèi)存映像 72
3.9 英特爾MIC架構(gòu):Xeon Phi 74
3.10 GPU是怎樣的 75
3.11 本章小結(jié) 76
第4章 理解核心和內(nèi)存 77
4.1 曾經(jīng)的英特爾 77
4.2 CPU和內(nèi)存制造商 78
4.3 動態(tài)存儲器與靜態(tài)存儲器 79
4.3.1 靜態(tài)隨機(jī)存取存儲器(SRAM) 79
4.3.2 動態(tài)隨機(jī)存取存儲器(DRAM) 79
4.3.3 DRAM接口標(biāo)準(zhǔn) 79
4.3.4 DRAM對程序性能的影響 80
4.3.5 SRAM對程序性能的影響 81
4.4 圖像旋轉(zhuǎn)程序:imrotate.c 81
4.4.1 imrotate.c的說明 82
4.4.2 imrotate.c:參數(shù)限制和簡化 82
4.4.3 imrotate.c:實(shí)現(xiàn)原理 83
4.5 imrotate的性能 87
4.5.1 線程效率的定性分析 87
4.5.2 定量分析:定義線程效率 87
4.6 計(jì)算機(jī)的體系結(jié)構(gòu) 89
4.6.1 核心、L1$和L2$ 89
4.6.2 核心內(nèi)部資源 90
4.6.3 共享L3高速緩存(L3 $) 91
4.6.4 內(nèi)存控制器 92
4.6.5 主存 92
4.6.6 隊(duì)列、非核心和I/O 93
4.7 imrotateMC:讓imrotate更高效 94
4.7.1 Rotate2( ):平方根和浮點(diǎn)除法有多差 96
4.7.2 Rotate3( )和Rotate4( ):sin( )和cos( )有多差 97
4.7.3 Rotate5( ):整數(shù)除法/乘法有多差 98
4.7.4 Rotate6( ):合并計(jì)算 100
4.7.5 Rotate7( ):合并更多計(jì)算 100
4.7.6 imrotateMC的總體性能 101
4.8 本章小結(jié) 103
第5章 線程管理和同步 104
5.1 邊緣檢測程序:imedge.c 104
5.1.1 imedge.c的說明 105
5.1.2 imedge.c:參數(shù)限制和簡化 106
5.1.3 imedge.c:實(shí)現(xiàn)原理 106
5.2 imedge.c:實(shí)現(xiàn) 108
5.2.1 初始化和時(shí)間戳 109
5.2.2 不同圖像表示的初始化函數(shù) 110
5.2.3 啟動和終止線程 111
5.2.4 高斯濾波 112
5.2.5 Sobel 113
5.2.6 閾值過濾 114
5.3 imedge的性能 115
5.4 imedgeMC:讓imedge更高效 116
5.4.1 利用預(yù)計(jì)算降低帶寬 116
5.4.2 存儲預(yù)計(jì)算的像素值 117
5.4.3 預(yù)計(jì)算像素值 118
5.4.4 讀取圖像并預(yù)計(jì)算像素值 119
5.4.5 PrGaussianFilter 1