當前位置:
首頁 > 知識 > 性能之殤:從馮·諾依曼瓶頸談起

性能之殤:從馮·諾依曼瓶頸談起

機器之心專欄

作者:JohnLui

本文作者根據自己的認知,討論了人們為提高性能做出的種種努力,包括硬體層面的 CPU、RAM、磁碟,操作系統層面的並發、並行、事件驅動,軟體層面的多進程、多線程,網路層面的分散式等。

本文共分為七個部分:

天才馮·諾依曼與馮·諾依曼瓶頸

分支預測、流水線與多核 CPU

通用電子計算機的胎記:事件驅動

Unix 進程模型的局限

DPDK、SDN 與大頁內存

現代計算機最親密的夥伴:局部性與樂觀

分散式計算、超級計算機與神經網路共同的瓶頸

(一)天才馮·諾依曼與馮·諾依曼瓶頸

電子計算機與信息技術是最近幾十年人類科技發展最快的領域,無可爭議地改變了每個人的生活:從生活方式到戰爭方式,從烹飪方式到國家治理方式,都被計算機和信息技術徹底地改變了。如果說核武器徹底改變了國與國之間相處的模式,那麼計算機與信息技術則徹底改變了人類這個物種本身,人類的進化也進入了一個新的階段。

簡單地說,生物進化之前還有化學進化。然而細胞一經誕生,中心法則的分子進化就趨於停滯了:38 億年來,中心法則再沒有新的變動,所有的蛋白質都由 20 種標準氨基酸連成,連鹼基與氨基酸對應關係也沿襲至今,所有現代生物共用一套標準遺傳密碼。正如中心法則是化學進化的產物,卻因為開創了生物進化而停止了化學進化,人類是生物進化的產物,也因為開創了文化進化和技術進化而停止了生物進化——進化已經走上了更高的維度。 ——《進化的階次 | 混亂博物館》

本文目標

本文的目標是在我有限的認知範圍內,討論一下人們為了提高性能做出的種種努力,這裡面包含硬體層面的 CPU、RAM、磁碟,操作系統層面的並發、並行、事件驅動,軟體層面的多進程、多線程,網路層面的分散式,等等等等。事實上,上述名詞並不局限於某一個層面,計算機從 CPU 內的門電路到顯示器上瀏覽器中的某行字,是層層協作才得以實現的;計算機科學中的許多概念,都跨越了層級:事件驅動就是 CPU 和操作系統協作完成的。

天才 馮·諾依曼

馮·諾依曼 1903 年 12 月 28 日出生於奧匈帝國布達佩斯,1957 年 2 月 8 日卒於美國,終年 53 歲。在他短暫的一生中,他取得了巨大的成就,遠不止於世人熟知的「馮·諾依曼架構」。

約翰·馮·諾伊曼,出生於匈牙利的美國籍猶太人數學家,現代電子計算機與博弈論的重要創始人,在泛函分析、遍歷理論、幾何學、拓撲學和數值分析等眾多數學領域及計算機學、量子力學和經濟學中都有重大貢獻。 ——約翰·馮·諾伊曼的維基百科

除了對計算機科學的貢獻,他還有一個稱號不被大眾所熟知:「博弈論之父」。博弈論被認為是 20 世紀經濟學最偉大的成果之一。(說到博弈論,我相信大多數人第一個想到的肯定跟我一樣,那就是「納什均衡」)

馮·諾依曼架構

馮·諾依曼由於在曼哈頓工程中需要大量的運算,從而使用了當時最先進的兩台計算機 Mark I 和 ENIAC,在使用 Mark I 和 ENIAC 的過程中,他意識到了存儲程序的重要性,從而提出了存儲程序邏輯架構。

「馮·諾依曼架構」定義如下:

以運算單元為中心

採用存儲程序原理

存儲器是按地址訪問、線性編址的空間

控制流由指令流產生

指令由操作碼和地址碼組成

數據以二進位編碼

優勢

馮·諾依曼架構第一次將存儲器和運算器分開,指令和數據均放置於存儲器中,為計算機的通用性奠定了基礎。雖然在規範中計算單元依然是核心,但馮·諾依曼架構事實上導致了以存儲器為核心的現代計算機的誕生。

註:請各位在心裡明確一件事情:存儲器指的是內存,即 RAM。磁碟理論上屬於輸入輸出設備。

該架構的另一項重要貢獻是用二進位取代十進位,大幅降低了運算電路的複雜度。這為晶體管時代超大規模集成電路的誕生提供了最重要的基礎,讓我們實現了今天手腕上的 Apple Watch 運算性能遠超早期大型計算機的壯舉,這也是摩爾定律得以實現的基礎。

馮·諾伊曼瓶頸

馮·諾依曼架構為計算機大提速鋪平了道路,卻也埋下了一個隱患:在內存容量指數級提升以後,CPU 和內存之間的數據傳輸帶寬成為了瓶頸。

上圖是 i9-7980XE 18 核 36 線程的民用最強 CPU,其配合超頻過的 DDR4 3200MHz 的內存,測試出的內存讀取速度為 90GB/S。看起來很快了是不是?看看圖中的 L1 Cache,3.7TB/S。

我們再來算算時間。這顆 CPU 最大睿頻 4.4GHz,就是說 CPU 執行一個指令需要的時間是 0.000000000227273 秒,即 0.22ns(納秒),而內存的延遲是 68.1ns。換句話說,只要去內存里取一個位元組,就需要 CPU 等待 300 個周期,何其的浪費 CPU 的時間啊。

CPU L1 L2 L3 三級緩存是使用和 CPU 同樣的 14 納米工藝製造的硅半導體,每一個 bit 都使用六個場效應管(通俗解釋成三極體)構成,成本高昂且非常佔用 CPU 核心面積,故不能做成很大容量。

除此之外,L1 L2 L3 三級緩存對計算機速度的提升來源於計算機內存的「局部性」,相關內容我們之後會專門討論。

(二)分支預測、流水線與多核 CPU

CPU 硬體為了提高性能,逐步發展出了指令流水線(分支預測)和多核 CPU,本文我們就將簡單地探討一下它們的原理和效果。

指令流水線

在一台純粹的圖靈機中,指令是一個一個順序執行的。而現實世界的通用計算機所用的很多基礎演算法都是可以並行的,例如加法器和乘法器,它們可以很容易地被切分成可以同時運行的多個指令,這樣就可以大幅提升性能。

指令流水線,說白了就是 CPU 電路層面的並發。

Intel Core i7 自 Sandy Bridge(2010)架構以來一直都是 14 級流水線設計。基於 Cedar Mill 架構的最後一代奔騰 4,在 2006 年就擁有 3.8GHz 的超高頻率,卻因為其長達 31 級的流水線而成了為樣子貨,被 AMD 1GHz 的晶元按在地上摩擦。

RISC 機器的五層流水線示意圖

下圖形象地展示了流水線式如何提高性能的。

缺點

指令流水線通過硬體層面的並發來提高性能,卻也帶來了一些無法避免的缺點。

設計難度高,一不小心就成為了高頻低能的奔四

並發導致每一條指令的執行時間變長

優化難度大,有時候兩行代碼的順序變動就可能導致數倍的性能差異,這對編譯器提出了更高的要求

如果多次分支預測失敗,會導致嚴重的性能損失

分支預測

指令形成流水線以後,就需要一種高效的調控來保證硬體層面並發的效果:最佳情況是每條流水線里的十幾個指令都是正確的,這樣完全不浪費時鐘周期。而分支預測就是干這個的:

分支預測器猜測條件表達式兩路分支中哪一路最可能發生,然後推測執行這一路的指令,來避免流水線停頓造成的時間浪費。但是,如果後來發現分支預測錯誤,那麼流水線中推測執行的那些中間結果全部放棄,重新獲取正確的分支路線上的指令開始執行,這就帶來了十幾個時鐘周期的延遲,這個時候,這個 CPU 核心就是完全在浪費時間。

幸運的是,當下的主流 CPU 在現代編譯器的配合下,把這項工作做得越來越好了。

還記得那個讓 Intel CPU 性能跌 30% 的漏洞補丁嗎,那個漏洞就是 CPU 設計的時候,分支預測設計的不完善導致的。

多核 CPU

多核 CPU 的每一個核心擁有自己獨立的運算單元、寄存器、一級緩存、二級緩存,所有核心共用同一條內存匯流排,同一段內存。

多核 CPU 的出現,標誌著人類的集成電路工藝遇到了一個嚴酷的瓶頸,沒法再大規模提升單核性能,只能使用多個核心來聊以自慰。實際上,多核 CPU 性能的提升極其有限,遠不如增加一點點單核頻率提升的性能多。

優勢

多核 CPU 的優勢很明顯,就是可以並行地執行多個圖靈機,可以顯而易見地提升性能。只不過由於使用同一條內存匯流排,實際帶來的效果有限,並且需要操作系統和編譯器的密切配合才行。

題外話: AMD64 技術可以運行 32 位的操作系統和應用程序,所用的方法是依舊使用 32 位寬的內存匯流排,每計算一次要取兩次內存,性能提升也非常有限,不過好處就是可以使用大於 4GB 的內存了。大家應該都沒忘記第一篇文章中提到的馮·諾依曼架構擁有 CPU 和內存通信帶寬不足的弱點。(註:AMD64 技術是和 Intel 交叉授權的專利,i7 也是這麼設計的)

劣勢

多核 CPU 劣勢其實更加明顯,但是人類也沒有辦法,誰不想用 20GHz 的 CPU 呢,誰想用這八核的 i7 呀。

內存讀寫效率不變,甚至有降低的風險

操作系統複雜度提升很多倍,計算資源的管理複雜了太多了

依賴操作系統的進步:微軟以肉眼可見的速度,在這十幾年間大幅提升了 Windows 的多核效率和安全性:XP 只是能利用,7 可以自動調配一個進程在多個核心上遊走,2008R2 解決了依賴 CPU0 調度導致死機的 bug(中國的銀行提的 bug 哦),8 可以利用多核心啟動,10 優化了殺進程依賴 CPU0 的問題。

超線程技術

Intel 的超線程技術是將 CPU 核心內部再分出兩個邏輯核心,只增加了 5% 的裸面積,就帶來了 15%~30% 的性能提升。

懷念過去

Intel 肯定懷念摩爾定律提出時候的黃金年代,只依靠工藝的進步,就能一兩年就性能翻番。AMD 肯定懷念 K8 的黃金一代,1G 戰 4G,靠的就是把內存控制器從北橋晶元移到 CPU 內部,提升了 CPU 和內存的通信效率,自然性能倍增。而今天,人類的技術已經到達了一個瓶頸,只能通過不斷的提升 CPU 和操作系統的複雜度來獲得微弱的性能提升,嗚呼哀哉。

不過我們也不能放棄希望,AMD RX VAGA64 顯卡擁有 2048 位的顯存位寬,理論極限還是很恐怖的,這可能就是未來內存的發展方向。

(三)通用電子計算機的胎記:事件驅動

Event-Driven(事件驅動)這個詞這幾年隨著 Node.js 的大熱也成了一個熱詞,似乎已經成了「高性能」的代名詞,殊不知事件驅動其實是通用計算機的胎記,是一種與生俱來的能力。本文我們就要一起了解一下事件驅動的價值和本質。

通用電子計算機中的事件驅動

首先我們定義當下最火的 x86 PC 機為典型的通用電子計算機:可以寫文章,可以打遊戲,可以上網聊天,可以讀 U 盤,可以列印,可以設計三維模型,可以編輯渲染視頻,可以作路由器,還可以控制巨大的工業機器。那麼,這種計算機的事件驅動能力就很容易理解了:

假設 Chrome 正在播放 Youtube 視頻,你按下了鍵盤上的空格鍵,視頻暫停了。這個操作就是事件驅動:計算機獲得了你單擊空格的事件,於是把視頻暫停了。

假設你正在跟人聊 QQ,別人發了一段話給你,計算機獲得了網路傳輸的事件,於是將信息提取出來顯示到了屏幕上,這也是事件驅動。

事件驅動的實現方式

事件驅動本質是由 CPU 提供的,因為 CPU 作為 控制器 + 運算器,他需要隨時響應意外事件,例如上面例子中的鍵盤和網路。

CPU 對於意外事件的響應是依靠 Exception Control Flow(異常控制流)來實現的。

強大的異常控制流

異常控制流是 CPU 的核心功能,它是以下聽起來就很牛批的功能的基礎:

時間片

CPU 時間片的分配也是利用異常控制流來實現的,它讓多個進程在宏觀上在同一個 CPU 核心上同時運行,而我們都知道在微觀上在任一個時刻,每一個 CPU 核心都只能運行一條指令。

虛擬內存

這裡的虛擬內存不是 Windows 虛擬內存,是 Linux 虛擬內存,即邏輯內存。

邏輯內存是用一段內存和一段磁碟上的存儲空間放在一起組成一個邏輯內存空間,對外依然表現為「線性數組內存空間」。邏輯內存引出了現代計算機的一個重要的性能觀念:

內存局部性天然的讓相鄰指令需要讀寫的內存空間也相鄰,於是可以把一個進程的內存放到磁碟上,再把一小部分的「熱數據」放到內存中,讓其作為磁碟的緩存,這樣可以在降低很少性能的情況下,大幅提升計算機能同時運行的進程的數量,大幅提升性能。

虛擬內存的本質其實是使用 緩存 + 樂觀 的手段提升計算機的性能。

系統調用

系統調用是進程向操作系統索取資源的通道,這也是利用異常控制流實現的。

硬體中斷

鍵盤點擊、滑鼠移動、網路接收到數據、麥克風有聲音輸入、插入 U 盤這些操作全部需要 CPU 暫時停下手頭的工作,來做出響應。

進程、線程

進程的創建、管理和銷毀全部都是基於異常控制流實現的,其生命周期的鉤子函數也是操作系統依賴異常控制流實現的。線程在 Linux 上和進程幾乎沒有功能上的區別。

編程語言中的 try catch

C++ 編譯成的二進位程序,其異常控制語句是直接基於異常控制流的。Java 這種硬虛擬機語言,PHP 這種軟虛擬機語言,其異常控制流的一部分也是有最底層的異常控制流提供的,另一部分可以由邏輯判斷來實現。

基於異常控制流的事件驅動

其實現在人們在談論的事件驅動,是 Linux kernel 提供的 epoll,是 2002 年 10 月 18 號伴隨著 kernel 2.5.44 發布的,是 Linux 首次將操作系統中的 I/O 事件的異常控制流暴露給了進程,實現了本文開頭提到的 Event-Driven(事件驅動)。

Kqueue

FreeBSD 4.1 版本於 2000 年發布,起攜帶的 Kqueue 是 BSD 系統中事件驅動的 API 提供者。BSD 系統如今已經遍地開花,從 macOS 到 iOS,從 watchOS 到 PS4 遊戲機,都受到了 Kqueue 的蒙蔭。

epoll 是什麼

操作系統本身就是事件驅動的,所以 epoll 並不是什麼新發明,而只是把本來不給用戶空間用的 api 暴露在了用戶空間而已。

epoll 做了什麼

網路 IO 是一種純非同步的 IO 模型,所以 Nginx 和 Node.js 都基於 epoll 實現了完全的事件驅動,獲得了相比於 select/poll 巨量的性能提升。而磁碟 IO 就沒有這麼幸運了,因為磁碟本身也是單體阻塞資源:即有進程在寫磁碟的時候,其他寫入請求只能等待,就是天王老子來了也不行,磁碟做不到呀。所以磁碟 IO 是基於 epoll 實現的非阻塞 IO,但是其底層依舊是非同步阻塞,即便這樣,性能也已經爆棚了。Node.js 的磁碟 IO 性能遠超其他解釋型語言,過去幾年在 web 後端霸佔了一些對磁碟 IO 要求高的領域。

(四)Unix 進程模型的局限

Unix 系統 1969 年誕生於 AT&T 旗下的貝爾實驗室。1971 年,Ken Thompson(Unix 之父)和 Dennis Ritchie(C 語言之父)共同發明了 C 語言,並在 1973 年用 C 語言重寫了 Unix。

Unix 自誕生起就是多用戶、多任務的分時操作系統,其引入的「進程」概念是計算機科學中最成功的概念之一,幾乎所有現代操作系統都是這一概念的受益者。但是進程也有局限,由於 AT&T 是做電話交換起家,所以 Unix 進程在設計之初就是延續的電話交換這個業務需求:保證電話交換的效率,就夠了。

1984 年,Richard Stallman 發起了 GNU 項目,目標是創建一個完全自由且向下兼容 Unix 的操作系統。之後 Linus Torvalds 與 1991 年發布了 Linux 內核,和 GNU 結合在了一起形成了 GNU/Linux 這個當下最成功的開源操作系統。所以 Redhat、CentOS、Ubuntu 這些如雷貫耳的 Linux 伺服器操作系統,他們的內存模型也是高度類似 Unix 的。

Unix 進程模型介紹

進程是操作系統提供的一種抽象,每個進程在自己看來都是一個獨立的圖靈機:獨佔 CPU 核心,一個一個地運行指令,讀寫內存。進程是計算機科學中最重要的概念之一,是進程使多用戶、多任務成為了可能。

上下文切換

操作系統使用上下文切換讓一個 CPU 核心上可以同時運行多個進程:在宏觀時間尺度,例如 5 秒內,一台電腦的用戶會認為他的桌面進程、音樂播放進程、滑鼠響應進程、瀏覽器進程是在同時運行的。

圖片來自《CS:APP》

上下文切換的過程

以下就是 Linux 上下文切換的過程:

假設正在運行網易雲音樂進程,你突然想搜歌,假設焦點已經位於搜索框內。

當前進程是網易雲音樂,它正在優哉游哉的播放著音樂

你突然打字,CPU 接到鍵盤發起的中斷信號(異常控制流中的一個異常),準備調起鍵盤處理進程

將網易雲音樂進程的寄存器、棧指針、程序計數器保存到內存中

將鍵盤處理進程的寄存器、棧指針、程序計數器從內存中讀出來,寫入到 CPU 內部相應的模塊中

執行程序計數器的指令,鍵盤處理程序開始處理鍵盤輸入

完成了一次上下文切換

名詞解釋

寄存器:CPU 核心裡的用於暫時存儲指令、地址和數據的電路,和內核頻率一樣,速度極快

棧指針:該進程所擁有的棧的指針

程序計數器:簡稱 PC,它存儲著內核將要執行的下一個指令的內存地址。程序計數器是圖靈機的核心組成部分。還記得馮·諾依曼架構嗎,它的一大創造就是把指令和數據都存在內存里,讓計算機獲得了極大的自由度。

Unix 進程模型的局限

Unix 進程模型十分的清晰,上下文切換使用了一個非常簡單的操作就實現了多個進程的宏觀同時運行,是一個偉大的傑作。但是它卻存在著一個潛在的缺陷,這個缺陷在 Unix 誕生數十年之後才漸漸浮出了水面。

致命的內存

進程切換過程中需要分別寫、讀一次內存,這個操作在 Unix 剛發明的時候沒有發現有什麼性能問題,但是 CPU 裹挾著摩爾定律一路狂奔,2000 年,AMD 領先 Intel 兩天發布了第一款 1GHz 的微處理器「AMD Athlon 1GHz」,此時一個指令的執行時間已經低到了 1ns,而其內存延遲高達 60ns,這導致了一個以前不曾出現的問題:

上下文切換讀寫內存的時間成了整個系統的性能瓶頸。

軟體定義一切

我們將在下一篇文章探討 SDN(軟體定義網路),在這裡我們先來看一下「軟體定義一切」這個概念。當下,不僅有軟體定義網路,還有軟體定義存儲,甚至出現了軟體定義基礎架構(這不就是雲計算嘛)。是什麼導致了軟體越來越強勢,開始傾入過去只有專業的硬體設備才能提供的高性能高穩定性服務呢?我認為,就是通用計算機的發展導致的,確切地說,是 CPU 和網路的發展導致的。

當前的民用頂級 CPU 的性能已經爆表,因為規模巨大,所以其價格也要顯著低於同性能的專用處理器:自建 40G 軟路由的價格大約是 40G 專用路由價格的二十分之一。

(五)DPDK、SDN 與大頁內存

上文我們說到,當今的 x86 通用微處理器已經擁有了十分強大的性能,得益於其龐大的銷量,讓它的價格和專用 CPU 比也有著巨大的優勢,於是,軟體定義一切誕生了!

軟路由

說到軟路由,很多人都露出了會心的微笑,因為其擁有低廉的價格、超多的功能、夠用的性能和科學上網能力。現在網上能買到的軟路由,其本質就是一個 x86 PC 加上多個網口,大多是基於 Linux 或 BSD 內核,使用 Intel 低端被動散熱 CPU 打造出的千兆路由器,幾百塊就能實現千兆的性能,最重要的是擁有 QOS、多路撥號、負載均衡、防火牆、VPN 組網、科學上網等強大功能,傳統路由器拋開科學上網不談,其他功能也不是幾百塊就搞得定的。

軟路由的弱點

軟路由便宜,功能強大,但是也有弱點。它最大的弱點其實是性能:傳統 *UNIX 網路棧的性能實在是不高。

軟路由的 NAT 延遲比硬路由明顯更大,而且幾百塊的軟路由 NAT 性能也不夠,跑到千兆都難,而幾百塊的硬路由跑到千兆很容易。那怎麼辦呢?改操作系統啊。

SDN

軟體定義網路,其本質就是使用計算機科學中最常用的「虛擬機」構想,將傳統由硬體實現的 交換、網關、路由、NAT 等網路流量控制流程交由軟體來統一管理:可以實現硬體不動,網路結構瞬間變化,避免了傳統的停機維護調試的煩惱,也為大規模公有雲計算鋪平了道路。

虛擬機

虛擬機的思想自底向上完整地貫穿了計算機的每一個部分,硬體層有三個場效應管虛擬出的 SRAM、多個內存晶元虛擬出的一個「線性數組內存」,軟體層有 jvm 虛擬機,PHP 虛擬機(解釋器)。自然而然的,當網路成為了更大規模計算的瓶頸的時候,人們就會想,為什麼網路不能虛擬呢?

OpenFlow

最開始,SDN 還是基於硬體來實施的。Facebook 和 Google 使用的都是 OpenFlow 協議,作用在數據鏈路層(使用 MAC 地址通信的那一層,也就是普通交換機工作的那一層),它可以統一管理所有網關、交換等設備,讓網路架構實時地做出改變,這對這種規模的公司所擁有的巨大的數據中心非常重要。

DPDK

DPDK 是 SDN 更前沿的方向:使用 x86 通用 CPU 實現 10Gbps 甚至 40Gbps 的超高速網關(路由器)。

DPDK 是什麼

Intel DPDK 全稱為 Intel Data Plane Development Kit,直譯為「英特爾數據平面開發工具集」,它可以擺脫 *UNIX 網路數據包處理機制的局限,實現超高速的網路包處理。

DPDK 的價值

當下,一台 40G 核心網管路由器動輒數十萬,而 40G 網卡也不會超過一萬塊,而一顆性能足夠的 Intel CPU 也只需要幾萬塊,軟路由的性價比優勢是巨大的。

實際上,阿里雲和騰訊雲也已經基於 DPDK 研發出了自用的 SDN,已經創造了很大的經濟價值。

怎麼做到的?

DPDK 使用自研的數據鏈路層(MAC 地址)和網路層(ip 地址)處理功能(協議棧),拋棄操作系統(Linux,BSD 等)提供的網路處理功能(協議棧),直接接管物理網卡,在用戶態處理數據包,並且配合大頁內存和 NUMA 等技術,大幅提升了網路性能。有論文做過實測,10G 網卡使用 Linux 網路協議棧只能跑到 2G 多,而 DPDK 分分鐘跑滿。

用戶態網路棧

上篇文章我們已經說到,Unix 進程在網路數據包過來的時候,要進行一次上下文切換,需要分別讀寫一次內存,當系統網路棧處理完數據把數據交給用戶態的進程如 Nginx 去處理還會出現一次上下文切換,還要分別讀寫一次內存。夭壽啦,一共 1200 個 CPU 周期呀,太浪費了。

而用戶態協議棧的意思就是把這塊網卡完全交給一個位於用戶態的進程去處理,CPU 看待這個網卡就像一個假肢一樣,這個網卡數據包過來的時候也不會引發系統中斷了,不會有上下文切換,一切都如絲般順滑。當然,實現起來難度不小,因為 Linux 還是分時系統,一不小心就把 CPU 時間占完了,所以需要小心地處理阻塞和緩存問題。

NUMA

NUMA 來源於 AMD Opteron 微架構,其特點是將 CPU 直接和某幾根內存使用匯流排電路連接在一起,這樣 CPU 在讀取自己擁有的內存的時候就會很快,代價就是讀取別 U 的內存的時候就會比較慢。這個技術伴隨著伺服器 CPU 核心數越來越多,內存總量越來越大的趨勢下誕生的,因為傳統的模型中不僅帶寬不足,而且極易被搶佔,效率下降的厲害。

NUMA 利用的就是電子計算機(圖靈機 + 馮·諾依曼架構)天生就帶的渦輪:局部性。渦輪:汽車發動機加上渦輪,可以讓動力大增油耗降低

細說大頁內存

內存分頁

為了實現虛擬內存管理機制,前人們發明了內存分頁機制。這個技術誕生的時候,內存分頁的默認大小是 4KB,而到了今天,絕大多數操作系統還是用的這個數字,但是內存的容量已經增長了不知道多少倍了。

TLB miss

TLB(Translation Lookaside Buffers)轉換檢測緩衝區,是內存控制器中為增虛擬地址到物理地址的翻譯速度而設立的一組電子元件,最近十幾年已經隨著內存控制器被集成到了 CPU 內部,每顆 CPU 的 TLB 都有固定的長度。

如果緩存未命中(TLB miss),則要付出 20-30 個 CPU 周期的帶價。假設應用程序需要 2MB 的內存,如果操作系統以 4KB 作為分頁的單位,則需要 512 個頁面,進而在 TLB 中需要 512 個表項,同時也需要 512 個頁表項,操作系統需要經歷至少 512次TLB Miss 和 512次缺頁中斷才能將 2MB 應用程序空間全部映射到物理內存;然而,當操作系統採用 2MB 作為分頁的基本單位時,只需要一次TLB Miss 和一次缺頁中斷,就可以為 2MB 的應用程序空間建立虛實映射,並在運行過程中無需再經歷 TLB Miss 和缺頁中斷。

大頁內存

大頁內存 HugePage 是一種非常有效的減少 TLB miss 的方式,讓我們來進行一個簡單的計算。

2013 年發布的 Intel Haswell i7-4770 是當年的民用旗艦 CPU,其在使用 64 位 Windows 系統時,可以提供 1024 長度的 TLB,如果內存頁的大小是 4KB,那麼總緩存內存容量為 4MB,如果內存頁的大小是 2MB,那麼總緩存內存容量為 2GB。顯然後者的 TLB miss 概率會低得多。

DPDK 支持 1G 的內存分頁配置,這種模式下,一次性緩存的內存容量高達 1TB,絕對夠用了。

不過大頁內存的效果沒有理論上那麼驚人,DPDK 實測有 10%~15% 的性能提升,原因依舊是那個天生就帶的渦輪:局部性。

(六)現代計算機最親密的夥伴:局部性與樂觀

馮·諾依曼架構中,指令和數據均存儲在內存中,徹底打開了計算機「通用」的大門。這個結構中,「線性數組」內存天生攜帶了一個渦輪:局部性。

局部性分類

空間局部性

空間局部性是最容易理解的局部性:如果一段內存被使用,那麼之後,離他最近的內存也最容易被使用,無論是數據還是指令都是這樣。舉一個淺顯易懂的例子:

循環處理一個 Array,當處理完了 [2] 之後,下一個訪問的就是 [3],他們在內存里是相鄰的。

時間局部性

如果一個變數所在的內存被訪問過,那麼接下來這一段內存很可能被再次訪問,例子也非常簡單:

在一個 function 內,一個內存地址很可能被訪問、修改多次。

樂觀

「樂觀」作為一種思考問題的方式廣泛存在於計算機中,從硬體設計、內存管理、應用軟體到資料庫均廣泛運用了這種思考方式,並給我們帶來了十分可觀的性能收益。

樂觀的 CPU

第一篇文章中的 L1 L2 L3 三級緩存和第二篇文章中的分支預測與流水線,均是樂觀思想的代表。

樂觀的虛擬內存

虛擬內存依據計算機內存的局部性,將磁碟作為內存的本體,將內存作為磁碟的緩存,用很小的性能代價帶來了數十倍並發進程數,是樂觀思想的集大成者。

樂觀的緩存

Java 經典面試題 LRU 緩存實現,也是樂觀思想的一種表達。

同樣,鳥哥的 yac 也是這一思想的強烈體現。

設計 Yac 的經驗假設

對於一個應用來說, 同名的 Cache 鍵, 對應的 Value, 大小几乎相當.

不同的鍵名的個數是有限的.

Cache 的讀的次數, 遠遠大於寫的次數.

Cache 不是資料庫, 即使 Cache 失效也不會帶來致命錯誤.

Yac 的限制

key 的長度最大不能超過 48 個字元. (我想這個應該是能滿足大家的需求的, 如果你非要用長 Key, 可以 MD5 以後再存)

Value 的最大長度不能超過 64M, 壓縮後的長度不能超過 1M.

當內存不夠的時候, Yac 會有比較明顯的踢出率, 所以如果要使用 Yac, 那麼盡量多給點內存吧.

樂觀鎖

樂觀鎖在並發控制和資料庫設計里都擁有重要地位,其本質就是在特定的需求下,假定不會衝突,衝突之後再浪費較長時間處理,比直接每次請求都浪費較短時間檢測,總體的性能高。樂觀鎖在演算法領域有著非常豐富而成熟的應用。

樂觀的分散式計算

分散式計算的核心思想就是樂觀,由 95% 可靠的 PC 機組成的分散式系統,起可靠性也不會達到 99.99%,但是絕大多數場景下,99% 的可靠性就夠了,畢竟拿 PC 機做分散式比小型機便宜得多嘛。下一篇文章我會詳細介紹分散式計算的性能之殤,此處不再贅述。

樂觀的代價

出來混,早晚是要還的。

樂觀給了我們很多的好處,總結起來就是一句話:以微小的性能損失換來大幅的性能提升。但是,人在河邊走,哪有不濕鞋。每一個 2015 年 6 月入 A 股的散戶,都覺得大盤還能再翻一番,豈不知一周之後,就是股災了。

樂觀的代價來自於「微小的性能損失」,就跟房貸市場中「微小的風險」一樣,當大環境小幅波動的時候,他確實能承擔壓力,穩住系統,但是怕就怕突然雪崩:

虛擬內存中的內存的局部性突然大幅失效,磁碟讀寫速度成了內存讀寫速度,系統卡死

分散式資料庫的六台機器中的 master 掛了,系統在一秒內選舉出了新的 master,你以為系統會穩定運行?master 掛掉的原因就是壓力過大,這樣就會導致新的 master 瞬間又被打掛,然後一台一台地繼續,服務徹底失效。例如:「故障說明」對六月六日 LeanCloud 多項服務發生中斷的說明

(七)分散式計算、超級計算機與神經網路共同的瓶頸

分散式計算是這些年的熱門話題,各種大數據框架層出不窮,容器技術也奮起直追,各類資料庫(Redis、ELasticsearch、MongoDB)也大搞分散式,可以說是好不熱鬧。分散式計算在大熱的同時,也存在著兩台機器也要硬上 Hadoop 的「面向簡歷編程」,接下來我就剖析一下分散式計算的本質,以及我的理解和體會。

分散式計算的本質

分散式計算來源於人們日益增長的性能需求與落後的 x86 基礎架構之間的矛盾。恰似設計模式是面向對象對現實問題的一種妥協。

x86 伺服器

x86 伺服器,俗稱 PC 伺服器、微機伺服器,近二十年以迅雷不及掩耳盜鈴之勢全面搶佔了絕大部分的伺服器市場,它和小型機比只有一個優勢,其他的全是缺點,性能、可靠性、可擴展性、佔地面積都不如小型機,但是一個優勢就決定了每年 2000 多億美元的 IDC 市場被 x86 伺服器佔領了 90%,這個優勢就是價格。畢竟有錢能使磨推鬼嘛。

現有的分散式計算,無論是 Hadoop 之類的大數據平台,還是 HBase 這樣的分散式資料庫,無論是 Docker 這種容器排布,還是 Redis 這種樸素分散式資料庫,其本質都是因為 x86 的擴展性不夠好,導致大家只能自己想辦法利用網路來自己構建一個宏觀上更強性能更高負載能力的計算機。

x86 分散式計算,是一種新的計算機結構。

基於網路的 x86 伺服器分散式計算,其本質是把網路當做匯流排,設計了一套新的計算機體系結構:

每一台機器就等於一個運算器加一個存儲器

master 節點就是控制器加輸入設備、輸出設備

x86 分散式計算的弱點

上古時代,小型機的擴展能力是非常變態的,到今天,基於小型機的 Oracle 資料庫系統依舊能做到驚人的性能和可靠性。實際上單顆 x86 CPU 的性能已經遠超 IBM 小型機用的 PowerPC,但是當數量來到幾百顆,x86 伺服器集群就敗下陣來,原因也非常簡單:

小型機是專門設計的硬體和專門設計的軟體,只面向這種規模(例如幾百顆 CPU)的計算

小型機是完全閉源的,不需要考慮擴展性,特定的幾種硬體在穩定性上前進了一大步

x86 的 IO 性能被架構鎖死了,各種匯流排、PCI、PCIe、USB、SATA、乙太網,為了個人計算機的便利性,犧牲了很多的性能和可靠性

小型機使用匯流排通信,可以實現極高的信息傳遞效率,極其有效的監控以及極高的故障隔離速度

x86 伺服器基於網路的分散式具有天然的缺陷:

操作系統決定了網路性能不足

網路需要使用事件驅動處理,比匯流排電路的延遲高几個數量級

PC 機的硬體不夠可靠,故障率高

很難有效監控,隔離故障速度慢

x86 分散式計算的基本套路

Google 系大數據處理框架

2003 年到 2004 年間,Google 發表了 MapReduce、GFS(Google File System)和 BigTable 三篇技術論文,提出了一套全新的分散式計算理論。MapReduce 是分散式計算框架,GFS(Google File System)是分散式文件系統,BigTable 是基於 Google File System 的數據存儲系統,這三大組件組成了 Google 的分散式計算模型。

Hadoop、Spark、Storm 是目前最重要的三大分散式計算系統,他們都是承襲 Google 的思路實現並且一步一步發展到今天的。

MapReduce 的基本原理也十分簡單:將可以並行執行的任務切分開來,分配到不同的機器上去處理,最終再匯總結果。而 GFS 是基於 Master-Slave 架構的分散式文件系統,其 master 只扮演控制者的角色,操控著所有的 slave 幹活。

Redis、MongoDB 的分散式

Redis 有兩個不同的分散式方案。Redis Cluster 是官方提供的工具,它通過特殊的協議,實現了每台機器都擁有數據存儲和分散式調節功能,性能沒有損失。缺點就是缺乏統一管理,運維不友好。Codis 是一個非常火的 Redis 集群搭建方案,其基本原理可以簡單地描述如下:通過一個 proxy 層,完全隔離掉了分散式調節功能,底層的多台機器可以任意水平擴展,運維十分友好。

MongoDB 官方提供了一套完整的分散式部署的方案,提供了 mongos 控制中心,config server 配置存儲,以及眾多的 shard(其底層一般依然有兩台互為主從強數據一致性的 mongod)。這三個組件可以任意部署在任意的機器上,MongoDB 提供了 master 選舉功能,在檢測到 master 異常後會自動選舉出新的 master 節點。

問題和瓶頸

人們費這麼大的勁研究基於網路的 x86 伺服器分散式計算,目的是什麼?還不是為了省錢,想用一大票便宜的 PC 機替換掉昂貴的小型機、大型機。雖然人們已經想盡了辦法,但還是有一些頑固問題無法徹底解決。

master 失效問題

無論怎樣設計,master 失效必然會導致服務異常,因為網路本身不夠可靠,所以監控系統的容錯要做的比較高,所以基於網路的分散式系統的故障恢復時間一般在秒級。而小型機的單 CPU 故障對外是完全無感的。

現行的選舉機制主要以節點上的數據以及節點數據之間的關係為依據,通過一頓猛如虎的數學操作,選舉出一個新的 master。邏輯上,選舉沒有任何問題,如果 master 因為硬體故障而失效,新的 master 會自動頂替上,並在短時間內恢復工作。

而自然界總是狠狠地打人類的臉:

硬體故障概率極低,大部分 master 失效都不是因為硬體故障

如果是流量過大導致的 master 失效,那麼選舉出新的 master 也無濟於事:提升集群規模才是解決之道

即使能夠及時地在一分鐘之內頂替上 master 的工作,那這一分鐘的異常也可能導致雪崩式的 cache miss,從磁碟緩存到虛擬內存,從 TLB 到三級緩存,再到二級緩存和一級緩存,全部失效。如果每一層的失效會讓系統響應時間增加五倍的話,那最終的總響應時長將是驚人的。

系統規模問題

無論是 Master-Slave 模式還是 Proxy 模式,整個系統的流量最終還是要落到一個特定的資源上。當然這個資源可能是多台機器,但是依舊無法解決一個嚴重的問題:系統規模越大,其本底性能損失就越大。

這其實是我們所在的這個宇宙空間的一個基本規律。我一直認為,這個宇宙里只有一個自然規律:熵增。既然我們這個宇宙是一個熵增宇宙,那麼這個問題就無法解決。

超級計算機

超級計算機可以看成一個規模特別巨大的分散式計算系統,他的性能瓶頸從目前的眼光來看,是超多計算核心(數百萬)的調節效率問題。其本質是通信速率不夠快,信息傳遞的太慢,讓數百萬核心一起工作,傳遞命令和數據的工作佔據了絕大多數的運行時間。

神經網路

深度學習這幾年大火,其原因就是卷積神經網路(CNN)造就的 AlphaGo 打敗了人類,計算機在這個無法窮舉的遊戲里徹底贏了。伴隨著 Google 帝國的強大推力,深度學習,機器學習,乃至人工智慧,這幾個詞在過去的兩年大火,特別是在中美兩國。現在拿手機拍張照背後都有機器學習你敢信?

機器學習的瓶頸,本質也是數據交換:機器學習需要極多的計算,而計算速度的瓶頸現在就在運算器和存儲器的通信上,這也是顯卡搞深度學習比 CPU 快數十倍的原因:顯存和 GPU 信息交換的速度極快。

九九歸一

分散式系統的性能問題,表現為多個方面,但是歸根到底,其原因只是一個非常單純的矛盾:人們日益增長的性能需求和數據一致性之間的矛盾。一旦需要強數據一致性,那就必然存在一個限制性能的瓶頸,這個瓶頸就是信息傳遞的速度。

同樣,超級計算機和神經網路的瓶頸也都是信息傳遞的速度。

那麼,信息傳遞速度的瓶頸在哪裡呢?

我個人認為,信息傳遞的瓶頸最表層是人類的硬體製造水平決定的,再往底層去是馮·諾依曼架構決定的,再往底層去是圖靈機的邏輯模型決定的。可是圖靈機是計算機可行的理論基礎呀,所以,還是怪這個熵增宇宙吧,為什麼規模越大維護成本越高呢,你也是個成熟的宇宙了,該學會自己把自己變成熵減宇宙了。

本文為機器之心專欄,轉載請聯繫本公眾號獲得授權。

------------------------------------------------


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 機器之心 的精彩文章:

首次成功用CNN自動生成代碼:北大研究者搞定了爐石傳說
數據並行化對神經網路訓練有何影響?谷歌大腦進行了實證研究

TAG:機器之心 |