python 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 也很簡單:

實例1:

 

1 import asyncio
2 import uvloop
3 
4 
5 asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

 

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

實例2:

 

1 import asyncio
2 import uvloop
3 
4 
5 # 聲明使用 uvloop 事件循環
6 asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
7 loop = uvloop.new_event_loop()
8 
9 asyncio.set_event_loop(loop)

 

實例3:

 1 import sys
 2 import signal
 3 import asyncio
 4 import logging
 5 import uvloop
 6 from typing import Coroutine, Any
 7 from commons.databases.cache import RedisCache
 8 from commons.databases.mongo import MongoCollectionManager
 9 from commons.databases.elasticsearch import ElasticSearchManager
10 from .databases.mysql import MysqlConnection
11 from .sdks.th_data import THData
12 from .kafka.producers import KafkaProducerManager
13 
14 logger = logging.getLogger(__name__)
15 
16 
17 def shutdown(sig: signal.Signals) -> None:
18     # stop other tasks
19     logging.info(f"Received exit signal {sig}...")
20     logging.info("Nacking outstanding messages")
21     for task in asyncio.all_tasks():
22         if task is not asyncio.current_task():
23             task.cancel()
24     logging.info('bye, exiting in a minute...')
25 
26 
27 def signal_handler_register(loop: asyncio.AbstractEventLoop) -> None:
28     if sys.platform != 'win32':
29         loop.add_signal_handler(signal.SIGTERM, lambda: shutdown(signal.SIGTERM))
30         loop.add_signal_handler(signal.SIGHUP, lambda: shutdown(signal.SIGHUP))
31         loop.add_signal_handler(signal.SIGINT, lambda: shutdown(signal.SIGINT))
32 
33 
34 async def startup() -> None:
35     loop = asyncio.get_event_loop()
36     signal_handler_register(loop=loop)
37     # prepare database
38     RedisCache.set_lock()
39     KafkaProducerManager.set_lock()
40 
41     # start all sdk
42     THData.create_session()
43 
44 
45 async def teardown() -> None:
46     # stop all database
47     logging.info("Closing database connections")
48     await RedisCache.close_all()
49     logger.info("Cache are all stoped")
50     MongoCollectionManager.close_all()
51     logger.info("Mongo are all stoped")
52     await ElasticSearchManager.close_all()
53     logger.info("Elasticsearch are all stoped")
54     await KafkaProducerManager.close_all()
55     logger.info("Kafka producer are all stoped")
56     await MysqlConnection.close_all()
57     logger.info("Mysql are all stoped")
58 
59     # stop all sdk
60     await THData.close_session()
61     logging.info(f"Flushing metrics")
62 
63 
64 async def kick(coro: Coroutine) -> Any:
65     try:
66         await startup()
67         return await coro
68     except asyncio.CancelledError:
69         logging.info(f'task {coro} was cannceled!')
70     finally:
71         await teardown()
72 
73 
74 def set_default_event_loop_policy():
75     if sys.platform == 'win32' and hasattr(asyncio, 'WindowsSelectorEventLoopPolicy'):
76         asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
77     else:
78         asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
79 
80 
81 def asyncio_graceful_run(coro: Coroutine, debug: bool = False) -> None:
82     set_default_event_loop_policy()
83     asyncio.run(kick(coro), debug=debug)

架構

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 寫出高性能的網絡互連代碼比以前任何時候都簡單。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM