Slowdown with repeated calls to GPIO.setup()

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
mikeduffy
 
Posts: 16
Joined: Mon May 19, 2014 4:51 pm

Slowdown with repeated calls to GPIO.setup()

Post by mikeduffy »

Hi, Justin (jwcooper).

It appears that just making calls to GPIO.setup() is gradually using up memory (and slowing down the rate at which these operations can be performed). This is unexpected. :)

Here's an example program, bug.py (press Ctrl-C to exit):

Code: Select all

#!/usr/bin/python

import sys
import time

import Adafruit_BBIO.GPIO as GPIO

# GPIO assignments for each button
BTN_GPIO_A   = "P8_17"
BTN_GPIO_B   = "P8_18"
BTN_GPIO_C   = "P8_26"
BTN_GPIO_D   = "P9_15"
BTN_GPIO_X_1 = "P8_7"    # shared with LED
BTN_GPIO_X_2 = "P8_8"    # shared with LED
BTN_GPIO_Y_1 = "P8_9"    # shared with LED
BTN_GPIO_Y_2 = "P8_10"   # shared with LED
BTN_GPIO_Z_1 = "P8_11"   # shared with LED
BTN_GPIO_Z_2 = "P8_12"   # shared with LED
BTN_GPIO_E   = "P8_13"
BTN_GPIO_COM = "P9_41"

LED_GPIO_T   = BTN_GPIO_X_1
LED_GPIO_D   = BTN_GPIO_X_2
LED_GPIO_C   = BTN_GPIO_Y_1
LED_GPIO_H   = BTN_GPIO_Y_2
LED_GPIO_P   = BTN_GPIO_Z_1
LED_GPIO_E   = BTN_GPIO_Z_2
LED_GPIO_COM = "P9_42"

# these button GPIOs are ALWAYS inputs
GPIO.setup(BTN_GPIO_A, GPIO.IN)
GPIO.setup(BTN_GPIO_B, GPIO.IN)
GPIO.setup(BTN_GPIO_C, GPIO.IN)
GPIO.setup(BTN_GPIO_D, GPIO.IN)
GPIO.setup(BTN_GPIO_E, GPIO.IN)

# BTN_GPIO_COM is ALWAYS an output
GPIO.setup(BTN_GPIO_COM, GPIO.OUT)

try:
    tickTime = 0
    seconds = count = 0

    runCode = int(sys.argv[1])
    print "runCode:", runCode

    while True:
        count += 1
        now = time.time()

        # first time, establish next tick time
        if tickTime == 0:
            tickTime = now

        # do stuff
        if runCode:
            # to read buttons, BTN_GPIO_COCOM (always an output) must be low
# removed
# so that only GPIO.setup calls are made
#           GPIO.output(BTN_GPIO_COM, GPIO.LOW)
            # and LED_COM must be an output set low
            GPIO.setup(LED_GPIO_COM, GPIO.OUT)
# removed
# so that only GPIO.setup calls are made
#           GPIO.output(LED_GPIO_COM, GPIO.LOW)

            # to read buttons, GPIOs must be inputs
            GPIO.setup(BTN_GPIO_X_1, GPIO.IN)
            GPIO.setup(BTN_GPIO_X_2, GPIO.IN)
            GPIO.setup(BTN_GPIO_Y_1, GPIO.IN)
            GPIO.setup(BTN_GPIO_Y_2, GPIO.IN)
            GPIO.setup(BTN_GPIO_Z_1, GPIO.IN)
            GPIO.setup(BTN_GPIO_Z_2, GPIO.IN)


        # Tick! Print elapsed seconds, number of times through loop in last second
        if now >= tickTime:
            tickTime += 1
            print "%ds %d" % (seconds, count)
            seconds += 1
            count = 0

except KeyboardInterrupt:
    print
    print "Exiting..."
If run as

Code: Select all

# bug.py 0
(so that it doesn't execute the suspect code), it typically does about 130,000 calls per second (BBB rev B, Debian 2014-05-14 in eMMC)
BUT, if run as

Code: Select all

# bug.py 1
(so that it DOES execute that code), it starts out doing about 500 calls per second (expected -- doing more work), but rapidly declines from that number, to doing about 15 calls per second after 10 minutes.

Code: Select all

ps -o vsize -p <process id>
shows that the program's memory use is growing as well, even though there's nothing to justify it (it doesn't grow without the repeated GPIO.setup() calls)

I discovered this while running production code which multiplexes some buttons and LEDs over the GPIOs.

Any thoughts?
Last edited by mikeduffy on Fri Jun 27, 2014 1:29 pm, edited 1 time in total.

mikeduffy
 
Posts: 16
Joined: Mon May 19, 2014 4:51 pm

Re: Memory leak in GPIO.setup()?

Post by mikeduffy »

Fixed. Not a memory leak, just a list that got very, very long.

The problem was in event_gpio.c, in the function gpio_export(). The routine inserted a new item in the list of exported GPIOs, regardless of whether the GPIO has already been exported (i.e. the GPIO already existed in the list exported_gpios.

This makes sense: the list of exported GPIOs gets longer and longer, taking more time to traverse (since inserts occur at the end of the list).

Here's the corrected code for gpio_export():

Code: Select all

int gpio_export(unsigned int gpio)
{
    int fd, len;
    char str_gpio[10];
    struct gpio_exp *new_gpio, *g, **x;

    if (exported_gpios == NULL)
    {
        // no list yet -- point to the list header
        x = &exported_gpios;
    }
    else
    {
        for (g = exported_gpios ; g->next != NULL ; g = g->next)
            if (g->gpio == gpio)
                // already on the list
                return 0;

        // not found on list -- point to .next in the last element
        x = &(g->next);
    }
    // arriving here, g points to the place to stuff the address of the new gpio
    // either the list header, or the .next element of the last element in the list

    // export the GPIO
    if ((fd = open("/sys/class/gpio/export", O_WRONLY)) < 0)
    {
        return -1;
    }
    len = snprintf(str_gpio, sizeof(str_gpio), "%d", gpio);
    write(fd, str_gpio, len);
    close(fd);

    // allocate and initialize list element
    if ((new_gpio = (struct gpio_exp *) malloc(sizeof(struct gpio_exp))) == 0)
        return -1; // out of memory

    new_gpio->gpio = gpio;
    new_gpio->next = NULL;

    // add it to list
    *x = new_gpio;

    return 0;
}
Thanks again for a great library. If it weren't good to begin with, I wouldn't feel compelled to help fix little glitches like this.
Last edited by mikeduffy on Fri Jul 25, 2014 3:50 pm, edited 1 time in total.

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: Slowdown with repeated calls to GPIO.setup()

Post by adafruit_support_mike »

Nice catch! Thanks for posting the solution and your code.

mikeduffy
 
Posts: 16
Joined: Mon May 19, 2014 4:51 pm

Re: Slowdown with repeated calls to GPIO.setup()

Post by mikeduffy »

I made an edit to my corrected code above -- writing the GPIO number (N) to /sys/devices/class/gpio/export should NOT occur until it is determined that the GPIO has not already been exported. Otherwise, you get a lot of kernel errors of the form

Code: Select all

Jul 25 12:43:43 beaglebone kernel: [ 4027.294489] gpio_request: gpio-N (sysfs) status -16
Jul 25 12:43:43 beaglebone kernel: [ 4027.294531] export_store: status -16
where N is the GPIO that has already been exported.

These errors can rapidly fill up /var/log/kern.log if you are changing the setup of the GPIO from IN to OUT or vice-versa.

Locked
Please be positive and constructive with your questions and comments.

Return to “Beagle Bone & Adafruit Beagle Bone products”