11.24 Python 異步 IO(asyncio)、多進程、多線程性能對比

IO 密集型應用IO 密集型應用CPU等待IO時間遠大於CPU 自身運行時間,太浪費;常見的 IO 密集型業務包括:瀏覽器交互、磁盤請求、網絡爬蟲、數據庫請求等

Python 異步 IO(asyncio)、多進程、多線程性能對比

Python 世界對於 IO 密集型場景的併發提升有 3 種方法:多進程、多線程、異步 IO(asyncio);理論上講asyncio是性能最高的,原因如下:

1.進程、線程會有CPU上下文切換

2.進程、線程需要內核態和用戶態的交互,性能開銷大;而協程對內核透明的,只在用戶態運行

3.進程、線程並不可以無限創建,最佳實踐一般是 CPU*2;而協程併發能力強,併發上限理論上取決於操作系統IO多路複用(Linux下是 epoll)可註冊的文件描述符的極限

那asyncio的實際表現是否如理論上那麼強,到底強多少呢?我構建瞭如下測試場景:

訪問500臺 DB,並sleep 100ms模擬業務查詢 
方法 1;順序串行一臺臺執行
方法 2:多進程
方法 3:多線程
方法 4:asyncio
方法 5:asyncio+uvloop

最後的 asyncio+uvloop 和官方asyncio 最大不同是用 Cython+libuv 重新實現了asyncio 的事件循環(event loop)部分,官方測試性能是 node.js的 2 倍,持平 golang。

以下測試代碼需要 Pyhton3.7+: 順序串行一臺臺執行

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import records

user=xx
pass=xx
port=xx
hosts= [....] # 500臺 db列表

def query(host):
conn = records.Database(
f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
rows = conn.query('select sleep(0.1);')
print(rows[0])

def main():
for h in hosts:
query(h)

# main entrance
if __name__ == '__main__':
main()

多進程

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concurrent import futures
import records

user=xx
pass=xx
port=xx
hosts= [....] # 500臺 db列表

def query(host):
conn = records.Database(
f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
rows = conn.query('select sleep(0.1);')
print(rows[0])

def main():
with futures.ProcessPoolExecutor() as executor:
for future in executor.map(query,hosts):
pass

# main entrance
if __name__ == '__main__':
main()

多線程

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concurrent import futures
import records

user=xx
pass=xx
port=xx
hosts= [....] # 500臺 db列表

def query(host):
conn = records.Database(
f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4')
rows = conn.query('select sleep(0.1);')
print(rows[0])

def main():
with futures.ThreadPoolExecutor() as executor:
for future in executor.map(query,hosts):
pass

# main entrance
if __name__ == '__main__':
main()

asyncio

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
from databases import Database

user=xx
pass=xx
port=xx
hosts= [....] # 500臺 db列表

async def query(host):
DATABASE_URL = f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4'
async with Database(DATABASE_URL) as database:
query = 'select sleep(0.1);'
rows = await database.fetch_all(query=query)
print(rows[0])

async def main():
tasks = [asyncio.create_task(query(host)) for host in hosts]
await asyncio.gather(*tasks)


# main entrance
if __name__ == '__main__':
asyncio.run(main())

asyncio+uvloop

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import asyncio
import uvloop
from databases import Database

user=xx
pass=xx
port=xx
hosts= [....] # 500臺 db列表

async def query(host):
DATABASE_URL = f'mysql+pymysql://{user}:{pass}@{host}:{port}/mysql?charset=utf8mb4'
async with Database(DATABASE_URL) as database:
query = 'select sleep(0.1);'
rows = await database.fetch_all(query=query)
print(rows[0])

async def main():
tasks = [asyncio.create_task(query(host)) for host in hosts]
await asyncio.gather(*tasks)

# main entrance
if __name__ == '__main__':
uvloop.install()
asyncio.run(main())

運行時間對比

方式運行時間串行1m7.745s多進程2.932s多線程4.813sasyncio1.068sasyncio+uvloop0.750s

可以看出: 無論多進程、多進程還是asyncio都能大幅提升IO 密集型場景下的併發,但asyncio+uvloop性能最高,運行時間只有原始串行運行時間的 1/90,相差快 2 個數量級了!

內存佔用對比 串行

Python 異步 IO(asyncio)、多進程、多線程性能對比

多進程

Python 異步 IO(asyncio)、多進程、多線程性能對比

多線程

Python 異步 IO(asyncio)、多進程、多線程性能對比

asyncio

Python 異步 IO(asyncio)、多進程、多線程性能對比

asyncio+uvloop

Python 異步 IO(asyncio)、多進程、多線程性能對比

可以看出 asyncio+uvloop 內存佔用表現仍然最優,只有 60M;而多進程佔用多達 1.4G,果然創建進程是個十分重的操作~

總結asyncio 無論運行時間還是內存佔用都遠優於多進程、多線程,配合 uvloop 性能還能進一步提升,在 IO 密集型業務中可以優先使用 asyncio。


分享到:


相關文章: