Vladimir Matveev <[email protected]> added the comment:
- introducing dedicated opcodes for each kind of awaited call is definitely an
option. In fact first implementation used it however as Dino has mentioned it
was more of a logistical issue (there were several spots that produced .pyc
files so compiler needed to be up to date across all of them).
- there was some perf win that was coming from rewriting `gather` in C however
main reason for us to do it was the ability to be await-aware which we made
only available in C (also returning completed waithandle is not exposed to
Python either) to reduce the scope. Providing ability to follow await-aware
protocol (read indicator that call is awaited + return completed waithandle for
eagerly completed calls) in pure Python is definitely possible
- to provide some context why it was beneficial: typical implementation of
endpoint in IG is an async function that in turn calls into numerous other
async functions to generate an output.
- `gather` is used all over the place in case if there are no sequential
dependency between calls
- amount of unique pieces of data that are ultimately fetched by async calls
is not very big, i.e. the same fragment of information can be requested by
different async calls which makes memoization a very attractive strategy to
reduce I/O and heavyweight computations.
- memoized pieces of data is represented effectively by completed futures and
it is very common to have `gather` accept either memoized value or coroutine
object that will be completed synchronously by awaiting memoized value.
Before making gather await-aware if always have to follow the standard process
and convert awaitables into tasks that are queued into the event loop for
execution. In our workload task creation/queueing were adding a noticeable
overhead. With await-aware gather we can execute coroutine objects eagerly and
if they were not suspended - bypass task creation entirely.
```
import asyncio
import time
async def step(i):
if i == 0:
return
await asyncio.gather(*[step(i - 1) for _ in range(6)])
async def main():
t0 = time.perf_counter()
await step(6)
t1 = time.perf_counter()
print(f"{t1 - t0} s")
N = 0
def create_task(loop, coro):
global N
N += 1
return asyncio.Task(coro, loop=loop)
loop = asyncio.get_event_loop()
loop.set_task_factory(create_task)
loop.run_until_complete(main())
print(f"{N} tasks created")
# Cinder
# 0.028410961851477623 s
# 1 tasks created
# Python 3.8
# 1.2157012447714806 s
# 55987 tasks created
```
----------
_______________________________________
Python tracker <[email protected]>
<https://bugs.python.org/issue46965>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com