效能之殤(三)-- 通用電子計算機的胎記:事件驅動
2018-11-13/ 閱讀數:3 / 分類:作業系統
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 要求高的領域。