讓木桶沒有短板,FISCO BCOS全面推進區塊鏈並行化改造

FISCO BCOS是完全開源的聯盟區塊鏈底層技術平臺,由金融區塊鏈合作聯盟(深圳)(簡稱金鍊盟)成立開源工作組通力打造。開源工作組成員包括博彥科技、華爲、深證通、神州數碼、四方精創、騰訊、微衆銀行、亦筆科技和越秀金科等金鍊盟成員機構。

代碼倉庫:https://github.com/FISCO-BCOS

 

背 景

PTE(Parallel Transaction Executor,一種基於DAG模型的並行交易執行器)的引入,使FISCO BCOS具備了並行執行交易的能力,顯著提升了節點交易處理的效率。對這個階段性結果,我們並不滿足,繼續深入挖掘發現,FISCO BCOS的整體TPS仍有較大提升空間。

用木桶打個比方:如果參與節點的交易處理所有模塊構成木桶,交易執行只是組成整個木桶的一塊木板,根據短板理論,一隻木桶能盛多少水取決於桶壁上最矮的那塊,同理,FISCO BCOS的性能也由速度最慢的組件決定

儘管PTE取得了理論上極高的性能容量,但是FISCO BCOS的整體性能仍然會被其他模塊較慢的交易處理速度所掣肘。爲了能夠最大化利用計算資源以進一步提高交易處理能力,在FISCO BCOS中全面推進並行化改造勢在必行。

 

數據分析

根據並行程序設計的『分析→分解→設計→驗證』四步走原則,首先需定位出系統中仍存在的性能瓶頸的精確位置,才能更深入地對任務進行分解,並設計相應的並行化策略。使用自頂向下分析法,我們將交易處理流程分爲四個模塊進行性能分析,這四個模塊分別是:

區塊解碼(decode):區塊在節點間共識或同步時需要從一個節點發送至另一個節點,這個過程中,區塊以RLP編碼的形式在網絡間傳輸。節點收到區塊編碼後,需要先進行解碼,將區塊還原爲內存中的二進制對象,然後才能做進一步處理。

交易驗籤(verify):交易在發送之前由發送者進行簽名,簽名得到的數據可以分爲(v, r, s)三部分,驗籤的主要工作便是在收到交易或交易執行前,從(v, r, s)數據中還原出交易發送者的公鑰,以驗證交易發送者的身份。

交易執行(execute):執行區塊中的所有交易,更新區塊鏈狀態。

數據落盤(commit):區塊執行完成後,需要將區塊及相關數據寫入磁盤中,進行持久化保存。

以包含2500筆預編譯轉賬合約交易的區塊爲測試對象,在我們的測試環境中,各階段的平均耗時分佈如下圖所示:

從圖中可以看出,2500筆交易的執行時間已經被縮短到了50毫秒以內,可以證明PTE對FISCO BCOS交易執行階段的優化是行之有效的。但圖中也暴露出了非常明顯的問題:其他階段的用時遠遠高於交易執行的用時,導致交易執行帶來的性能優勢被嚴重抵消,PTE無法發揮出其應有的價值。

早在1967年,計算機體系結構領域的元老Amdahl提出的以他名字命名的定律,便已經向我們闡明瞭衡量處理器並行計算後效率提升能力的經驗法則:

其中,SpeedUp爲加速比,Ws是程序的串行分量,Wp是程序中的並行分量,N爲CPU數量。可以看出,在工作總量恆定的情況下,可並行部分代碼佔比越多,系統的整體性能越高。我們需要把思維從線性模型中抽離出來,繼續細分整個處理流程,找出執行時間最長的程序熱點,對這些代碼段進行並行化從而將所有瓶頸逐個擊破,這纔是使通過並行化獲得最大性能提升的最好辦法。

 

根因拆解

 

1.串行的區塊解碼

區塊解碼主要性能問題出在RLP編碼方法本身。RLP全稱是遞歸的長度前綴編碼,是一種用長度作爲前綴標明編碼對象中元素個數的編碼方法。如下圖所示,RLP編碼的開頭即是此編碼中的對象個數(Object num)。在個數後,是相應個數的對象(Object)。遞歸地,每個對象,也是RLP編碼,其格式也與下圖相同。

需要特別注意的是,在RLP編碼中。每個Object的字節大小是不固定的,Object num只表示Object的個數,不表示Object的字節長度。

RLP通過一種長度前綴與遞歸結合的方式,理論上可編碼任意個數的對象。下圖是一個區塊的RLP編碼,在對區塊進行編碼時,先遞歸至最底層,對多個sealer進行編碼,多個sealer被編碼並加上長度前綴後,編碼成爲一串RLP編碼(sealerList),此編碼又作爲一個對象,被編入上層的一串RLP編碼(blockHeader)中。此後層層遞歸,最後編碼成爲區塊的RLP編碼。由於RLP編碼是遞歸的,在編碼前,無法獲知編碼後的長度。

解碼時,由於RLP編碼中每個對象的長度不確定,且RLP編碼只記錄了對象的個數,沒記錄對象的字節長度,若要獲取其中的一個編碼對象,必須遞歸解碼其前序的所有對象,在解碼前序的對象後,才能訪問到需要訪問的編碼對象的字節位置。例如在上圖中,若需要訪問區塊中的第0筆交易,即tx0,必須先將blockHeader解碼,而blockHeader的解碼,需要再次遞歸,把parentHash,stateRoot直至sealerList都解碼出來。

解碼區塊最重要的目的是解碼出包含在區塊中的交易,而交易的編碼都是互相獨立的,但在RLP特殊的編碼方式下,解碼一筆交易的必要條件是解碼出上一筆交易,交易的解碼任務之間環環相扣,形成了一種鏈式的依賴關係。需要指出的是,這種解碼方式並不是RLP的缺陷,RLP的設計目標之一本就是儘量減少空間佔用,充分利用好每一個字節,雖然編解碼變得低效了些,但編碼的緊湊度卻是有目共睹,因此這種編碼方式本質上還是一種時間換空間的權衡結果。

由於歷史原因,FISCO BCOS中使用了RLP編碼作爲多處信息交換協議,貿然換用其他並行化友好的序列化方案可能會帶來較大的開發負擔。基於這一考慮,我們決定在原有的RLP編解碼方案稍作修改,通過爲每個被編碼的元素添加額外的位置偏移信息,便可以做到並行解碼RLP的同時不會改動大量原有代碼。

 

2.交易驗籤 & 數據落盤開銷大

通過對交易驗籤和數據落盤部分的代碼進行拆解,我們發現兩者的主要功能都集中在一個耗時巨大的for循環。交易驗籤負責按序取出交易,然後從交易的簽名數據中取出(v, r, s)數據,並從中還原出交易發送者的公鑰,其中,還原公鑰這一步,由於涉及密碼學算法,因此耗時不少;數據落盤負責從緩存中逐個取出交易相關數據,將其編碼爲JSON字符串後寫入磁盤,由於JSON編碼過程本身效率比較低,因此也是性能損失的重災區。

兩者代碼分別如下所示:

兩個過程共有的特點是,它們均是將同樣的操作應用到數據結構中不同的部分,對於這種類型的問題,可以直接使用數據級並行進行改造。所謂數據級並行,即是將數據作爲劃分對象,通過將數據劃分爲大小近似相等的片段,通過在多個線程上對不同的數據片段上進行操作,達到並行處理數據集的目的。

數據級並行唯一的附加要求是任務之間彼此獨立,毫無疑問,在FISCO BCOS的實現中,交易驗籤和數據落盤均滿足這一要求。

 

優化實踐

 

1.區塊解碼並行化

改造過程中,我們在系統中使用的普通RLP編碼的基礎上,加入了offset字段,用以索引每個Object的位置。如下圖所示,改造後編碼格式的開頭,仍然是對象的個數(Object num),但是在個數字段後,是一個記錄對象偏移量的數組(Offsets)。

數組中的每個元素有着固定的長度。因此要讀取某個Offset的值,只需向訪問數組一樣,根據Offset的序號直接索引便可以進行隨機訪問。在Offsets後,是與RLP編碼相同的對象列表。相應序號的Offset,指向相應序號的對象的RLP編碼字節位置。因此,任意解碼一個對象,只需要根據對象的序號,找到其偏移量,再根據偏移量,就可定位到相應對象的RLP編碼字節位置。

編碼流程也進行了重新設計。流程本身仍然基於遞歸的思路,對於輸入的對象數組,首先將對象數組的大小編碼在輸出編碼的開頭處,若數組大小超過1,則按序逐個取出待編碼對象並緩存其遞歸編碼,並在Offsets數組中記錄該對象的偏移位置,待數組遍歷完後,將緩存的對象編碼第一次性取出並附加至輸出編碼末尾;若數組大小爲1,則遞歸對其編碼並寫入輸出編碼的末尾,結束遞歸。

編碼流程的僞代碼如下:

偏移量的引入使解碼模塊能夠對元素編碼進行隨機訪問。Offsets的數組範圍可以在多個線程間均攤,從而每個線程可以並行訪問對象數組的不同部分,分別進行解碼。由於是隻讀訪問,這種並行方式是線程安全的,僅需最後再對輸出進行彙總即可。

解碼流程的僞代碼如下:

 

2.交易驗籤 & 數據落盤並行化

對於數據級並行,業內已有多種成熟的多線程編程模型。雖然Pthread這類顯式的多線程編程模型能夠提供對線程進行更精細的控制,但是需要我們對線程通信、同步擁有嫺熟的駕馭技巧。實現的複雜度越高,犯錯的機率越大,日後代碼維護的難度也相應增加。我們的主要目標僅僅對密集型循環進行並行化,因此在滿足需求的前提下,Keep It Simple & Stupid纔是我們的編碼原則,因此我們使用隱式的編程模型來達成我們的目的。

經過再三權衡,我們在市面上衆多隱式多線程編程模型中,選擇了來自Intel的線程構建塊(Thread Building Blocks,TBB)開源庫。在數據級並行方面,TBB算是老手,TBB運行時系統不僅屏蔽了底層工作線程的實現細節,還能夠根據任務量自動在處理器間平衡工作負載,從而充分利用底層CPU資源。

使用TBB後,交易驗籤和數據落盤的代碼如下所示:

可以看到,除了使用TBB提供的tbb::parallel_for進行並行循環和tbb::blocked_range引用數據分片外,循環體內的代碼幾乎沒有任何變化,接近C++原生語法正是TBB的特點。TBB提供了抽象層級較高的並行接口,如parallel_for、parallel_for_each這類泛型並行算法,從而使得改造能夠較爲容易地進行。同時,TBB不依賴任何語言或編譯器,只要有能支持ISO C++標準的編譯器,便有TBB的用武之地。

當然,使用TBB並不是完全沒有額外負擔,比如線程間安全還是需要開發人員的仔細分析來保證,但TBB考慮周到,提供了一套方便的工具來輔助我們解決線程間互斥的問題,如原子變量、線程局部存儲和並行容器等,這些並行工具同樣被廣泛地應用在FISCO BCOS中,爲FISCO BCOS的穩定運行保駕護航。

 

#寫在最後#

經過一套並行優化的組合拳,FISCO BCOS的性能表現更上層樓。壓力測試的結果表明,FISCO BCOS的交易處理能力,相較於並行化改造之前,成功提升了1.74倍,基本達到了這個環節的預期效果。

但是我們也深深明白,性能優化之路漫漫,木桶最短的一板總是交替出現,並行之道在於,通過反覆的分析、拆解、量化和優化,使得各模塊互相配合齊頭並進,整個系統達到優雅的平衡,而最優解總是在“跳一跳”才能夠得着的地方。

 


 

我們鼓勵機構成員、開發者等社區夥伴參與開源共建事業,有你在一起,會更了不起。多樣參與方式:

1 進入微信社羣,隨時隨地與圈內最活躍、最頂尖的團隊暢聊技術話題(進羣請添加小助手微信,微信ID:fiscobcosfan);

2 訂閱我們的公衆號:“FISCO BCOS開源社區”,我們爲你準備了開發資料庫、最新FISCO BCOS動態、活動、大賽等信息;

3 來Meetup與開發團隊面對面交流,FISCO BCOS正在全國舉辦巡迴Meetup,深圳、北京、上海、成都……歡迎您公衆號在菜單欄【找活動】中找到附近的Meetup,前往結識技術大咖,暢聊硬核技術;

4 參與代碼貢獻,您可以在Github提交Issue進行問題交流,歡迎向FISCO BCOS提交Pull Request,包括但不限於文檔修改、修復發現的bug、提交新的功能特性。

代碼貢獻指引:

https://github.com/FISCO-BCOS/FISCO-BCOS/blob/master/docs/CONTRIBUTING_CN.md

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章