Home > Mobile >  Python "conditional" async method
Python "conditional" async method

Time:12-22

I'm curious what the behaviour/performance overhead is when I have a function like:

async def slow_function(some_resource):
    if some_resource in cache.keys():
        return cache[some_resource]
    return await requests.get(some_resource)

In the case where we somehow cache "some_resource", there is no "await" executed. What is the overhead of "async" in this case? Is it literally zero overhead since no "await" statement is reached? Or is there some unavoidable overhead no matter what, when an "async" function is called?

CodePudding user response:

Practically 0 overhead.

The async will just continue on like a normal function. The overhead is when you reach an await that yields a future down in the chain.

In essence, every time you await an uncompleted Future, the event loop has to bind to it and continue with a new cycle that entails a bunch of operations. If your await immediately returns without waiting for a future, the event loop is not cycled, and the function continues on normally much like any other generator function.

The rest of the overhead is the difference between a normal function call, and the initialization of a generator (as coroutines are implemented quite the same). While there is some overhead and might even be twice or thrice the normal function call (related to the creation of the generator itself mostly), it's negligible compared to around 50 or 60 full function calls with lots of instructions that happen in each event loop cycle.

CodePudding user response:

Sorry it's not an answer to the actual question but I think that's already covered

There is a not insignificant performance issue with your code but it's not related to async.

Checking to see if a key is in the cache and then looking up the key in the cache is slower than accessing the key and catching the KeyError by a fair margin

(This is just looking at the "happy" path where the cache has the key)

>>> import timeit
>>> timeit.timeit('try: cache[x]\nexcept KeyError: pass', setup='cache = {i: i for i in range(500)}; x = 5')
0.0273395610274747
>>> timeit.timeit('if x in cache.keys(): cache[x]', setup='cache = {i: i for i in range(500)}; x = 5')
0.08461441402323544

Something like this would probably perform slightly better

async def slow_function(some_resource):
    try:
        return cache[some_resource]
    except KeyError:
        return await requests.get(some_resource)
  • Related