0

I ran some code to monitor Pi hysteresis which involved mirroring a GPIO input to output. I was surprised as how slow the code was, so wrote a C version. Although this was slightly better it was still unacceptably slow.

Just how fast are the various libraries?

3

I wrote programs for a number of libraries, both c and python. These just toggle a GPIO pin as fast as possible.

NOTE In case anyone misunderstands I am aware this is an artificial test with no real world use and there are far better (and faster) ways of generating a "square" wave. It is indicative of the response to programmed GPIO commands.

pigpio is quite slow; this is to be expected due to the socket interface which has significant overhead.

WiringPI is extremely fast (I would expect the BCM2835 to perform similarly although I did not test). This is expected as they directly access GPIO registers.

RPi.GPIO is very fast - the underlying /dev/gpiomem code is written in c to directly access GPIO registers.

lgpio C library allows control of the GPIO pins through the gpiochip kernel interface is also very fast.

WiringPi       7930214 toggles per second
RPi.GPIO        801556 toggles per second
lgpio           523049 toggles per second
pigpiod C         7014 toggles per second
pigpiod Python    5974 toggles per second

This post has been referenced elsewhere and (as I feared) misinterpreted. If I wanted a program which runs as fast as possible I would not use any library.

Joan has pointed out pigpiod uses a client server model and each toggle requires a couple of network messages which accounts for the dramatic performance difference. It would be more sensible to compare against pigpio.

I did not include pigpio for the simple reason that I do not use it. It is incompatible with pigpiod (which I do use) and only allows a single program to run at a time.

I have added pigpio code which is much faster (even when run on a different model Pi).

pigpio C       2905649 toggles per second

The code is listed below:-

/*
This program toggles a GPIO pin and measures rate.
Using wiringPi

TO BUILD

gcc -Wall -o bench-wiringPi bench-wiringPi.c -lwiringPi

sudo ./bench-wiringPi

*/

// 2021-08-06

#define SigOUT 12
#define LOOPS 20000

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <wiringPi.h>

int main(void) {
  unsigned t0, t1;
  unsigned i;

  wiringPiSetupGpio();
  pinMode(SigOUT, OUTPUT);

  t0 = micros();

  for (i = 0; i < LOOPS + 1; i++) {
    digitalWrite(SigOUT, HIGH);
    digitalWrite(SigOUT, LOW);
  }
  t1 = micros();

    printf("WiringPi\t%10.0f toggles per second\n", (1.0e6 * LOOPS) / (t1 - t0));

  return 0;
}
/*
bench.c
This program toggles a GPIO pin and measures rate.

Using lgpio C API

gcc -Wall -o bench-lgpio bench-lgpio.c -llgpio

./bench
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>

#include <lgpio.h>

#define LFLAGS 0

#define SigOUT 12
#define LOOPS 20000

int main(int argc, char *argv[]) {
  int h;
  int i;
  double t0, t1;
  int status;

  h = lgGpiochipOpen(0);

  if (h >= 0) {
    if ((status = lgGpioClaimOutput(h, LFLAGS, SigOUT, 0)) == LG_OKAY) {
      t0 = lguTime();

      for (i = 0; i < LOOPS; i++) {
        lgGpioWrite(h, SigOUT, 0);
        lgGpioWrite(h, SigOUT, 1);
      }

      t1 = lguTime();

      printf("lgpio\t\t%10.0f toggles per second\n", (1.0 * LOOPS) / (t1 - t0));
    } else {
      printf("lgGpioClaimSigOUTput FAILED on Pin %d\n", SigOUT);
      lgLineInfo_t lInfo;

      status = lgGpioGetLineInfo(h, SigOUT, &lInfo);

      if (status == LG_OKAY) {
        if (lInfo.lFlags & 1) {
          printf("GPIO in use by the kernel ");
          printf("name=%s  user=%s\n", lInfo.name, lInfo.user);
        }
      }
    }
        lgGpioFree(h, SigOUT);
    lgGpiochipClose(h);
  } else
    printf("Unable to open gpiochip 0\n");
}
#! /usr/bin/env python3
"""
This program toggles a GPIO pin and measures rate.
Using RPi.GPIO Python Interface
"""

import RPi.GPIO as GPIO
import time
from signal import pause

SigOUT = 12
LOOPS = 20000

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(SigOUT, GPIO.OUT)

t0 = time.time()

for i in range(LOOPS):
    GPIO.output(SigOUT,0)
    GPIO.output(SigOUT,1)

t1 = time.time()

print("RPi.GPIO\t{:>10.0f} toggles per second".format((1.0 * LOOPS) / (t1 - t0)))

GPIO.cleanup()
/*
This program toggles a GPIO pin and measures rate.
Using pigpiod C Interface

TO BUILD

gcc -Wall -o bench-pigpiod bench-pigpiod.c -lpigpiod_if2

*/

// 2021-09-21

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <pigpiod_if2.h>

char *optHost = NULL;
char *optPort = NULL;
int SigOUT = 12;
#define LOOPS 20000

void usage() {
  fprintf(stderr,
          "\n"
          "Usage: bench-pigpio [OPTION] ...\n"
          "   -o value, output pin, 0-31,                 default %d\n"
          "   -h string, host name,                       default NULL\n"
          "   -p value, socket port, 1024-32000,          default 8888\n"
          "EXAMPLE\n"
          "bench-pigpio -i20 -o26\n",
          SigOUT);
}

static uint64_t getNum(char *str, int *err) {
  uint64_t val;
  char *endptr;

  *err = 0;
  val = strtoll(str, &endptr, 0);
  if (*endptr) {
    *err = 1;
    val = -1;
  }
  return val;
}

static void initOpts(int argc, char *argv[]) {
  int opt, err;

  while ((opt = getopt(argc, argv, "g:h:i:m:p:")) != -1) {
    switch (opt) {

    case 'o':
      SigOUT = getNum(optarg, &err);
      break;

    case 'h':
      optHost = malloc(sizeof(optarg) + 1);
      if (optHost)
        strcpy(optHost, optarg);
      break;

      optPort = malloc(sizeof(optarg) + 1);
      if (optPort)
        strcpy(optPort, optarg);
      break;

    default: /* '?' */
      usage();
      exit(-1);
    }
  }
}

int main(int argc, char *argv[]) {
  int i;
  int pi;
  double t0, t1;

  initOpts(argc, argv);

  pi = pigpio_start(optHost, optPort); /* Connect to Pi. */

  if (pi >= 0) {
    set_mode(pi, SigOUT, PI_OUTPUT);

    t0 = time_time();

    for (i = 0; i < LOOPS; i++) {
      gpio_write(pi, SigOUT, 0);
      gpio_write(pi, SigOUT, 1);
    }
    t1 = time_time();

    printf("pigpio C\t%10.0f toggles per second\n", (1.0 * LOOPS) / (t1 - t0));

    pigpio_stop(pi); /* Disconnect from Pi. */
  }
  return 0;
}
#! /usr/bin/env python3
"""
This program toggles a GPIO pin and measures rate.
Using pigpiod Python Interface
"""

import pigpio
import time
from signal import pause

SigOUT = 12
LOOPS = 20000


pi = pigpio.pi()
pi.set_mode(SigOUT, pigpio.OUTPUT)

t0 = time.time()

for i in range(LOOPS):
    pi.write(SigOUT,0)
    pi.write(SigOUT,1)

t1 = time.time()

print("pigpiod Python\t{:>10.0f} toggles per second".format((1.0 * LOOPS) / (t1 - t0)))

pi.stop()
/*
This program toggles a GPIO pin and measures rate.
Using pigpio C Interface

TO BUILD

gcc -Wall -o bench-pigpio bench-pigpio.c -lpigpio

*/

// 2021-09-21

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <pigpio.h>

int SigOUT = 12;
#define LOOPS 20000

int main(int argc, char *argv[]) {
  int i;
  double t0, t1;

  if(gpioInitialise() < 0)  return 1;

  gpioSetMode(SigOUT, PI_OUTPUT);

  t0 = time_time();

  for (i = 0; i < LOOPS; i++) {
    gpioWrite(SigOUT, 0);
    gpioWrite(SigOUT, 1);
  }
  t1 = time_time();

  printf("pigpio C\t%10.0f toggles per second\n", (1.0 * LOOPS) / (t1 - t0));

  return 0;
}

I was asked what Pi I used. I can't remember, as I ran this code on a number of Pi (including a Pi3+ running Bullseye). The results above were probably on a Pi 4 Model B Rev 1.1, but there wasn't much difference.

While investigating GPIO hysteresis I ran a program which used the GPIO as a Schmitt trigger Hysteresis - on or off?.

This produced erratic results and was quite slow, indicating significant overhead in pigpiod. I wrote an assembler program to do similar.

I then decided to investigate the overheads of various libraries and the results are above.

The faster results were quite erratic (as expected) and depended more on what else the OS was doing.

7
  • FYI, I've posted a clip from your answer here
    – Seamus
    Aug 8 at 0:26
  • @Seamus I have been playing with my own library, still very much in development. Ultimately I intend to create a shared C library. This shows pi.gpio 36764706 toggles per second NOTE I have some scepticism about the timing algorithm I am using.
    – Milliways
    Aug 8 at 4:16
  • Thank you for documenting this. The results of WiringPi are admirable but would you compare to mapping gpiomem as well?
    – MaxDZ8
    Aug 11 at 15:59
  • @MaxDZ8 Most of these libraries use /dev/gpiomem
    – Milliways
    Aug 12 at 1:12
  • @Milliways yes I have noticed, it's very clearly marked on your message, as well as a wild difference on performance. Given the data it seems reasonable to me direct access might be different as well (I use direct access and as far as I recall the performance is very different).
    – MaxDZ8
    Aug 12 at 9:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.