1
# _*_ coding: utf-8 _*_

import asyncio
from pprint import pprint
import time

async def add(a, b, c): return a+b+c
async def concat(a, b): return a+b
async def even(l): return [x for x in l if not x%2]
async def split(s): return s.split()
async def int_list(s): return [int(c) for c in s]

d = {   add:(3,4,5), 
        concat:('Lill', 'Klas'),
        even:[[1,2,3,4,5,6,7,8,9]],
        split:['ett två tre fyra'],
        int_list:['12345']
    }

async def run(dic):
    return {k.__name__:await asyncio.gather(k(*v,)) for k, v in dic.items()}

start=time.perf_counter()
res = asyncio.run(run(d))
end=time.perf_counter()

pprint(res)
print(end-start)

The code is running three times slower than without the async. And I can't figure out what I am doing wrong. I am running python 3.10.5.

no async: 0.00033 async 0.00098

4
  • If you want to check the speed, do more than almost nothing. Get into the seconds at least. Commented Jul 21, 2022 at 20:46
  • @HEllRZA: Adding a for i in range(1000): around the one timed line actually makes the differences more obvious; it's roughly a factor of 100 difference. That said, structuring it that way means you are setting up and tearing down the event loop every time, which is a huge overhead expense you'd never pay in real asyncio code. Commented Jul 21, 2022 at 20:51
  • Side-note: The pointless gather call (you only have one thing to await, so gather does nothing) is responsible for nearly a third of the time of the async code. You seem to be going out of your way to make the code slower. Commented Jul 21, 2022 at 21:01
  • You got a good answer now below. What I actually meant was: do more work in your asynchronous thread, not almost nothing. Commented Jul 21, 2022 at 21:23

1 Answer 1

2

async functions involve setting up a separate call stack for each of the tasks (similar to generator functions). The functions you've written are extremely lightweight, so the overhead of making them async is extreme.

It's unclear why you're even using asyncio here; asyncio.gather won't "parallelize" anything that doesn't ultimately devolve to low level I/O of some kind (it's not multithreading, so all it can do is schedule I/O and do other stuff while waiting for it; the best it can do for parallelism is wrap parallel task dispatch with run_in_executor, which has its own overhead). Add-on the work to launch the event loop, create and queue the tasks, extract their results, and in a program with so little actual work to be done, the asyncio overhead exceeds that of the real work. Practically speaking, what you've written is 100% synchronous, you just added a bunch of rigmarole to handle it as if it were asynchronous, without actually using any of those features.

In short: Don't use asyncio when your code is not in any way asynchronous. Don't time execution of trivial amounts of code wrapped in expensive overhead that real programs never pay (e.g. repeatedly creating, running, tearing down, and destroying an event loop, which is what asyncio.run does for you, or calling asyncio.gather once per call, when gather exists solely to simultaneously await multiple tasks).

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.