异步任务的开销是多少?
问题描述
任何异步任务在内存和速度方面的开销是多少?在不需要并发运行的情况下将任务数降至最低是否值得?解决方案
任何异步任务在内存和速度方面的开销是多少?
TL;DR内存开销看起来可以忽略不计,但时间开销可能很大,特别是在等待的协程选择不挂起的情况下。
让我们假设您正在测量与直接等待的协程相比的任务开销,例如:
await some_coro() # (1)
await asyncio.create_task(some_coro()) # (2)
没有理由直接编写(2),但是当使用自动"futurize"接收到的等待项的接口(如asyncio.gather
或asyncio.wait_for
)时,很容易产生不必要的任务。(我怀疑此问题的背景是构建或使用此类抽象。)
测量两个变体之间的内存和时间差非常简单。例如,下面的程序创建了一百万个任务,进程的内存消耗可以除以一百万来估计任务的内存成本:
async def noop():
pass
async def mem1():
tasks = [asyncio.create_task(noop()) for _ in range(1000000)]
time.sleep(60) # not asyncio.sleep() in this case - we don't
# want our noop tasks to exit immediately
在我的运行Python3.7的64位Linux机器上,该进程大约消耗1GiB的内存。这大约是每个任务1 KiB+协程,它计算任务的内存和事件循环簿记中条目的内存。以下程序仅测量协程开销的近似值:
async def mem2():
coros = [noop() for _ in range(1000000)]
time.sleep(60)
上述过程大约需要550 MiB的内存,或者每个协程仅占用0.55 KiB。因此,虽然任务并不是完全免费的,但它并不会给协程带来巨大的内存开销,特别是要记住上面的协程是空的。如果协程有某种状态,开销就会小得多(相对而言)。
但是CPU开销如何?与仅仅等待协程相比,创建和等待任务需要多长时间?让我们尝试一个简单的测量:
async def cpu1():
t0 = time.time()
for _ in range(1000000):
await asyncio.create_task(noop())
t1 = time.time()
print(t1-t0)
在我的计算机上,这需要27秒(平均而言,变化非常小)才能运行。没有任务的版本如下所示:
async def cpu2():
t0 = time.time()
for _ in range(1000000):
await noop()
t1 = time.time()
print(t1-t0)
这个只需要0.16秒,约为170倍!因此,与等待协程对象相比,等待任务的时间开销是不可忽略的。这有两个原因:
任务的创建成本比协程对象高,因为它们需要先初始化基
Future
,然后初始化Task
本身的属性,最后将任务插入到事件循环中,并进行自己的记账。新创建的任务处于挂起状态,其构造函数让scheduled该任务一有机会就开始执行协程。由于任务拥有协程对象,因此等待新任务不能仅仅开始执行协程;它必须挂起并等待任务开始执行它。等待的协同例程只有在完整的事件循环迭代之后才会恢复,即使在等待选择根本不挂起的协同例程时也是如此!事件循环迭代开销很大,因为它通过所有可运行任务和轮询内核的IO和超时活动。实际上,
cpu1
的strace
显示了对epoll_wait(2)
的200万次调用。cpu2
另一方面,只有零星的与分配相关的mmap()
进入内核,总共几千个。相反,直接等待协同例程doesn't yield到事件循环,除非等待的协同例程本身决定暂停。相反,它会立即继续并开始执行协同例程,就好像它是一个普通函数一样。
/li>
相关文章