python3 uvloop 簡介

asyncio 是Python 標準庫裏的一個異步 I/O 框架。在本文中,我們將介紹 uvloop : 這是 asyncio 默認事件循環的一個代替品,實現的功能完整,且即插即用。uvloop 是用 Cython 寫的,建於 libuv 之上。

uvloop 可以使 asyncio 更快。事實上,它至少比 nodejs、gevent 和其他 Python 異步框架要快 兩倍 。基於 uvloop 的 asyncio 的速度幾乎接近了 Go 程序的速度。

asyncio & uvloop

Asyncio 模塊在 PEP 3156 中引入,是一個網絡傳輸、協議和流量抽象化等的集合,帶有一個可插換的事件循環。這個事件循環是 asyncio 的核心。它給以下功能提供了 API:

  • 安排函數調用,
  • 通過網絡傳輸數據,
  • 執行 DNS 詢問,
  • 處理 OS 信號,
  • 可創建服務器和連接的方便抽象類
  • 異步地處理 subprocess

截止目前,uvloop 還智能在 *nix 平臺 和 Python 3.5 中使用。

uvloop是一個對 asyncio 默認事件循環的代替品。你可以用 pip 安裝它:

$ pip install uvloop

在 asyncio 代碼裏面使用 uvloop 也很簡單:

import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

上面這段代碼使得任何對 asyncio.get_event_loop() 的調用都將返回一個 uvloop 的實例。

實例2:

import asyncio
import uvloop
# 聲明使用 uvloop 事件循環
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = uvloop.new_event_loop()
asyncio.set_event_loop(loop)

架構

uvloop是用 Cython 寫的,其基礎是 libuv。

libuv 是 nodejs 使用的一個高性能、多平臺的異步 I/O 庫。由於 nodejs 使用很廣也很流行,使得 libuv 又快又穩定。

uvloop 實現了所有 asyncio 裏面的事件循環 API 。高級 Python 對象封裝了底層的 libuv 結構體和函數。爲了讓代碼乾淨、不重複,並保證手動內存管理都和 libuv 的原語生命週期保持一致,uvloop 使用了子類繼承的方法。

基準測試

爲了比較 uvloop 實現和其他實現方法的性能區別,我們創建了一個工具平臺(tool bench),來對 TCP 和 UNIX 套接字 I/O 和 HTTP 協議的性能進行測試。

基準測試服務器在一個 Docker 容器裏面運行,外面有一個負載生成工具(測試 HTTP 協議則使用 wrk),負責評估請求吞吐量和延遲。

本文提到的所有基準測試都是在一臺裝了 Ubuntu 的 Linux 機器上運行的。機器搭載了英特爾 Xeon CPU E5-1620 v2 @ 3.70GHz。我們用的是 Python 3.5,並且所有服務器都是單線程的。除此之外,對 Go 代碼我們設定 GOMAXPORCS=1, nodejs 不使用集羣,並且所有 Python 服務器都是單進程的。每個基準測試都設置了 TCP_NODELAY 標誌。

在Mac OS X上運行的基準測試得到了類似的結果。

TCP

該基準測試通過不同大小的消息,對一個簡單的 echo 服務器的性能進行了檢測。我們使用了 1、10和 100KB 的包。併發級別設爲 10,每個基準測試運行30秒。

參見完整的TCP基準測試報告

Python TCP 網絡互連的性能測試

所有基準測試的代碼在這裏 也可以看看Unix Socket的基準測試

簡單評論一下每個位置的情況:

  1. asyncio-steams。 使用內置的純 Python 事件循環的 asyncio 。在這個基準測試裏,我們測試了高級別流抽象的性能。我們用 asyncio.create_server() 來創建一個服務器。這個服務器回傳一對 (reader, writer) 給客戶端的協同程序。

  2. tornado。 這個服務器實現了一個簡單的 Tornado 協議,可以立即傳回它收到的任何數據。

  3. curio-steams。Curio 是Python 異步庫中的新生兒。和 asyncio-steams 一樣,在本次基準測試裏我們打算測試 curio 的數據流。我們使用 curio.make_steams() 來創建一對 (reader, writer) , 提供了一些高級API,比如readline()

  4. twisted。和Tornado類似,這裏我們測試了一個最簡單的 echo 的協議。

  5. curio。 這個基準測試檢驗 curio 套接字的性能:一個由 sock.recv() 和 sock.sendall() 協同程序組成的緊湊循環。

  6. uvloop-streams。 就像第二個(tornado)一樣,這裏我們測試 asyncio 高級數據流的性能,只不過這次我們使用的是 uvloop 。

  7. gevent。我們用 gevent.SteamServer 和一個 gevent 套接字,在緊湊循環中來發送/接收數據。

  8. asyncio。看起來原生的 asyncio 也很快!和第 2 個(tornado)和第 4 個(twisted)類似,這裏我們測試一個最簡的 echo 協議的性能。這個協議是用純 Python 的 asyncio 實現的。

  9. nodejs。我們用 net.createServer API來測試 nodejs v4.2.6 中的數據流性能。

  10. uvloop。 這個基準測試中,我們用以 uvloop 爲基礎的 asyncio 實現一個最簡單的 echo 協議(像第2、4、8個一樣),並對該協議的性能進行測試。用 1KB 的信息, uvloop是最快的實現,每秒達到了 105,000 次請求!對於 100KB 的信息來說,uvloop 的傳輸速度可以做到 2.3 GB/s。

  11. Go。使用由net.Conn.Read/Write調用組成的緊湊循環。Golang 的性能和 uvloop 十分相似,對於 10KB 和 100KB 的信息來說性能稍好一些。

HTTP

一開始,我們想比較搭建在 asyncio 和 uvloop 之上的 aiohttp 與 nodejs、Go 的性能差別。aiohttp 是用 asyncio 搭建異步 HTTP 服務器最流行的框架。

然而,aiohttp 的性能瓶頸竟然是它的 HTTP 解析器。這個解析器的速度非常慢,導致底層 I/O 庫再快也沒有用。爲了讓事情更有趣一點,我們爲 http-parser (nodejs 中的 HTTP 解析器,用 C 編寫,一開始爲 Nginx 開發) 創建了一個 Python 綁定。這個庫叫作 httptools ,可在 Github 和 PyPI上找到。

對於 HTTP ,所有的基準測試都使用的 wrk 來生成負載。併發級別設置爲 300。每次基準測試的時間爲30秒。

HTTP 網絡互連性能比較

出人意料的是,有了高性能 HTTP 解析器的幫助,純 Python 的 asyncio 的速度超過了nodejs,而後者用的也是同一種 HTTP 解析器!

Go在 1KB 的響應上的性能比較快,但是 uvloop+asyncio 的實現在 10/100KB 的表現上明顯比較快。對於用 httptools 的 asyncio 和 uvloop 而言,它們的性能非常棒。Go 語言也是一樣。

不可否認的是,基於 httptools 的服務器非常的簡單,而且比起其他實現方法來,也不包括其他的路由邏輯。然而,這次的基準測試證明了配合一個實現得很有效率的協議,uvloop 可以變得非常之快。

結論

我們可以安全地下結論說,有了 uvloop,我們可以寫出每秒每 CPU 核心可以推送上萬次請求的 Python 網絡互連代碼。在一個多核心繫統上,用上進程池,也許還可以進一步地提高性能。

在 Python 3.5 中,配合async/awit的力量, uvloop 和 asyncio 使得用 Python 寫出高性能的網絡互連代碼比以前任何時候都簡單。

 

 

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