Jon*_*han 2 python python-3.x async-await python-asyncio
当我意识到同步版本比异步版本快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.")
Run Code Online (Sandbox Code Playgroud)
这需要19秒。该代码循环遍历1M个键,data并使用相同的键和值构建一个字典。
$ python3.7 async_test.py
Took 19.08 seconds.
Run Code Online (Sandbox Code Playgroud)
同步版本
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.")
Run Code Online (Sandbox Code Playgroud)
这需要0.17秒!并执行与上述完全相同的操作。
$ python3.7 test.py
Took 0.17 seconds.
Run Code Online (Sandbox Code Playgroud)
异步版本 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.")
Run Code Online (Sandbox Code Playgroud)
此版本将其降低到11秒。
$ python3.7 async_test2.py
Took 11.91 seconds.
Run Code Online (Sandbox Code Playgroud)
为什么会这样?
在生产代码中,我将有一个阻塞调用,process_usage将密钥值保存到Redis数据库中。
比较这些基准时,应该注意异步版本是异步的:asyncio花费了大量精力来确保您提交的协程可以同时运行。你的具体情况他们并不真正同时运行,因为process_usage没有确实的await什么,但系统实际上并没有说。另一方面,同步版本则没有这样的规定:它只是按顺序运行所有内容,从而使解释器满意。
对于同步版本,一个更合理的比较是尝试使用同步代码的惯用方式并行化事物:通过使用线程。当然,您将无法为每个线程创建一个单独的线程,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
Run Code Online (Sandbox Code Playgroud)
在我的系统上,这大约需要17秒,而异步版本大约需要18秒。(更快的异步版本大约需要13秒。)
如果asyncio的速度增益如此之小,人们可能会问为什么要烦恼asyncio?区别在于,使用异步,假设使用惯用的代码和IO绑定的协程,您可以随意使用几乎无限数量的任务,这些任务实际上可以并发执行。您可以同时创建数万个异步连接,并且asyncio将使用高质量的轮询器和可伸缩的协程调度程序一次愉快地处理所有异步连接。对于线程池,并行执行的任务数始终受池中线程数的限制,通常最多为数百个。
甚至玩具的例子也具有价值,对于学习其他什么也没有价值。如果您使用这样的微基准进行决策,我建议您花更多的精力使示例更加真实。异步示例中的协程应至少包含一个await,同步示例应使用线程来模拟与异步获得的相同数量的并行性。如果您将两者进行调整以符合您的实际用例,那么基准测试实际上使您能够做出(更多)明智的决定。
| 归档时间: |
|
| 查看次数: |
508 次 |
| 最近记录: |