Slow returning scalar types in async function

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
yjchun
Posts: 10
Joined: Fri Aug 27, 2021 8:04 pm

Slow returning scalar types in async function

Post by yjchun » Fri Feb 25, 2022 2:59 am

I found a weird performance issue. In async functions, it is a lot slower when returning scalar types than object types.

Code: Select all

import time
import uasyncio as asyncio

async def ret(arg):
    return arg

async def test3(count=10000, d=False):
    for n in range(count):
        if d:
            v = dict() # list() or object()
        else:
            v = 0 # True 0.0 int() str()
        await ret(v)

t = time.ticks_ms()
asyncio.run(test3(d=True))
print('dict(): ' + str(time.ticks_diff(time.ticks_ms(), t)))

t = time.ticks_ms()
asyncio.run(test3(d=False))
print('int: ' + str(time.ticks_diff(time.ticks_ms(), t)))
in RP2:
dict(): 427
int: 3556


in unix:
dict(): 4
int: 157


in CPython:
dict(): 4
int: 3


Tested with yesterday's master.
I am a heavy user of asyncio and when I wanted to optimize my code by using simple types rather than creating objects, the result was the opposite. Anyone have any clue?
Thanks.

yjchun
Posts: 10
Joined: Fri Aug 27, 2021 8:04 pm

Re: Slow returning scalar types in async function

Post by yjchun » Fri Feb 25, 2022 8:57 am

Similar result with yield:

Code: Select all

import time
def ret(arg):
    yield arg

def test3(count=10000, d=False):
    for n in range(count):
        if d:
            v = dict(a=0) # list() object()
        else:
            v = int() # 0
        r = next(ret(v))

t = time.ticks_ms()
test3(d=True)
print('dict(): ' + str(time.ticks_diff(time.ticks_ms(), t)))

t = time.ticks_ms()
test3(d=False)
print('int: ' + str(time.ticks_diff(time.ticks_ms(), t)))
with unix micropython:
dict(): 7
int: 90


with CPython:
dict(): 5
int: 5

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Slow returning scalar types in async function

Post by pythoncoder » Fri Feb 25, 2022 11:56 am

This is puzzling. I repeated the test with synchronous code:

Code: Select all

import time

def ret(arg):
    return arg

def test3(count=10000, d=False):
    for n in range(count):
        if d:
            v = {} # faster than dict()
        else:
            v = 0
        ret(v)

t = time.ticks_ms()
test3(d=True)
print('dict(): ' + str(time.ticks_diff(time.ticks_ms(), t)))

t = time.ticks_ms()
test3(d=False)
print('int: ' + str(time.ticks_diff(time.ticks_ms(), t)))
with the outcome (on a Pico)

Code: Select all

dict(): 288
int: 217
For some reason assigning an int seems much slower in a coroutine than in synchronous code. This is particularly odd as in your benchmark I don't think the scheduler ever gets a look-in. However if I add a asyncio.sleep_ms(0) in ret() there is still a >2:1 discrepancy in favour of the dict:

Code: Select all

dict(): 4850
int: 11016
Peter Hinch
Index to my micropython libraries.

yjchun
Posts: 10
Joined: Fri Aug 27, 2021 8:04 pm

Re: Slow returning scalar types in async function

Post by yjchun » Fri Feb 25, 2022 6:34 pm

Code: Select all

import time
def ret(arg):
    yield None

def test3(klass=int, count=10000):
    for n in range(count):
        v = klass()
        ret(v)

def test4(v=0, count=10000):
    for n in range(count):
        ret(v)

t = time.ticks_ms()
test3(dict)
print('dict(): ' + str(time.ticks_diff(time.ticks_ms(), t)))

t = time.ticks_ms()
test3(int)
print('int: ' + str(time.ticks_diff(time.ticks_ms(), t)))

t = time.ticks_ms()
test4()
print('test4: ' + str(time.ticks_diff(time.ticks_ms(), t)))
dict(): 391
int: 4874
test4: 5013

Further narrowed down the problem. And I don't understand the results.
Not only the problem is passing value to a generator function but also it matters type of value and location of value definition.
test4() is supposed to be fastest because new object is not created but it is the slowest. And that 10 times slower...

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Slow returning scalar types in async function

Post by pythoncoder » Sat Feb 26, 2022 1:39 pm

The code

Code: Select all

ret(v)
is running a generator function, instantiating a generator, and then discarding the generator. This is an unusual thing to do in a loop. While your observation is very odd, I'm not sure whether the performance of a generator function (rather than a generator) would be seen as a critical bug.
Peter Hinch
Index to my micropython libraries.

yjchun
Posts: 10
Joined: Fri Aug 27, 2021 8:04 pm

Re: Slow returning scalar types in async function

Post by yjchun » Sat Feb 26, 2022 2:57 pm

pythoncoder wrote:
Sat Feb 26, 2022 1:39 pm
The code

Code: Select all

ret(v)
is running a generator function, instantiating a generator, and then discarding the generator. This is an unusual thing to do in a loop. While your observation is very odd, I'm not sure whether the performance of a generator function (rather than a generator) would be seen as a critical bug.
Hello @pythoncoder. Yes I know that. I wanted to show the performance problem of asyncio and that instantiating generator is the source of performance issue. If I understand correctly, everytime async function is called, a generator function is created(instantiated).

stijn
Posts: 735
Joined: Thu Apr 24, 2014 9:13 am

Re: Slow returning scalar types in async function

Post by stijn » Sun Feb 27, 2022 8:27 am

Something is very wrong here. Windows port for test3 (and 10 iterations of that just to make sure):

Code: Select all

dict 15.809
int 3406.55
float 22.054
Seems worth creating a Github issue for this: while it's not suprirising creating a generator has an overhead, the differences observed here are so large it's either an underlying problem, or at least wordt being documented somewhere.


Post Reply