Pin Toggle Frequency Contest against C. Please Help! :)

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
mad474
Posts: 60
Joined: Sun Dec 29, 2013 7:48 pm

Pin Toggle Frequency Contest against C. Please Help! :)

Post by mad474 » Mon Feb 01, 2016 10:02 pm

In THE german microcontroller forum a poor micropython guy is under heavy pressure lying behind by factor 500
https://www.mikrocontroller.net/topic/388161#4450525

Any suggestions to get better results in

Code: Select all

Micro Python v1.3.9-44-gb5a790d on 2015-02-08; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> def togglePerformance():
...     stop = pyb.millis() + 1000
...     count = 0
...     while pyb.millis() < stop:
...         pyb.LED(1).toggle()
...         count += 1
...     print("Counted: ", count)
... 
>>> togglePerformance()
Counted:  39715
>>> togglePerformance()
Counted:  39720
>>>
are welcome. Or post an alternative toggle counter there directly. No login required.
Thanks!

Damien
Site Admin
Posts: 647
Joined: Mon Dec 09, 2013 5:02 pm

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by Damien » Mon Feb 01, 2016 11:23 pm

You could try viper mode, or inline assembler (depending on the rules of the competition....).

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by dhylands » Mon Feb 01, 2016 11:58 pm

Here is a couple of improved versions:

Code: Select all

import pyb

def togglePerformance():
    stop = pyb.millis() + 1000
    count = 0
    millis = pyb.millis
    toggle = pyb.LED(1).toggle
    while millis() < stop:
        toggle()
        count += 1
    print("Counted: ", count)

@micropython.native
def togglePerformance2():
    stop = pyb.millis() + 1000
    count = 0
    millis = pyb.millis
    toggle = pyb.LED(1).toggle
    while millis() < stop:
        toggle()
        count += 1
    print("Counted: ", count)

togglePerformance()
togglePerformance2()
which yields the following:

Code: Select all

>>> import togglePerf
Counted:  130893
Counted:  214670
To be comparable to C, then inline assembler, could be used.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by dhylands » Tue Feb 02, 2016 12:53 am

Here's my solution including inline assembler:

Code: Select all

import pyb

def togglePerformance():
    stop = pyb.millis() + 1000
    count = 0
    millis = pyb.millis
    toggle = pyb.LED(1).toggle
    while millis() < stop:
        toggle()
        count += 1
    print('Counted: {:10,} (bytecode)'.format(count))


@micropython.native
def togglePerformance2():
    stop = pyb.millis() + 1000
    count = 0
    millis = pyb.millis
    toggle = pyb.LED(1).toggle
    while millis() < stop:
        toggle()
        count += 1
    print('Counted: {:10,} (native)'.format(count))

@micropython.viper
def togglePerformance3Core(stop):
    count = 0
    millis = pyb.millis
    toggle = pyb.LED(1).toggle
    while millis() < stop:
        toggle()
        count += 1
    print('Counted: {:10,} (viper)'.format(count))

def togglePerformance3():
    stop = pyb.millis() + 1000
    count = togglePerformance3Core(stop)

@micropython.asm_thumb
def _counter():
    mov(r0, 0)
    movwt(r1, stm.GPIOA)    # LED(1) is A13
    add(r1, stm.GPIO_BSRRL)
    movw(r5, 1 << 13)       # r5 has mask for BSRR register
    movwt(r2, stm.TIM2)
    ldr(r3, [r2, stm.TIM_CNT])
    movw(r4, 2000)
    add(r4, r4, r3)         # r4 is our ending count
# loop
    label(loop)
    strh(r5, [r1, 0])       # Turn the LED on
    add(r0,1)               # increment counter
    strh(r5, [r1, 2])       # Turn the LED off
    add(r0,1)               # increment counter
    ldr(r3, [r2, stm.TIM_CNT])
    cmp(r3, r4)
    bls(loop)

def togglePerformance4():
    # Setup a timer to increment twice per millisecond
    # Timer 2 runs at 84 MHz, so we divide by 42000 and when the count
    # gets to 2000 then one second will have passed.
    t2 = pyb.Timer(2, prescaler=41999, period=0x0fffffff)
    count = _counter()
    print('Counted: {:10,} (asm_thumb)'.format(count))

togglePerformance()
togglePerformance2()
togglePerformance3()
togglePerformance4()
which produces this as output:

Code: Select all

MicroPython v1.6 on 2016-02-01; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> 
>>> import togglePerf
Counted:    130,922 (bytecode)
Counted:    214,641 (native)
Counted:    239,252 (viper)
Counted: 11,975,836 (asm_thumb)
>>> 

mad474
Posts: 60
Joined: Sun Dec 29, 2013 7:48 pm

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by mad474 » Tue Feb 02, 2016 9:58 am

@Damien
No rules, everything allowed, as usual in this context :)

@dhylands
Very much appreciated, thank you Dave! I'll repulse this evening.

Damien
Site Admin
Posts: 647
Joined: Mon Dec 09, 2013 5:02 pm

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by Damien » Tue Feb 02, 2016 12:39 pm

Nice work Dave!

Here is an improved viper version that uses native viper pointers to do the toggling:

Code: Select all

@micropython.viper
def togglePerformance3b():
    count = 0 
    stop = int(pyb.millis()) + 1000
    millis = pyb.millis
    odr = ptr16(stm.GPIOA + stm.GPIO_ODR)
    while int(millis()) < stop:
        odr[0] ^= 1 << 13 # PA13 = LED_RED
        count += 1
    print('Counted: {:10,} (viper2)'.format(count))
On PYBv1.0 I get:

Code: Select all

Counted:    128,519 (bytecode)
Counted:    218,550 (native)
Counted:    244,130 (viper)
Counted:    537,699 (viper2)
Counted: 11,975,820 (asm_thumb)

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by dhylands » Tue Feb 02, 2016 1:34 pm

So it turns out the previous assembler version wasn't incrementing the counter the same way that the C version was.

So I coded up an inline assembler version which dies things more or less the same way as the C version, but my counter is still only reaching 10 million, so I'm not sure what's wrong.

Code: Select all

import pyb
import stm
import uctypes

run_buf = bytearray(1)
run_buf[0] = 1

def timer_irq(tim):
    global run_buf
    run_buf[0] = 0

@micropython.asm_thumb
def counter(r0):    # r0 has address of run_buf
    movwt(r1, stm.GPIOA)    # LED(1) is A13
    add(r1, stm.GPIO_BSRRL)
    movw(r2, 1 << 13)       # r2 has mask for setting LED
    mov(r3, r2)
    mov(r4, 16)
    lsl(r3, r4)             # r3 has mask for clearing LED

    mov(r4, 0)              # r4 is counter
# loop
    label(loop)
    ldrb(r3, [r0, 0])
    cmp(r3, 1)
    bne(endloop)
    str(r2, [r1, 0])
    add(r4, 1)
    str(r3, [r1, 0])
    b(loop)
# endloop
    label(endloop)
    mov(r0, r4) # return counter in r0

def main():
    # Setup Timer2 to increment at 10 kHz
    # Disable SysTick
    stm.mem32[0xe000e010] = 0
    t2 = pyb.Timer(2, prescaler=84000000 // 10000 - 1, period=9999, callback=timer_irq)
    print('PSC =', stm.mem32[stm.TIM2 + stm.TIM_PSC])
    count = counter(run_buf)
    print('Counted: {:10,} (asm_thumb)'.format(count))

main()
I threw in the line to disable systick, which increased things a bit, but not much.

This is still a factor of 2 worse than the C example (I'll need to compile the C version and verify independently)

blmorris
Posts: 348
Joined: Fri May 02, 2014 3:43 pm
Location: Massachusetts, USA

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by blmorris » Tue Feb 02, 2016 3:07 pm

I don't read German, but I still clicked through to see if I could understand any of it.

At least one comment was perfectly clear in the original German :D
Autor: Hans (Gast)
Datum: 2016-02-01 15:18
Hehe, Pintoggeln, der ultimative Benchmark für Mikrocontroller :-)
It is still a nice opportunity to show off MicroPython's assembler capabilities.

-Bryan

ETA: After seeing Dave's post below, it's clear that this little contest could actually drive a legitimate improvement to the assembler capabilities, and of course it is important to be able to control pins close to the hardware limit. I was just amused to see this little dose of humorous perspective in the thread.
Last edited by blmorris on Tue Feb 02, 2016 3:34 pm, edited 1 time in total.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by dhylands » Tue Feb 02, 2016 3:17 pm

One thing I did notice was that when I compiled the C code, it generates a cbz instruction, which is a compare & branch (and there is also a cbnz instruction), and these seem to not be present in MicroPython. These seem like useful instructions to have. I'm going to see if I can encode this using a data word

mad474
Posts: 60
Joined: Sun Dec 29, 2013 7:48 pm

Re: Pin Toggle Frequency Contest against C. Please Help! :)

Post by mad474 » Tue Feb 02, 2016 5:18 pm

You guys rock! I'm going to push Dave's first inline assembler version and Damien's viper version and, well, the other versions as well. Thanks, learning a lot (yet understanding only a fraction). Sidenote: In order not to hard fault the board (4 LEDs cycle on and off, reset pin recoverable) Dave's assembler requires new firmware.

Post Reply