1、背景知識
從1986年到2002年,微處理器的性能以平均50%的速度不斷提升。但是,從2002年開始,單處理器的性能提升速度降到每年大約20%,這個差距是巨大的:如果以每年50%的速度提升,在10年里,微處理器的性能能提升60倍,而如果以20%的速度提升的話,10時間里,只能提升6倍。所以,從2005年起,大部分主流的CPU制造商決定通過並行處理來快速提升微處理器的性能。他們不再繼續開發速度更快的單處理器芯片,而是開始將多個完整的單處理器放到一個集成電路芯片上。這一變化對軟件人員帶來了重大的影響,也由此引出了一系列的問題:
為什么需要不斷提升的性能?
過去的幾十年,借助不斷提升的計算能力,人類在許多領域(如:人類基因解碼、醫療成像、人工智能、虛擬現實等)發展的非常迅速。但隨着人類在科學步伐的前進,這里領域的許多應用在計算能力上的要求也越來越高,解決問題的規模也在不斷的增加。
圖1 氣象模擬 圖2 蛋白質分析 圖3 葯物發現
為什么需要構建並行系統?
單處理器性能大幅度提升的主要原因之一是日益增加的集成電路晶體管密度。隨着晶體管尺寸減小,傳遞速度增快,集成電路整體的速度也加快,它們的能耗也相應增加。大多數能量是以熱能的形式消耗,而在21世紀的前10年中,用空氣冷卻的集成電路的散熱能力已經達到極限了,所以,集成電腦制造商的決策是:與其構建更快、更復雜的單處理器,不如在芯片上放置多個相對簡單的處理器,這樣的有多個處理器的集成電腦稱之為多核處理器。
為什么需要編寫並行程序?
通常我們傳統單核處理器上編寫的程序無法利用多核處理器,我們需要使得程序充分利用處理器更快的運行程序,更加及時與逼真的模擬現實世界。為了達到這一目的,就需要軟件開發工程師將串行程序改寫為並行程序。
怎么樣編寫並行程序?
廣泛采用的兩種方式:任務並行和數據並行。
任務並行:是指將有待解決的問題需要執行的任務分配到各個核上完成。
數據並行:是指將有待解決的問題所需要處理的數據分配到各個核上完成,每個核在所分配的大致相當的數據集上執行相同操作。
這里我們舉個例子來說明什么是任務並行,什么是數據並行?假如一個學校的高三總共有200個學生,有5個數學老師,分別是A、B、C、D、E老師,到期末考試的時候,數學試卷出了5道題目進行考試,在閱卷的時候,有兩種方案:每個人負責一道題目進行打分;或者將學生分成5組,每人負責一組,即20個學生。在這兩種方案中,數學老師充當計算機核的角色。
在第一種方案中,可以認為是任務並行的例子。有五個任務(題目)需要執行,即給批閱第一道題、批閱第二道題、……批閱第五道題。每個人執行的指令是不相同的。應該每一道題的內容不同。
而第二種方案中,可以認為數據並行的例子,“數據”就是學生的試卷,在不同的核(打分人)之間平分,每個核執行大致相同的指令。
2、並行硬件
我們目前的計算機都是基於經典的馮偌伊曼結構的:主存+CPU+總線(互連設備)。根據Flynn(費林)分類法,即基於指令和數據流方式,可以將並行計算機划分為:SISD、MISD、SIMD和MIMD四種。具體如下圖:
圖4 硬件系統
通俗的講,SISD系統即是我們典型的馮諾依曼系統,它是一次執行一條指令,一次存取一個數據項。而其它三個才是真正的並行計算系統。SIMD系統通過對多個數據執行相同的指令,從而實現在多個數據流上的操作,因此,該系統非常適合處理大型數組的簡單循環,從此衍生出向量處理器,重點對數組或數據向量進行操作。這里重點說明一下,我們常說的GPU(graphics processing unit)圖形處理器計算機就是講物體表面的內部表示為使用點、線、面的像素數組來表示的,也是一個概念上的SIMD系統,不過隨着技術的發展,現代的GPU也可以有幾十個核,每個核也能獨立的執行指令流。
在並行計算中,一般將MIMD作為主要研究對象。其中MIMD系統主要有兩種類型:共享內存系統(圖5)和分布式內存系統(圖6)。
圖5 共享內存系統 圖6 分布式內存系統
在共享內存系統中,自治的CPU通過互連網絡與內存系統相連,每個CPU都能訪問每個內存系統中的內存。分布式內存系統中,每個CPU都有自己的獨立內存,每個單元之間的內存訪問通過消息通訊或者特殊的函數來進行。
3、並行軟件
SPMD(單程序多數據流):指的不是在每個核上運行不同的程序,而是一個程序僅包含一份代碼,通過一定的條件分支,使得這一份代碼再執行的時候表現的像是在不同的處理器上執行不同的代碼。
在我們主要討論的MIMD分布式內存系統中,各個核能夠直接訪問自己的內存,而運行在不同核之間的進程需要交換內存數據的時候,只能通過消息傳遞API來實現。消息傳遞的API至少要提供一個發送函數和接收函數。進程之間通過它們的序號(rank)進行識別。
我們編寫並行計算程序的主要目的當然是為了提供它們的性能,那么我們應該如何去評價一個並行計算程序的性能呢?答案是:加速比與效率。
加速比:
效率:
理論上而言,如果使用p個進程或者線程來運行一個並行程序,並且每個核中之多只有一個進程/線程,那么,S=p,E=1。但是在實際情況下,S<p,E<1。既然已經知道如何評價一個將串行程序改為並行程序的性能,那么現在假設我們已經有了一個串行程序,為了提高性能,我們如何將串行程序改為高性能的並行計算程序呢?需要哪些步驟?
一般情況下,需要將工作進行拆分,使得分布在每個進程中的工作量大致相仿,並行讓它們之間的通信量最少。於是串行程序並行化設計步驟一般按以下幾步進行:
第一步:划分。將串行程序中需要要執行的指令和數據按照計算部分拆分成多個小任務。關鍵:識別出可以進行並行執行的任務。
第二步:通訊。確定第一步識別出來的任務之間需要執行那些通信。
第三步:凝聚/聚合。將第一步確定的任務與通信結合成更大的任務。
第四步:分配。將上一步聚合好的任務分配到進程/線程中。這一步還主要注意的是,要使得通信量最小化,讓各個進程/線程所得到的工作量大致均衡。
4、實現的手段
4.1 MPI
MPI實現並行是進程級;采用的是分布式內存系統,顯式(數據分配方式)實現並行執行,通過通信在進程之間進行消息傳遞,可擴展性好。MPI雖適合於各種機器,但它的編程模型復雜:
- 需要分析及划分應用程序問題,並將問題映射到分布式進程集合;
- 需要解決通信延遲大和負載不平衡兩個主要問題;
- 調試MPI程序麻煩;
- MPI程序可靠性差,一個進程出問題,整個程序將錯誤;
4.2 Pthreads
Pthreads實現並行是線程級;采用的是共享內存系統,只有在POSIX的系統(linux、mac OS X、Solaris、HPUX等)上才有效。它是一個可以連接到C程序中的庫,目前標准的C++共享內存線程庫還在開發中,也許在將來在C++程序中,使用這個庫更加方便。
4.3 OpenMP
OpenMP是線程級(並行粒度);采用的是共享內存系統,隱式(數據分配方式)實現並行執行;可擴展性差;正因為采用共享內存分布系統,意味着它只適應於SMP(Symmetric Multi-Processing 對稱多處理結構),DSM(Distributed Shared Memory 共享內存分布系統)機器,不適合於集群。
4.4 OpenCL
全稱Open Computing Language,開放運算語言。是一個為異構平台編寫程序的框架,此異構平台可由CPU,GPU或其他類型的處理器組成。OpenCL由一門用於編寫kernels (在OpenCL設備上運行的函數)的語言(基於C99)和一組用於定義並控制平台的API組成。OpenCL提供了基於任務分割和數據分割的並行計算機制。OpenCL類似於另外兩個開放的工業標准OpenGL和OpenAL,這兩個標准分別用於三維圖形和計算機音頻方面。
4.5 GPU
GPU是專門為執行復雜的數學和集合計算而設計的,一個GPU內有許多流處理簇(stream Multiprocessor)SM,他們就類似於CPU的核。這些SM與共享(一級緩存)連接在一起,然后又與相當於SM間互聯開關的二級緩存相連。類似於這種設計,都是為計算圖形渲染所必須的。
4.6 Hadoop
是谷歌公司MapReduce框架的一個開源版本。它針對的是linux平台。其概念是你取來一個大數據集,然后將其切割或映射(map)成很多小的數據塊。然而,並不是將數據發送到各個節點,取而代之的是數據集通過並行文件系統已經被划分給上百或者上千個節點。因此,歸約(Reduce)步驟就是把程序發送到已經包含數據的節點上,然后輸出結果寫入本地節點並保存在那里。