This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author v2m
Recipients v2m, yselivanov
Date 2020-09-11.01:10:36
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <>
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):
t1 = time.time()
for _ in range(N):
    except StopIteration:
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:37v2msetrecipients: + v2m, yselivanov
2020-09-11 01:10:36v2msetmessageid: <>
2020-09-11 01:10:36v2mlinkissue41756 messages
2020-09-11 01:10:36v2mcreate