簡體   English   中英

Python同步代碼示例比異步更快

[英]Python synchronous code example faster than async

當我意識到同步版本比異步版本快20倍時,我正在將生產系統遷移到異步。 我能夠創建一個非常簡單的例子來以可重復的方式證明這一點;

異步版本

import asyncio, time

data = {}

async def process_usage(key):
    data[key] = key

async def main():
    await asyncio.gather(*(process_usage(key) for key in range(0,1000000)))

s = time.perf_counter()
results = asyncio.run(main())
elapsed = time.perf_counter() - s
print(f"Took {elapsed:0.2f} seconds.")

這需要19秒。 代碼循環1M密鑰並生成一個字典, data使用相同的鍵和值。

$ python3.7 async_test.py
Took 19.08 seconds.

同步版本

import time

data = {}

def process_usage(key):
    data[key] = key

def main():
    for key in range(0,1000000):
        process_usage(key)

s = time.perf_counter()
results = main()
elapsed = time.perf_counter() - s
print(f"Took {elapsed:0.2f} seconds.")

這需要0.17秒! 並且與上面完全相同。

$ python3.7 test.py
Took 0.17 seconds.

帶有create_task異步版本

import asyncio, time

data = {}

async def process_usage(key):
    data[key] = key

async def main():
    for key in range(0,1000000):
        asyncio.create_task(process_usage(key))

s = time.perf_counter()
results = asyncio.run(main())
elapsed = time.perf_counter() - s
print(f"Took {elapsed:0.2f} seconds.")

這個版本將它降低到11秒。

$ python3.7 async_test2.py
Took 11.91 seconds.

為什么會這樣?

在我的生產代碼中,我將在process_usage中進行阻塞調用,我將key的值保存到redis數據庫中。

在比較這些基准測試時,應該注意異步版本是異步的:asyncio花費了大量精力來確保您提交的協同程序可以同時運行。 在您的特定情況下,它們並不實際並發運行,因為process_usage不會等待任何事情,但系統實際上並不是這樣。 另一方面,同步版本沒有這樣的規定:它只是按順序運行所有內容,觸及解釋器的快樂路徑。

更合理的比較是同步版本嘗試以同步代碼的慣用方式並行化:通過使用線程。 當然,您將無法為每個process_usage創建單獨的線程,因為與其任務的asyncio不同,操作系統將不允許您創建一百萬個線程。 但是您可以創建一個線程池並為其提供任務:

def main():
    with concurrent.futures.ThreadPoolExecutor() as executor:
        for key in range(0,1000000):
            executor.submit(process_usage, key)
        # at the end of "with" the executor automatically
        # waits for all futures to finish

在我的系統上,這需要大約17秒,而asyncio版本需要大約18秒。 (更快的asyncio版本需要大約13秒。)

如果asyncio的速度增益很小,可以問為什么要打擾asyncio? 不同之處在於,使用asyncio,假設慣用代碼和IO綁定協程,您可以隨意使用幾乎無限數量的任務,這些任務在非常真實的意義上同時執行。 您可以同時創建數以萬計的異步連接,並且asyncio將使用高質量的輪詢器和可擴展的協程調度程序一次性地將它們全部兼顧。 對於線程池,並行執行的任務數總是受池中線程數的限制,通常最多為數百個。

即使玩具的例子也有價值,如果沒有其他的東西可以學 如果您使用此類微基准測試做出決策,我建議您投入更多精力來使示例更具真實性。 asyncio示例中的協程應至少包含一個await ,同步示例應使用線程來模擬使用async獲得的相同數量的並行性。 如果您調整兩者以匹配您的實際用例,那么基准測試實際上使您能夠做出更明智的決定。

為什么會這樣?

TL; DR

因為使用asyncio本身並不能加速代碼。 您需要多個收集的網絡I / O相關操作才能看到同步版本之間的差異。

詳細

asyncio不是一個允許你加速任意代碼的魔法。 無論是否使用asyncio您的代碼仍然由具有限制性能的CPU運行。

asyncio是一種以清晰,明確的方式管理多個執行流程(協同程序)的方法。 多個執行流程允許您在等待其他操作完成之前啟動下一個與I / O相關的操作(例如對數據庫的請求)。 請閱讀此答案以獲得更詳細的解釋。

當有意義使用asyncio時,請閱讀此答案以獲得解釋。

一旦開始使用asyncio正確的方式開銷使用它應該遠遠低於並行化I / O操作的好處。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM