IO 密集型應(yīng)用
IO 密集型應(yīng)用CPU等待IO時(shí)間遠(yuǎn)大于CPU 自身運(yùn)行時(shí)間,太浪費(fèi)签孔;常見(jiàn)的 IO 密集型業(yè)務(wù)包括:瀏覽器交互叉讥、磁盤(pán)請(qǐng)求、網(wǎng)絡(luò)爬蟲(chóng)饥追、數(shù)據(jù)庫(kù)請(qǐng)求等
Python 世界對(duì)于 IO 密集型場(chǎng)景的并發(fā)提升有 3 種方法:多進(jìn)程图仓、多線程、異步 IO(asyncio);理論上講asyncio是性能最高的但绕,原因如下:
1.進(jìn)程救崔、線程會(huì)有CPU上下文切換
2.進(jìn)程惶看、線程需要內(nèi)核態(tài)和用戶態(tài)的交互,性能開(kāi)銷(xiāo)大六孵;而協(xié)程對(duì)內(nèi)核透明的,只在用戶態(tài)運(yùn)行
3.進(jìn)程纬黎、線程并不可以無(wú)限創(chuàng)建,最佳實(shí)踐一般是 CPU*2劫窒;而協(xié)程并發(fā)能力強(qiáng)本今,并發(fā)上限理論上取決于操作系統(tǒng)IO多路復(fù)用(Linux下是 epoll)可注冊(cè)的文件描述符的極限
那asyncio的實(shí)際表現(xiàn)是否如理論上那么強(qiáng),到底強(qiáng)多少呢烛亦?我構(gòu)建了如下測(cè)試場(chǎng)景:
訪問(wèn)500臺(tái) DB诈泼,并sleep 100ms模擬業(yè)務(wù)查詢
方法 1懂拾;順序串行一臺(tái)臺(tái)執(zhí)行
方法 2:多進(jìn)程
方法 3:多線程
方法 4:asyncio
方法 5:asyncio+uvloop
最后的asyncio+uvloop
和官方asyncio 最大不同是用 Cython+libuv 重新實(shí)現(xiàn)了asyncio 的事件循環(huán)(event loop)部分,官方測(cè)試性能是 node.js的 2 倍煤禽,持平 golang。
以下測(cè)試代碼需要 Pyhton3.7+:
順序串行一臺(tái)臺(tái)執(zhí)行
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import records
user=xx
pass=xx
port=xx
hosts= [....] # 500臺(tái) 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()
多進(jìn)程
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from concurrent import futures
import records
user=xx
pass=xx
port=xx
hosts= [....] # 500臺(tái) 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臺(tái) 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臺(tái) 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臺(tái) 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())
運(yùn)行時(shí)間對(duì)比
方式 | 運(yùn)行時(shí)間 |
---|---|
串行 | 1m7.745s |
多進(jìn)程 | 2.932s |
多線程 | 4.813s |
asyncio | 1.068s |
asyncio+uvloop | 0.750s |
可以看出: 無(wú)論多進(jìn)程岖赋、多進(jìn)程還是asyncio都能大幅提升IO 密集型場(chǎng)景下的并發(fā)檬果,但asyncio+uvloop性能最高,運(yùn)行時(shí)間只有原始串行運(yùn)行時(shí)間的 1/90唐断,相差快 2 個(gè)數(shù)量級(jí)了选脊!
內(nèi)存占用對(duì)比
串行
多進(jìn)程
多線程
asyncio
asyncio+uvloop
可以看出asyncio+uvloop
內(nèi)存占用表現(xiàn)仍然最優(yōu),只有 60M;而多進(jìn)程占用多達(dá) 1.4G,果然創(chuàng)建進(jìn)程是個(gè)十分重的操作~
總結(jié)
asyncio 無(wú)論運(yùn)行時(shí)間還是內(nèi)存占用都遠(yuǎn)優(yōu)于多進(jìn)程、多線程脸甘,配合 uvloop 性能還能進(jìn)一步提升恳啥,在 IO 密集型業(yè)務(wù)中可以優(yōu)先使用 asyncio。