Message376698
Currently async functions are more expensive to use comparing to their sync counterparts. A simple microbenchmark shows that difference could be quite significant:
```
import time
def f(a):
if a == 0:
return 0
return f(a - 1)
async def g(a):
if a == 0:
return 0
return await g(a - 1)
N = 100000
C = 200
t0 = time.time()
for _ in range(N):
f(C)
t1 = time.time()
for _ in range(N):
try:
g(C).send(None)
except StopIteration:
pass
t2 = time.time()
print(f"Sync functions: {t1 - t0} s")
print(f"Coroutines: {t2 - t1} s")
```
Results from master on my machine:
Sync functions: 2.8642687797546387 s
Coroutines: 9.172159910202026 s
NOTE: Due to viral nature of async functions their number in codebase could become quite significant so having hundreds of them in a single call stack is not something uncommon.
One of reasons of such performance gap is that async functions always return its results via raising StopIteration exception which is not cheap. This can be avoided if in addition to `_PyGen_Send` always return result via exception we could have another function that will allow us to distinguish whether value that was returned from generator is a final result (return case) of whether this is yielded value.
In linked PR I've added function `_PyGen_SendNoStopIteration` with this behavior and updated ceval.c and _asynciomodule.c to use it instead of `_PyGen_Send` which resulted in a measurable difference:
Sync functions: 2.8861589431762695 s
Coroutines: 5.730362176895142 s |
|
Date |
User |
Action |
Args |
2020-09-11 01:10:37 | v2m | set | recipients:
+ v2m, yselivanov |
2020-09-11 01:10:36 | v2m | set | messageid: <1599786636.95.0.884410425349.issue41756@roundup.psfhosted.org> |
2020-09-11 01:10:36 | v2m | link | issue41756 messages |
2020-09-11 01:10:36 | v2m | create | |
|