雖然 FPGA 可使用 Verilog 或 VHDL 等低層次硬件描述語言 (HDL) 來編程,,但現(xiàn)在已有多種高層次綜合 (HLS) 工具可以采用以 C/C++ 之類的更高層次的語言編寫的算法描述,,并將其轉(zhuǎn)換為 Verilog 或 VHDL 等低層次的硬件描述語言。隨后,,下游工具即可對(duì)轉(zhuǎn)換后的語言進(jìn)行處理,,以便對(duì) FPGA 器件進(jìn)行編程,。此類流程的主要優(yōu)勢在于,您可使用諸如 C/C++ 等編程語言來編寫高效代碼,,而后將代碼轉(zhuǎn)換為硬件,,但這類編程語言的優(yōu)勢仍能得以完整保留。此外,,寫好代碼乃是軟件設(shè)計(jì)師的專長,,比學(xué)習(xí)新的硬件描述語言更簡單。
以 C/C++ 編寫的程序本質(zhì)上是專為馮諾依曼樣式的架構(gòu)編寫的,,此類架構(gòu)中用戶程序內(nèi)的每條指令都是按順序執(zhí)行的,。為了實(shí)現(xiàn)高性能,HLS 工具必須推斷順序代碼中的并行性,,并利用它來實(shí)現(xiàn)更高的性能,。要解決這個(gè)問題可并不簡單。此外,,優(yōu)秀的軟件程序員按明確定義的規(guī)則和實(shí)踐來編寫程序,,例如,RTTI,、遞歸和動(dòng)態(tài)存儲(chǔ)器分配,。其中諸多技巧在硬件中都無法找到直接等效的對(duì)象,故而給 HLS 工具帶來了諸多挑戰(zhàn),。這也意味著任意現(xiàn)成軟件都無法高效轉(zhuǎn)換為硬件。最低限度,,需檢驗(yàn)此類軟件中是否存在不可綜合的構(gòu)造,,并需要重構(gòu)代碼,使其可綜合,。
現(xiàn)如今,,即使軟件程序可自動(dòng)轉(zhuǎn)換(或綜合)為硬件,但要實(shí)現(xiàn)可接受的結(jié)果質(zhì)量 (QoR),,仍需要額外工作(例如,,重寫軟件)以幫助 HLS 工具實(shí)現(xiàn)期望的性能目標(biāo)。為此,,您需要了解正確編寫軟件的最佳實(shí)踐,,以確保在 FPGA 器件上正常執(zhí)行軟件。在接下來的幾個(gè)章節(jié)內(nèi),,將著重探討如何首先識(shí)別部分宏觀級(jí)別架構(gòu)最優(yōu)化以明確程序結(jié)構(gòu),,然后聚焦更細(xì)化的微觀級(jí)別架構(gòu)最優(yōu)化來實(shí)現(xiàn)性能目標(biāo)。
生產(chǎn)者使用者范例
請考慮軟件設(shè)計(jì)師編寫多線程程序的方式,,通常有一個(gè)主線程用于執(zhí)行某些初始化步驟,,隨后分叉為多個(gè)子線程用于執(zhí)行某些并行計(jì)算,,當(dāng)所有并行計(jì)算都完成后,主線程會(huì)整理結(jié)果并寫入輸出,。程序員必須理清哪些部分可以分叉以供并行計(jì)算,,哪些部分需要按順序執(zhí)行。這種分叉/連接類型的并行化操作不僅適用于 CPU,,也適用于 FPGA,,但 FPGA 上的吞吐量的關(guān)鍵模式之一是生產(chǎn)者使用者范例。您需要將生產(chǎn)者使用者范例應(yīng)用于順序程序,,并將其轉(zhuǎn)換為可并行執(zhí)行的抽取功能以便提升性能,。
您可借助一條簡單的問題語句的幫助來更好地理解這個(gè)分解進(jìn)程。假定您有一份數(shù)據(jù)手冊,,可供我們將其中的項(xiàng)導(dǎo)入列表,。隨后,您將對(duì)列表中的每個(gè)項(xiàng)進(jìn)行處理,。處理完每個(gè)項(xiàng)需耗時(shí)約 2 秒,。處理完后,您將把結(jié)果寫入另一份數(shù)據(jù)手冊,,此操作將耗時(shí)約每項(xiàng)各 1 秒,。因此,如果輸入 Excel 工作表中有總計(jì) 100 個(gè)項(xiàng),,那么將耗時(shí)總計(jì) 300 秒來生成輸出,。這樣做的目的是對(duì)此問題進(jìn)行分解,以便您識(shí)別能夠并行執(zhí)行的任務(wù),,從而提升系統(tǒng)吞吐量,。
圖 1. 程序工作流程
第一步是了解程序工作流程,識(shí)別獨(dú)立的任務(wù)或函數(shù),。整個(gè)工作流程分 4 個(gè)步驟,,類似上圖所示的程序工作流程(無重疊)。在此示例中,,“寫入輸出”(步驟 3)任務(wù)獨(dú)立于“處理數(shù)據(jù)”(步驟 2)處理任務(wù),。雖然步驟 3 取決于步驟 2 的輸出,但當(dāng)步驟 2 中的任意項(xiàng)完成處理后,,您即可立即將其寫入輸出文件,。您無需等待所有數(shù)據(jù)都完成處理后再開始將數(shù)據(jù)寫入輸出文件。此類型的交織/重疊任務(wù)執(zhí)行方式是非常常見的原則,。如上圖所示(例如:含重疊的程序工作流程),。如圖所示,含重疊的工作流程比不含重疊時(shí)更快完成?,F(xiàn)在,,您可將步驟 2 視作為生產(chǎn)者,,將步驟 3 視作為使用者。生產(chǎn)者使用者模式對(duì)于 CPU 性能的影響有限,。您可交織執(zhí)行每個(gè)線程的步驟,,但這需要謹(jǐn)慎分析,以便充分利用底層多線程和 L1 高速緩存架構(gòu),,因此較為耗時(shí),。但在 FPGA 上,由于可采用定制架構(gòu),,生產(chǎn)者和使用者線程可以同時(shí)執(zhí)行,,開銷極低甚至沒有,因此能夠顯著提升吞吐量,。
首先考量的最簡單案例是單一生產(chǎn)者和單一使用者通過大小有限的緩沖器來進(jìn)行通信,。如果緩沖器已滿,那么生產(chǎn)者可以選擇阻塞/停滯或者丟棄數(shù)據(jù),。當(dāng)使用者從緩沖器中移除某個(gè)項(xiàng)后,,它會(huì)通知生產(chǎn)者,生產(chǎn)者隨后開始再次填充緩沖器,。如果使用者發(fā)現(xiàn)緩沖器已空,,則可以同樣方式停滯。當(dāng)生產(chǎn)者將數(shù)據(jù)置入緩沖器后,,它會(huì)喚醒休眠中的使用者,。該解決方案可通過進(jìn)程間通信(通常使用監(jiān)控器或信號(hào)量)來實(shí)現(xiàn)。不充分的解決方案可能導(dǎo)致死鎖,,即兩個(gè)進(jìn)程都停滯并等待喚醒,。但在單一生產(chǎn)者和使用者的情況下,通信模式與先入先出 (FIFO) 或乒乓緩沖器 (PIPO) 實(shí)現(xiàn)之間存在強(qiáng)映射關(guān)系,。這種類型的通道無需依賴信號(hào)量、互斥體或監(jiān)控器來進(jìn)行數(shù)據(jù)傳輸,,即可提供高效的數(shù)據(jù)通信,。使用此類鎖定原語對(duì)于性能可能開銷較大,并且難以使用和調(diào)試,。PIPO 和 FIFO 是常用的選擇,,因?yàn)榭梢员苊舛说蕉说脑油叫枨蟆?/p>
在此類宏觀級(jí)別架構(gòu)最優(yōu)化中,可通過緩沖器封裝通信,,使程序員可免于擔(dān)心存儲(chǔ)器模型和其它非確定性行為(如爭用條件等),。在此類設(shè)計(jì)中可達(dá)成的網(wǎng)絡(luò)類型為純粹的“數(shù)據(jù)流網(wǎng)絡(luò)”,可在輸入側(cè)接受串流數(shù)據(jù),,對(duì)此數(shù)據(jù)串流執(zhí)行一些基本處理,,然后將其作為數(shù)據(jù)串流發(fā)出,。并行程序的復(fù)雜性完全被抽離。請注意,,“導(dǎo)入數(shù)據(jù)”(步驟 1)和“導(dǎo)出數(shù)據(jù)”(步驟 4)同樣在最大程度提升可用的并行性方面扮演著相應(yīng)的角色,。為了使計(jì)算能夠與 I/O 成功重疊,重要的是第一步對(duì)輸入的讀取結(jié)果進(jìn)行封裝,,最后一步則是寫入輸出,。這樣即可實(shí)現(xiàn) I/O 與計(jì)算的最大程度重疊。在計(jì)算步驟中間讀取或?qū)懭胼斎?輸出端口將會(huì)限制設(shè)計(jì)的可用并發(fā)性,。這同樣是在對(duì)設(shè)計(jì)工作流程進(jìn)行設(shè)計(jì)時(shí)需要牢記的,。
最后,此類“數(shù)據(jù)流網(wǎng)絡(luò)”的性能依賴于設(shè)計(jì)師能夠持續(xù)向網(wǎng)絡(luò)饋送數(shù)據(jù),,使數(shù)據(jù)能夠在系統(tǒng)中保持串流,。數(shù)據(jù)流中出現(xiàn)中斷可能導(dǎo)致性能下降。視頻串流應(yīng)用就是一個(gè)很好的例子,,比如在線游戲中,,實(shí)時(shí)高清 (HD) 視頻持續(xù)流經(jīng)系統(tǒng),幀處理率受到持續(xù)監(jiān)控,,以確保滿足期望的結(jié)果質(zhì)量,。游戲玩家可以在屏幕上立即觀察到幀處理率下降。假想一下,,這能為一大群游戲玩家提供持續(xù)性幀率支持,,同時(shí)功耗相比傳統(tǒng) CPU 或 GPU 架構(gòu)顯著降低 - 這就是硬件加速的魅力。使數(shù)據(jù)在生產(chǎn)者與使用者之間持續(xù)保持流動(dòng)至關(guān)重要,。下一步,,您將深入了解本節(jié)中介紹的這種串流范例。
串流數(shù)據(jù)范例
串流是一種重要的抽象:它表示無限制的連續(xù)更新數(shù)據(jù)集,,其中“無限制”表示“大小未知或者大小無限”,。串流可以是一連串?dāng)?shù)據(jù)(標(biāo)量或緩沖器)在源(生產(chǎn)者)進(jìn)程與目標(biāo)(使用者)進(jìn)程之間單向流動(dòng)。串流范例會(huì)強(qiáng)制您根據(jù)數(shù)據(jù)訪問模式(或序列)來思考,。在軟件中,,隨機(jī)存儲(chǔ)器對(duì)數(shù)據(jù)的訪問幾乎是免費(fèi)的(忽略高速緩存成本),但在硬件中,,執(zhí)行順序訪問實(shí)際上是很有利的,,此類訪問可轉(zhuǎn)換為串流。將算法分解為生產(chǎn)者使用者關(guān)系并通過網(wǎng)絡(luò)串流數(shù)據(jù)來進(jìn)行通信具有如下所述幾大優(yōu)勢,。它允許程序員以順序方式定義算法,,并通過其它方式來提取并行度(例如,通過編譯器)。諸如任務(wù)間同步等復(fù)雜性會(huì)被抽離,。它允許生產(chǎn)者和使用者任務(wù)同時(shí)處理數(shù)據(jù),,這是提升吞吐量的關(guān)鍵。另一個(gè)優(yōu)勢是代碼更清潔且更簡單,。
如前文所述,,對(duì)于生產(chǎn)者和使用者范例,數(shù)據(jù)傳輸模式與 FIFO 或 PIPO 緩沖器實(shí)現(xiàn)之間存在強(qiáng)映射關(guān)系,。FIFO 緩沖器只是預(yù)定義大小/深度的隊(duì)列,,其中插入隊(duì)列的首個(gè)元素也會(huì)成為可從隊(duì)列跳出的首個(gè)元素。使用 FIFO 緩沖器的主要優(yōu)勢在于,,只要生產(chǎn)者將數(shù)據(jù)插入緩沖器,,使用者進(jìn)程就可以立即在 FIFO 緩沖器內(nèi)部開始訪問數(shù)據(jù)。使用 FIFO 緩沖器的唯一問題在于,,由于生產(chǎn)者與使用者之間的生產(chǎn)/使用速率不同,,可能導(dǎo)致 FIFO 緩沖器大小錯(cuò)誤,從而導(dǎo)致死鎖,。在具有多個(gè)生產(chǎn)者和使用者的設(shè)計(jì)中,,這種情況較為常見。乒乓緩沖器屬于用于加速進(jìn)程的雙緩沖器,,可將 I/O 操作與數(shù)據(jù)處理操作重疊,。其中一個(gè)緩沖器用于保存數(shù)據(jù)塊,以便使用者進(jìn)程能夠看到完整(舊)版本的數(shù)據(jù),,而另一個(gè)緩沖器中,,生產(chǎn)者進(jìn)程則正在創(chuàng)建新(部分)版本的數(shù)據(jù)。當(dāng)新的數(shù)據(jù)塊完成并有效時(shí),,使用者和生產(chǎn)者進(jìn)程將交換對(duì)兩個(gè)緩沖器的訪問,。由此導(dǎo)致,使用乒乓緩沖器會(huì)增加器件的整體吞吐量,,并幫助防止出現(xiàn)最終瓶頸,。PIPO 的主要優(yōu)勢在于,工具能夠?qū)⑸a(chǎn)速率與使用速率自動(dòng)匹配,,并創(chuàng)建高性能且無死鎖的通信通道,。此處值得注意的是,無論使用的是 FIFO 還是 PIPO,,關(guān)鍵特性是相同的:生產(chǎn)者將數(shù)據(jù)塊發(fā)送或者串流至使用者。數(shù)據(jù)塊可以是單個(gè)值,,也可以是一組 N 個(gè)值,。塊越大,所需存儲(chǔ)器資源越多。
以下是簡單的求和應(yīng)用,,用于展示經(jīng)典的串流網(wǎng)絡(luò)/數(shù)據(jù)流網(wǎng)絡(luò),。在此例中,該應(yīng)用的目標(biāo)是成對(duì)添加隨機(jī)數(shù)值串流,,然后打印這些數(shù)值,。前兩個(gè)任務(wù)(任務(wù) 1 和 2)提供了隨機(jī)數(shù)值串流以供添加。這些數(shù)值通過 FIFO 通道發(fā)送到求和任務(wù)(任務(wù) 3),,任務(wù) 3 會(huì)從 FIFO 通道讀取值,。隨后,求和任務(wù)將輸出發(fā)送到打印任務(wù)(任務(wù) 4),,以發(fā)布結(jié)果,。FIFO 通道可在這些獨(dú)立的執(zhí)行線程之間提供異步緩沖。
圖 2. 串流網(wǎng)絡(luò)/數(shù)據(jù)流網(wǎng)絡(luò)
連接每項(xiàng)“任務(wù)”的串流通常是作為 FIFO 隊(duì)列來實(shí)現(xiàn)的,。FIFO 能夠抽離程序員的并行行為,,使其專注于推理任務(wù)活動(dòng)(調(diào)度)的“快照”時(shí)間。FIFO 能夠使并行化更易于實(shí)現(xiàn),。這主要得益于它減少了程序員實(shí)現(xiàn)并行化框架或容錯(cuò)解決方案時(shí),,必須應(yīng)付的可變空間。獨(dú)立內(nèi)核之間的 FIFO 展現(xiàn)出經(jīng)典的排隊(duì)行為,。對(duì)于純串流系統(tǒng),,可使用排隊(duì)或網(wǎng)絡(luò)流模型來對(duì)此行為進(jìn)行建模。這種數(shù)據(jù)流類型網(wǎng)絡(luò)和串流最優(yōu)化的另一個(gè)主要優(yōu)勢在于它可按不同粒度級(jí)別來應(yīng)用,。程序員可以在每項(xiàng)任務(wù)內(nèi)部設(shè)計(jì)此類網(wǎng)絡(luò),,也可以為任務(wù)或內(nèi)核系統(tǒng)設(shè)計(jì)此類網(wǎng)絡(luò)。實(shí)際上,,您可以通過串流網(wǎng)絡(luò)來以分層方式例化并連接多個(gè)串流網(wǎng)絡(luò)或任務(wù),。支持更細(xì)粒度的并行化的另一項(xiàng)最優(yōu)化措施是流水打拍。
流水線范例
流水線是您日常生活中常用的概念,。造車廠生產(chǎn)線就是一個(gè)典型的例子,,其中每一項(xiàng)具體任務(wù)通常都是由一個(gè)獨(dú)立且唯一的工作站來完成的,如安裝引擎,、安裝車門和安裝車輪,。各工作站各自對(duì)一輛不同的車并行執(zhí)行自己的任務(wù)。當(dāng)某一輛車執(zhí)行完某一項(xiàng)任務(wù)后,,它就會(huì)移至下一個(gè)工作站,。完成各項(xiàng)任務(wù)的時(shí)間差可通過“緩沖”(將一輛或多輛車暫存在各工作站之間的空間內(nèi))和/或“停滯”(暫時(shí)中止上游工作站的操作)來加以調(diào)整,直至下一個(gè)工作站變?yōu)榭捎脼橹埂?/p>
假設(shè)組裝一輛車需要執(zhí)行 3 項(xiàng)任務(wù) A,、B 和 C,,這 3 項(xiàng)任務(wù)分別需要 20,、10 和 30 分鐘。那么,,如果全部 3 項(xiàng)任務(wù)均由單個(gè)工作站來執(zhí)行,,工廠每 60 分鐘才能輸出一輛車。通過使用 3 個(gè)工作站組成的流水線,,該工廠 60 分鐘即可輸出第一輛車,,隨后每 30 分鐘再輸出一輛新車。正如此示例所示,,流水線并不會(huì)降低時(shí)延,,即單個(gè)項(xiàng)穿越整個(gè)系統(tǒng)的總時(shí)間。但它會(huì)增加系統(tǒng)吞吐量,,即,,第一個(gè)項(xiàng)完成后處理新的項(xiàng)的速率。
由于流水線的吞吐量不可能優(yōu)于其最慢的元素,,因此程序員應(yīng)嘗試在各階段間拆分工作和資源,,以使各階段耗用相同時(shí)間來完成自己的任務(wù)。在上述車輛組裝線示例中,,如果 3 項(xiàng)任務(wù) A,、B 和 C 各自耗時(shí) 20 分鐘,而不是分別耗時(shí) 20,、10 和 30 分鐘,,那么時(shí)延將仍為 60 分鐘,但每隔 20 分鐘(而不是 30 分鐘)即可完成一輛新車,。下圖顯示了承擔(dān)制造 3 輛車任務(wù)的假想生產(chǎn)線示例,。假定任務(wù) A、B 和 C 各耗時(shí) 20 分鐘,,那么順序生產(chǎn)線將需要 180 分鐘才能生產(chǎn) 3 輛車,。而流水線式生產(chǎn)線只需 100 分鐘即可生產(chǎn) 3 輛車。
生產(chǎn)第一輛車所耗費(fèi)的時(shí)間為 60 分鐘,,稱為流水線的迭代時(shí)延,。生產(chǎn)完第一輛車后,后兩輛車各自只需 20 分鐘,,這稱為流水線的啟動(dòng)時(shí)間間隔 (II),。生產(chǎn)三輛車所耗費(fèi)的總時(shí)間為 100 分鐘,稱為流水線的總時(shí)延,,即,,總時(shí)延 = 迭代時(shí)延 + II * (項(xiàng)數(shù) - 1)。因此,,改善 II 即可改善總時(shí)延,,但不影響迭代時(shí)延,。從程序員視角來看,流水線范例可適用于設(shè)計(jì)中的函數(shù)和循環(huán),。確定初始建立時(shí)間成本后,要實(shí)現(xiàn)理想吞吐量目標(biāo),,II 應(yīng)為 1,,即初始建立時(shí)間延遲過后,在流水線的每個(gè)周期都將有輸出可用,。在以上示例中,,初始建立時(shí)間延遲 60 分鐘過后,每隔 20 分鐘就有一輛車可用,。
圖 3. 流水打拍
流水打拍是經(jīng)典的微觀級(jí)別架構(gòu)最優(yōu)化,,可應(yīng)用于多個(gè)抽象層。在前文中,,我們通過生產(chǎn)者使用者范例解釋了任務(wù)級(jí)別流水打拍,。這一概念同樣適用于指令級(jí)別。這實(shí)際上是使生產(chǎn)者使用者流水線(和串流)保持填滿并繁忙的關(guān)鍵,。僅當(dāng)每個(gè)任務(wù)都高速率生產(chǎn)/使用數(shù)據(jù),,故而需要指令級(jí)流水打拍 (ILP) 時(shí),生產(chǎn)者使用者流水線才能保持高效,。
由于流水打拍長期使用相同資源來執(zhí)行相同功能,,因此需要有關(guān)每項(xiàng)任務(wù)時(shí)延的完整信息,故而被視作為靜態(tài)最優(yōu)化,。有鑒于此,,低級(jí)指令流水打拍方法無法應(yīng)用于數(shù)據(jù)流類型的網(wǎng)絡(luò),因?yàn)樵诖祟惥W(wǎng)絡(luò)中任務(wù)的時(shí)延是輸入數(shù)據(jù)的函數(shù),,故而可能未知,。下一章節(jié)中將詳解如何利用介紹的這三種基本范例來對(duì)不同類型的任務(wù)并行度進(jìn)行建模。
組合三種范例
用戶程序中,,大部分最優(yōu)化的主要焦點(diǎn)是函數(shù)和循環(huán)?,F(xiàn)如今的最優(yōu)化工具通常在函數(shù)/過程級(jí)別工作。每個(gè)函數(shù)都能轉(zhuǎn)換為特定硬件組件,。每個(gè)此類硬件組件都與類定義相似,,該組件的許多對(duì)象(或?qū)嵗┚稍谧罱K硬件設(shè)計(jì)中創(chuàng)建和例化。每個(gè)硬件組件都將由許多更小的預(yù)定義組件組成,,這些預(yù)定義組件通常用于實(shí)現(xiàn)基本函數(shù)(例如,,加法、減法和乘法),。雖然不支持遞歸,,但是函數(shù)可以調(diào)用其它函數(shù),。較小且調(diào)用次數(shù)較少的函數(shù)通常還可以內(nèi)聯(lián)到其調(diào)用方函數(shù)中,正如軟件函數(shù)內(nèi)聯(lián)方式一樣,。在此情況下,,實(shí)現(xiàn)函數(shù)所需的資源將匯總到調(diào)用方函數(shù)的組件中,這樣能更好地共享公用資源,。將設(shè)計(jì)構(gòu)造為一組通信函數(shù)有助于在執(zhí)行這些函數(shù)時(shí)提升推斷并行度,。
循環(huán)是程序中最重要的構(gòu)造之一。由于循環(huán)主體多次迭代,,因此可輕松利用該屬性來提升并行度,。可通過多種方式來對(duì)循環(huán)和循環(huán)嵌套進(jìn)行變換(例如,,流水打拍和展開),,從而提升并行執(zhí)行的效率。這些變換使存儲(chǔ)器系統(tǒng)最優(yōu)化以及映射到多核和 SIMD 執(zhí)行資源成為可能,??茖W(xué)與工程應(yīng)用中的許多程序都表現(xiàn)為對(duì)大型數(shù)據(jù)結(jié)構(gòu)進(jìn)行各種運(yùn)算。這些運(yùn)算包括對(duì)陣列或矩陣進(jìn)行簡單的逐元素運(yùn)算,,或者也可能是具有循環(huán)進(jìn)位依賴關(guān)系的更復(fù)雜的循環(huán)嵌套運(yùn)算,,如跨循環(huán)迭代的數(shù)據(jù)依賴關(guān)系。此類數(shù)據(jù)依賴關(guān)系會(huì)影響循環(huán)內(nèi)可達(dá)成的并行度,。在諸多此類情況下,,必須對(duì)代碼進(jìn)行重構(gòu),才能在現(xiàn)代化的并行平臺(tái)上有效執(zhí)行并行循環(huán)迭代,。
下圖顯示了 4 個(gè)連續(xù)任務(wù)(即,,C/C++ 函數(shù))A、B,、C 和 D 的不同重疊執(zhí)行的簡單示例,,其中,A 在 2 個(gè)不同陣列內(nèi)分別為 B 和 C 生成數(shù)據(jù),,D 則使用來自 B 和 C 所生成的 2 個(gè)不同陣列的數(shù)據(jù),。假定這種“菱形”通信模式將運(yùn)行 2 次,且 2 次運(yùn)行彼此獨(dú)立,。
void diamond(data_t vecIn[N], data_t vecOut[N]) { data_t c1[N], c2[N], c3[N], c4[N]; #pragma HLS dataflow A(vecIn, c1, c2),; B(c1, c3); C(c2, c4),; D(c3, c4, vecOut),; }
以上代碼示例所示 C/C++ 源代碼片段顯示了這些函數(shù)的調(diào)用方式。請注意,,任務(wù) B 和 C 相互之間不存在數(shù)據(jù)依賴關(guān)系,。下圖對(duì)應(yīng)完全順序執(zhí)行方式,,其中黑色圓圈表示用于實(shí)現(xiàn)串行的某種同步形式。
圖 4. 順序執(zhí)行 - 運(yùn)行 2 輪
在菱形示例中,,B 與 C 完全彼此獨(dú)立,。兩者既不相互通信也不訪問任何共享存儲(chǔ)器資源,因此如果無需共享計(jì)算資源,,那么兩者可并行執(zhí)行,。由此可得結(jié)果如下圖所示,一輪運(yùn)行內(nèi)形成了某種形式的分叉式連接并行運(yùn)行,。當(dāng) A 任務(wù)結(jié)束后,B 和 C 并行執(zhí)行,,而 D 則等待 B 和 C,,但下一輪仍按順序連續(xù)執(zhí)行。
圖 5. 一輪運(yùn)行中的任務(wù)并行
此類執(zhí)行方式可總結(jié)為 (A; (B || C),; D),; (A; (B || C); D),,其中“,;”表示串行連續(xù),“||”則表示完全并行,。這種形式的嵌套分叉連接并行運(yùn)行對(duì)應(yīng)于一種從屬任務(wù)子類,,稱為串并行任務(wù)圖。一般來說,,任何從屬任務(wù)的有向無環(huán)圖 (DAG) 均可通過獨(dú)立的分叉并連接類型同步來實(shí)現(xiàn),。此外同樣值得注意的是,這正是在具有多個(gè)線程并使用共享存儲(chǔ)器的 CPU 上運(yùn)行多線程程序的方式,。
在 FPGA 上,,您可以探索其它可用的并行形式。先前的執(zhí)行模式利用的是在單次調(diào)用內(nèi)執(zhí)行任務(wù)級(jí)別并行操作,。那么重疊連續(xù)運(yùn)行又會(huì)如何呢,?如果每次運(yùn)行之間真正彼此獨(dú)立,但每個(gè)函數(shù)(即,,A,、B、C 或 D)復(fù)用前一輪的相同計(jì)算硬件,,那么我們?nèi)钥赡芟胍獙?A 的第二次調(diào)用與 B 和 C 的第一次調(diào)用并行執(zhí)行,。這是一種跨調(diào)用的任務(wù)級(jí)流水線形式,由此可得結(jié)果如下圖所示?,F(xiàn)在,,由于吞吐量受到所有任務(wù)間的最大時(shí)延的限制,,而非所有任務(wù)時(shí)延總和的限制,因而吞吐量明顯改善,。雖然每輪運(yùn)行的時(shí)延不變,,但多輪運(yùn)行的總時(shí)延得以縮短。
圖 6. 利用流水打拍實(shí)現(xiàn)任務(wù)并行
但現(xiàn)在,,當(dāng) B 首次運(yùn)行從存儲(chǔ)器中執(zhí)行讀取時(shí),,A 則已得到其首輪運(yùn)行的結(jié)果,A 的第二輪操作可能已在相同存儲(chǔ)器內(nèi)執(zhí)行寫入,。為避免在使用數(shù)據(jù)之前寫入數(shù)據(jù),,您可以依靠某種形式的存儲(chǔ)器擴(kuò)展(即所謂的雙重緩沖或 PIPO)來達(dá)成此交織操作。這種交織操作以任務(wù)間的黑色圓圈來表示,。
有一種有效的方法可用于提升吞吐量和復(fù)用計(jì)算資源,,即對(duì)運(yùn)算符、循環(huán)和/或函數(shù)進(jìn)行流水打拍,。如果每個(gè)任務(wù)現(xiàn)在都能與自身重疊,,您即可在一輪運(yùn)行內(nèi)實(shí)現(xiàn)任務(wù)并行操作,同時(shí)跨多輪運(yùn)行實(shí)現(xiàn)任務(wù)流水打拍,,這兩者都屬于宏觀級(jí)別并行度的例證,。多任務(wù)內(nèi)流水打拍則是微觀級(jí)別并行度的例證。現(xiàn)在,,由于每一輪運(yùn)行依賴于任務(wù)間的最小吞吐量而不是任務(wù)的最大吞吐量,,因此每一輪的總體吞吐量得以進(jìn)一步提升。最后,,根據(jù)通信數(shù)據(jù)的同步方式,,僅當(dāng)生成全部數(shù)據(jù) (PIPO) 或者以逐元素方式 (FIFO) 生成全部數(shù)據(jù)后,才有可能在一輪運(yùn)行內(nèi)出現(xiàn)某種程度的額外重疊,。例如,,在下圖中,B 和 C 都比 A 更早啟動(dòng)并以流水打拍方式執(zhí)行,,而 D 則假定仍必須等待 B 和 C 完成,。這是最后一種類型的單輪運(yùn)行內(nèi)重疊,當(dāng) A 通過 FIFO 串流訪問(表現(xiàn)為不含圓圈的直線)來與 B 和 C 通信時(shí),,才能實(shí)現(xiàn)這種方式的重疊,。同樣,D 也能與 B 和 C 重疊,,前提是采用的通道為 FIFO 而不是 PIPO,。但不同于前幾種執(zhí)行模式,使用 FIFO 可能導(dǎo)致死鎖,因此需要正確設(shè)置這些串流 FIFO 的大小,。
圖 7. 單輪運(yùn)行內(nèi)的任務(wù)并行和流水打拍,、多輪運(yùn)行的流水打拍以及單一任務(wù)內(nèi)的流水打拍
總之,本節(jié)中所演示的三種范例為您展示了如何在避免多線程和/或并行編程語言的復(fù)雜操作的同時(shí)下,,仍能在設(shè)計(jì)中實(shí)現(xiàn)并行操作,。生產(chǎn)者使用者范例搭配串流通道即可輕松組合小型系統(tǒng)與大型系統(tǒng)在內(nèi)的各種系統(tǒng)。如上文所述,,串流接口支持輕松耦合并行任務(wù),,亦或是分層數(shù)據(jù)流網(wǎng)絡(luò)也不在話下。這其中部分原因是由于編程語言 (C/C++) 能夠靈活支持此類規(guī)范,,并且還有各種工具可用于在當(dāng)今 FPGA 器件上可用的異構(gòu)計(jì)算平臺(tái)上實(shí)現(xiàn)這些規(guī)范,。
結(jié)論 - 性能良方
本文檔中所演示的設(shè)計(jì)概念具有一個(gè)主要的核心原則,即并行計(jì)算模型傾向于通過狀態(tài)封裝和模塊化單元或任務(wù)內(nèi)的順序執(zhí)行來為并行編程提供更簡單的編程模型,。隨后,,任務(wù)將與串流相連(以執(zhí)行同步和通信)。每個(gè)串流均可包含不同類型的通道,,例如,F(xiàn)IFO 或 PIPO,。您不妨思考下為什么這種范例未能推而廣之?,F(xiàn)如今的變化在于,每個(gè)人手中所使用的并行硬件數(shù)量發(fā)生了巨大變化,。即使手機(jī)都是多核的,,并擁有異構(gòu)加速器(GPU 等)。利用大部分現(xiàn)有編程工具對(duì)這些器件進(jìn)行編程都無疑是一場夢魘,。將 OpenCL ,、C、Java 和/或 C++ 的位相結(jié)合來打造一致的系統(tǒng)極為耗時(shí),。而基于串流的處理方式則可為此提供一種解決方案,。狀態(tài)/邏輯區(qū)隔化使工具(例如編譯器和調(diào)度器)能夠更加輕松地理清何時(shí)何地需要運(yùn)行應(yīng)用的哪些部分?;诖鞯奶幚矸绞饺諠u流行的另一個(gè)原因是,,它打破了傳統(tǒng)多線程“分叉/連接”模型基于并行執(zhí)行的觀點(diǎn)。通過啟用任務(wù)級(jí)流水打拍和指令級(jí)流水打拍,,運(yùn)行時(shí)間可執(zhí)行的并發(fā)操作數(shù)量遠(yuǎn)遠(yuǎn)超過了分叉/連接模型現(xiàn)今所能執(zhí)行的操作數(shù)量,。這種附加的并行性對(duì)于充分利用現(xiàn)今 FPGA 器件上可用的硬件而言至關(guān)重要。與啟用流水線并行性如出一轍的是,,串流同樣支持設(shè)計(jì)師構(gòu)建并行應(yīng)用,,而無需擔(dān)心鎖定、爭用條件等可能導(dǎo)致并行編程無從著手的問題,。
最后,,建議您依據(jù)以下高層次操作檢查表為標(biāo)準(zhǔn),,在可重配置 FPGA 平臺(tái)上實(shí)現(xiàn)所期望的性能。
專為 CPU 編寫的軟件與專為 FPGA 編寫的軟件有著本質(zhì)上的不同,。要想編寫可在 CPU 平臺(tái)與 FPGA 平臺(tái)之間進(jìn)行移植的代碼,,性能犧牲是不可避免的。因此,,F(xiàn)PGA 軟件與 CPU 軟件的編寫方式差異巨大,,與其抗拒這個(gè)事實(shí),不如坦然接受,。
從工程伊始,,就應(yīng)該建立起能夠?qū)υ创a更改進(jìn)行功能性驗(yàn)證的流程?;趨⒖寄P突蛘呤褂命S金矢量來測試軟件是極為常用的實(shí)踐,。
首先專注于設(shè)計(jì)的宏架構(gòu)。并考慮使用生產(chǎn)者使用者范例來對(duì)解決方案進(jìn)行建模,。
明確設(shè)計(jì)的宏架構(gòu)后,,即可繪制出期望的活動(dòng)時(shí)間線,其中以橫軸表示時(shí)間,,并顯示您期望在多次迭代(或調(diào)用)過程中執(zhí)行每一項(xiàng)功能相對(duì)于其它功能的時(shí)間,。這樣您將能夠?qū)υO(shè)計(jì)中期望的并行性了然于胸,后續(xù)即可用于與最終達(dá)成的結(jié)果進(jìn)行比較,。HLS GUI 通??捎糜谥庇^顯示所達(dá)成的并行度。
僅當(dāng)您已掌握宏架構(gòu)并確立活動(dòng)時(shí)間線之后,,再開始進(jìn)行程序編碼或重構(gòu),。
一般,HLS 編譯器僅根據(jù)函數(shù)調(diào)用來推斷任務(wù)級(jí)別并行度,。因此,,需要在硬件中并發(fā)運(yùn)行的順序代碼塊(例如循環(huán))應(yīng)置于專用函數(shù)內(nèi)。
將原始算法分解/分區(qū)為較小的組件,,這些組件可通過串流來彼此進(jìn)行通信,。這樣您就能在一定程度上掌握數(shù)據(jù)在設(shè)計(jì)中流動(dòng)的方式。
較小的模塊化組件的優(yōu)勢在于,,可以按需進(jìn)行賦值,,從而提升并行性。
請避免通信通道的位寬過寬,。將此類寬通道分解為較窄些的通道,,這樣有助于在 FPGA 器件上實(shí)現(xiàn)。
大型函數(shù)(手寫或通過內(nèi)聯(lián)較小的函數(shù)生成)可能包含重要路徑,工具可能難以處理此類路徑,。具有更簡單的控制路徑的小型函數(shù)有助于 FPGA 器件上的實(shí)現(xiàn),。
目標(biāo)是在每個(gè)函數(shù)內(nèi)包含單個(gè)循環(huán)嵌套(可采用 HLS 工具可推斷的固定循環(huán)邊界,或者手動(dòng)向 HLS 工具提供循環(huán)次數(shù)信息),。這樣可以顯著促進(jìn)吞吐量的測量和最優(yōu)化,。此方法雖然可能不適用于所有設(shè)計(jì),但對(duì)于大部分案例都很有效,。
吞吐量 - 縱觀全局,,掌握設(shè)計(jì)每個(gè)階段期間所需的處理速率,這一點(diǎn)至關(guān)重要,。這將影響您為 FPGA 編寫應(yīng)用的方式,。
思考設(shè)計(jì)中的關(guān)鍵路徑(即,關(guān)鍵任務(wù)級(jí)別路徑,,如,,ABD 或 ACD),調(diào)查此關(guān)鍵路徑中哪個(gè)部分可能成為瓶頸,。通過設(shè)計(jì)仿真,,觀察哪幾個(gè)任務(wù)采用流水打拍,以及任一路徑的不同分支是否在吞吐量方面存在不一致,。隨后,,可使用 HLS GUI 工具和/或仿真波形查看器來直觀顯示此類吞吐量問題。
基于串流的通信允許使用者在生產(chǎn)者開始生產(chǎn)時(shí)立即開始處理,,由此即可支持重疊執(zhí)行(從而提升并行性和吞吐量)。
為了使生產(chǎn)者和使用者任務(wù)保持持續(xù)不間斷運(yùn)行,,請使用流水打拍和調(diào)整串流的相應(yīng)大小等方法來對(duì)每項(xiàng)任務(wù)的執(zhí)行進(jìn)行最優(yōu)化,,使其能盡快運(yùn)行。
請思考串流通道的同步粒度(和開銷),。您可使用 PIPO 通道來重疊任務(wù)執(zhí)行,,而無需擔(dān)心死鎖,顯式手動(dòng)串流 FIFO 通道支持您(比 PIPO)更快開始重疊執(zhí)行,,但請謹(jǐn)慎處理 FIFO 大小調(diào)整,,以避免死鎖。
了解有關(guān)可綜合的 C/C++ 編碼樣式的更多信息,。
使用 HLS 編譯器生成的報(bào)告作為指導(dǎo)來完成最優(yōu)化進(jìn)程,。
請將上述檢查表放在附近,以便隨時(shí)參考,。它總結(jié)了構(gòu)建能滿足您的性能目標(biāo)的設(shè)計(jì)所需的整個(gè)設(shè)計(jì)活動(dòng),。
設(shè)計(jì)的另一個(gè)需要考量的重要方面在于您的加速函數(shù)或內(nèi)核。內(nèi)核連接外部的接口是最終系統(tǒng)設(shè)計(jì)的重要要素。您的內(nèi)核可能需要插入更大的設(shè)計(jì)或者與更大的內(nèi)核系統(tǒng)中的其它內(nèi)核進(jìn)行通信,,或者與系統(tǒng)外部的存儲(chǔ)器或器件進(jìn)行通信,。設(shè)計(jì)高效內(nèi)核 提供了另一份檢查表,以供您在設(shè)計(jì)加速內(nèi)核的外部接口時(shí)考量其中所列出的各項(xiàng)內(nèi)容,。
更多信息可以來這里獲取==>>電子技術(shù)應(yīng)用-AET<<