Vladimir Matveev <desco...@gmail.com> 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 <rep...@bugs.python.org>
<https://bugs.python.org/issue46965>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to