rust-embedded / rust-sysfs-gpio

A Rust Interface to the Linux sysfs GPIO interface (https://www.kernel.org/doc/Documentation/gpio/sysfs.txt)
Apache License 2.0
383 stars 45 forks source link

Write performance is slow #37

Closed NealEhardt closed 5 years ago

NealEhardt commented 7 years ago

I'm using a Raspberry Pi 3 to write bits to a shift register. I use 2 pins: data and clock. At the rising edge of the clock, the shift register reads one bit from the data pin.

Here's a simple program that pushes 2 bits per loop [0, 1] to the shift register. Every second, it prints the number of loops it completed in the previous second.

fn main() {
    let clock = sysfs_gpio::Pin::new(20u64);
    let data = sysfs_gpio::Pin::new(21u64);
    match clock.with_exported(|| {
        data.with_exported(|| {
            sleep(Duration::from_millis(80));

            try!(clock.set_direction(sysfs_gpio::Direction::Out));
            try!(data.set_direction(sysfs_gpio::Direction::Out));

            println!("How fast can I tick-tock?");
            let start = std::time::Instant::now();
            let mut next_t = 1;
            let mut loops = 0;
            loop {
                try!(clock.set_value(0));
                try!(data.set_value(0));
                try!(clock.set_value(1));
                try!(clock.set_value(0));
                try!(data.set_value(1));
                try!(clock.set_value(1));

                loops += 1;
                let t = start.elapsed().as_secs();
                if t >= next_t {
                    println!("{}", loops);
                    next_t = t + 1;
                    loops = 0;
                }
            }
        })
    }) {
        Ok(()) => println!("Unreachable"),
        Err(err) => println!("main err: {}", err),
    }
}

Here's some output:

How fast can I tick-tock?
5095
5080
5087
5141
5133
5133
5141
5138

That's ~5130 loops/second, or ~10 Kbps. My requirement is 100 Mbps. The 1.2 GHz Pi 3 should be able to support this. Seems that rust-sysfs-gpio isn't the right tool for me, but I suspect there's an easy way to improve its performance by 1-2 orders of magnitude.

posborne commented 7 years ago

That's ~5130 loops/second, or ~10 Kbps. My requirement is 100 Mbps. The 1.2 GHz Pi 3 should be able to support this. Seems that rust-sysfs-gpio isn't the right tool for me, but I suspect there's an easy way to improve its performance by 1-2 orders of magnitude.

Even at the kernel layer (or talking to the raw peripheral) I doubt you will be able to bitbang 100mbps very easily without exploiting other peripherals (e.g. PWM) in order to make it happen. This is certainly well outside of the linux sysfs GPIO intended goals.

That being said, there have been some suggestions for how performance could be improved some and would welcome data on the performance improvements from implementing some of these changes: https://github.com/rust-embedded/rust-sysfs-gpio/issues/30. https://crates.io/crates/rppal in particular seems like a decent option if you are OK with sacrificing portability in favor of performance on this specific platform.

NealEhardt commented 7 years ago

Thanks for the tip! Very generous of you to maintain this library and guide me. I'll give rppal a shot and report my findings.

rtxm commented 7 years ago

Have you considered driving your shift register with SPI given your speed requirement?

NealEhardt commented 7 years ago

Good suggestion @rtxm. I'm using spidev for clock/data and rppal for the latch. So far so good!

I ported my above example to rppal for a speed comparison. This runs about 150 times faster.

extern crate rppal;
use rppal::gpio::{GPIO, Mode, Level};

fn main() {
    const CLOCK: u8 = 18u8;
    const DATA: u8 = 16u8;
    let mut gpio = GPIO::new().unwrap();
    gpio.set_mode(CLOCK, Mode::Output);
    gpio.set_mode(DATA, Mode::Output);

    println!("How fast can I tick-tock?");
    let start = std::time::Instant::now();
    let mut next_t = 1;
    let mut loops = 0;
    loop {
        gpio.write(CLOCK, Level::Low);
        gpio.write(DATA, Level::Low);
        gpio.write(CLOCK, Level::High);
        gpio.write(CLOCK, Level::Low);
        gpio.write(DATA, Level::High);
        gpio.write(CLOCK, Level::High);

        loops += 1;
        let t = start.elapsed().as_secs();
        if t >= next_t {
            println!("{}", loops);
            next_t = t + 1;
            loops = 0;
        }
    }
}
NealEhardt commented 5 years ago

My stated bandwidth expectations are unrealistic for any GPIO library.