joan2937 / lg

Linux C libraries and Python modules for manipulating GPIO
The Unlicense
57 stars 20 forks source link

unexpected CPU usage when monitoring a pin #26

Open warthog618 opened 1 month ago

warthog618 commented 1 month ago

I've been hearing lots of good things about lgpio lately, particularly now it is the default backend for gpiozero, so I thought would be worthwhile to take it for a quick spin.

First thing I tried was the gpiozero button example, but that didn't behave quite as I expected - it is using a lot more CPU than I anticipated:

$ python docs/examples/button_lgpio.py &
[1] 805
$ top -p 805 -H
top - 16:37:27 up 6 min,  1 user,  load average: 0.16, 0.21, 0.11
Threads:   5 total,   0 running,   5 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.2 us,  1.1 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1845.9 total,   1614.3 free,    148.6 used,    138.5 buff/cache     
MiB Swap:    100.0 total,    100.0 free,      0.0 used.   1697.3 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                
    805 pi        20   0  257248  18304   6016 S   5.3   1.0   0:03.87 python                                 
    808 pi        20   0  257248  18304   6016 S   5.3   1.0   0:03.46 python                                 
    806 pi        20   0  257248  18304   6016 S   0.0   1.0   0:00.00 python                                 
    807 pi        20   0  257248  18304   6016 S   0.0   1.0   0:00.07 python                                 
    810 pi        20   0  257248  18304   6016 S   0.0   1.0   0:00.03 python                                 

More than 5% CPU on a Pi4 to monitor one pin seems a tad excessive.

That example is based on the gpiozero _button4.py example, modified to enforce usage of lgpio and to request GPIO22, as my basic test setup is a Pi with a jumper across GPIO22 and GPIO23. The change isn't terribly relevant, so I will omit it for brevity - the unadulterated _button4.py example behaves the same, just on a different pin - as does the lgpio monitor.py example.

Checking that the line is requested by lgpio as expected (using libgpiod v2 tools):

$ gpioinfo GPIO22
gpiochip0 22    "GPIO22"            input bias=pull-up edges=both consumer="lg"

so it is definitely using lgpio.

It does see edges, so that is a plus:

$ gpioset -t0 GPIO23=0
Hello!
$ gpioset -t0 GPIO23=1
Goodbye!

I tried the same thing on a Pi0 (sadly an old Zero W, not a Zero 2), and I see similar:

$ python button_lgpio.py &
[1] 907
$ top -p 907 -H
top - 19:05:34 up  1:59,  1 user,  load average: 0.15, 0.07, 0.02
Threads:   5 total,   1 running,   4 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3.8 us,  8.9 sy,  0.0 ni, 87.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    427.9 total,    220.1 free,    100.2 used,    155.9 buff/cache     
MiB Swap:    100.0 total,    100.0 free,      0.0 used.    327.7 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                  
  907 pi        20   0   56344  14556   6160 S  11.6   3.3   0:05.31 python                                   
  910 pi        20   0   56344  14556   6160 R  11.3   3.3   0:03.26 python                                   
  912 pi        20   0   56344  14556   6160 S   0.3   3.3   0:00.09 python                                   
  908 pi        20   0   56344  14556   6160 S   0.0   3.3   0:00.00 python                                   
  909 pi        20   0   56344  14556   6160 S   0.0   3.3   0:00.06 python

So now over 10% of the CPU just to monitor one line.

For comparison, I tried the gpiod equivalent example, _watch_linevalue.py, from the libgpiod source tree, and got this:

$ python bindings/python/examples/watch_line_value.py &
[1] 983
$ top -p 983 -H
top - 19:13:26 up  2:07,  1 user,  load average: 0.14, 0.16, 0.09
Threads:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    427.9 total,    216.9 free,     96.9 used,    162.4 buff/cache     
MiB Swap:    100.0 total,    100.0 free,      0.0 used.    331.0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                  
  983 pi        20   0   16628   9456   5312 S   0.0   2.2   0:00.80 python          

which is more like what I expected - as the monitoring is interrupt driven there is no reason for it to be burning CPU.

Confirming gpiod has requested the line:

$ gpioinfo GPIO22
gpiochip0 22    GPIO22              input bias=pull-up edges=both debounce-period=10ms consumer=watch-line-value

And confirming it is working:

$ gpioset -t0 GPIO23=0
line: 22  type: Falling  event #1
$ gpioset -t0 GPIO23=1
line: 22  type: Rising   event #2

All cases above are running on Raspberry Pi OS Bookworm.

I also had a quick look at Bullseye on the Pi0, which uses RPi.GPIO as the default backend, and I get:

top - 20:16:23 up 42 min,  1 user,  load average: 0.17, 0.10, 0.05
Threads:   3 total,   0 running,   3 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  0.7 sy,  0.0 ni, 98.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :    428.1 total,    236.6 free,     44.8 used,    146.8 buff/cache
MiB Swap:    500.0 total,    500.0 free,      0.0 used.    330.4 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                  
  667 pi        20   0   32716  10492   5612 S   0.3   2.4   0:00.04 python                                   
  661 pi        20   0   32716  10492   5612 S   0.0   2.4   0:01.12 python                                   
  666 pi        20   0   32716  10492   5612 S   0.0   2.4   0:00.00 python   

That also does not require much CPU.

So why is lgpio using so much CPU?

Cheers, Kent.