mwall
mwall

Reputation: 11

asyncio running three times slower. What's wrong with my code?

# _*_ 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

Upvotes: 1

Views: 2247

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155363

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).

Upvotes: 2

Related Questions