GPU 運算(GPU computing)
有些工作是一長串環環相扣的決策,每一步都依賴上一步——CPU 正是為此而生。另一些工作則是把同一個簡單運算同時套用到數百萬個彼此獨立的項目上:把這兩百萬個數字相加、為這兩百萬個像素著色、把這些巨大的矩陣相乘。GPU 運算就是把圖形處理器——一塊原本為推送像素而造的晶片——當成處理第二類工作(大規模平行那一類)的通用引擎來使用。
關鍵差異在於硬體的形狀。CPU 有少數幾顆又大又聰明的核心,調校成盡快跑完單一指令流,配上深度亂序執行與大型快取來掩蓋每一次停頓。GPU 則有數千條又小又簡單的通道(lane),分組成許多核心,全部一起跑;它是面向吞吐量的裝置。它不試圖讓單一執行緒變快——它試圖讓海量的執行緒同時在飛,這樣當某些執行緒在等記憶體時,其他的正在運算,晶片便保持忙碌。取捨非常鮮明:當你有數千個獨立、相似的工作時,GPU 極為出色;而當工作是一條充滿分支、彼此依賴的序列時,它極為糟糕。這正是異質運算的核心——讓 CPU 與 GPU 各司其長、協同工作。
它之所以重要,是因為現代機器學習、科學模擬、圖形以及越來越大比例的資料處理,速度都來自於此;一個能良好對映到 GPU 的問題,可能比在 CPU 上快上一個數量級甚至更多。但要老實談代價。你必須把問題重構成資料平行的形式、把資料跨越 CPU 與 GPU 記憶體之間那條緩慢的連結搬來搬去(這往往才是真正的瓶頸),並接受發散的分支與不規則的記憶體存取可能讓加速崩解。GPU 不是「更快的 CPU」——它是一台不同的機器,獎勵的是一種不同風格的程式碼。
// 把兩個各 N 個元素的陣列相加: // CPU:for (i = 0; i < N; i++) c[i] = a[i] + b[i]; // 一顆核心依序走完全部 N 個 // GPU:啟動 N 個執行緒;執行緒 i 計算 c[i] = a[i] + b[i] // 所有 i 同時、平行進行
同一個逐元素加法的兩種做法:CPU 逐一迭代;GPU 為每個元素啟動一個執行緒並讓它們一起跑。
GPU 不只是更快的 CPU。它只在資料平行的工作上取勝,而在 CPU 與 GPU 記憶體之間搬資料常常才是真正的瓶頸——一個只跑幾微秒的核心(kernel),若你花了好幾毫秒把它的輸入跨匯流排複製過去,就毫無價值。充滿分支、彼此依賴或不規則的程式碼,在 GPU 上往往比在 CPU 上更慢。