Skip to content

Commit

Permalink
Read serial line in chunks instead of character by character
Browse files Browse the repository at this point in the history
Without this change, CPU load caused by this driver on a powerful x86
machine with a USB-connected GPS is 15.2%. The output of the strace
command looks like this:

    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999999000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=999997160})
    read(33, "$", 1)                        = 1
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999999000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=999997160})
    read(33, "G", 1)                        = 1
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999999000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=999997160})
    read(33, "P", 1)                        = 1
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999999000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=999997180})
    read(33, "R", 1)                        = 1
    ...

It can be seen that characters are read from the serial line one by
one. This is due to how IOBase.readline() interacts with pySerial.

In this commit, we implement the same functionality as
IOBase.readline(), but in a way allowing to read the serial line
characters in bigger chunks. With it, CPU load decreases to 4.6% and
strace reports this:

    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=946164000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=926513145})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 565) = 143
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=926286000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=906510472})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 422) = 143
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=906287000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=886945652})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 279) = 143
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=886721000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=866882570})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 136) = 136
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999996000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=999991549})
    read(33, ",,*57\r\n", 1024)             = 7
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=999827000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=985949688})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 1017) = 143
    pselect6(35, [33 34], [], [], {tv_sec=1, tv_nsec=985746000}, NULL) = 1 (in [33], left {tv_sec=1, tv_nsec=966235438})
    read(33, "$GPRMC,,V,,,,,,,,,,N*53\r\n$GPGGA,"..., 874) = 143

Reading from the kernel happens most of the time in chunks of 143
bytes (for our GPS) and therefore, the system call overhead is 143×
lower.
  • Loading branch information
wentasah committed Apr 1, 2023
1 parent 06d71c7 commit 95edd26
Showing 1 changed file with 14 additions and 10 deletions.
24 changes: 14 additions & 10 deletions src/libnmea_navsat_driver/nodes/nmea_serial_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# POSSIBILITY OF SUCH DAMAGE.

import serial
import io

import rclpy

Expand All @@ -50,17 +51,20 @@ def main(args=None):
GPS = serial.Serial(port=serial_port, baudrate=serial_baud, timeout=2)
driver.get_logger().info("Successfully connected to {0} at {1}.".format(serial_port, serial_baud))
try:
data = bytearray()
while rclpy.ok():
data = GPS.readline().strip()
try:
if isinstance(data, bytes):
data = data.decode("utf-8")
driver.add_sentence(data, frame_id)
except ValueError as e:
driver.get_logger().warn(
"Value error, likely due to missing fields in the NMEA message. Error was: %s. "
"Please report this issue at github.com/ros-drivers/nmea_navsat_driver, including a bag file "
"with the NMEA sentences that caused it." % e)
data.extend(GPS.read(1024))
lines = data.splitlines(keepends=True)
for line in lines[0:-1]:
line = line.decode("utf-8").rstrip()
try:
driver.add_sentence(line, frame_id)
except ValueError as e:
driver.get_logger().warn(
"Value error, likely due to missing fields in the NMEA message. Error was: %s. "
"Please report this issue at github.com/ros-drivers/nmea_navsat_driver, including a bag file "
"with the NMEA sentences that caused it." % e)
data = lines[-1]

except Exception as e:
driver.get_logger().error("Ros error: {0}".format(e))
Expand Down

0 comments on commit 95edd26

Please sign in to comment.