Using indirection for faster message passing
Message passing always involves copying the payload from the sender into a
static variable and then from the static variable into the receiver. Thus
sending a large buffer, like a [u8; 128]
, as a message involves two expensive
memcpy
s.
Indirection can minimize message passing overhead: instead of sending the buffer by value, one can send an owning pointer into the buffer.
One can use a global memory allocator to achieve indirection (alloc::Box
,
alloc::Rc
, etc.), which requires using the nightly channel as of Rust v1.37.0,
or one can use a statically allocated memory pool like heapless::Pool
.
As this example of approach goes completely outside of RTIC resource
model with shared and local the program would rely on the correctness
of the memory allocator, in this case heapless::pool
.
Here's an example where heapless::Pool
is used to "box" buffers of 128 bytes.
#![allow(unused)] fn main() { //! examples/pool.rs #![deny(unsafe_code)] #![deny(warnings)] // pool!() generates a struct without docs //#![deny(missing_docs)] #![no_main] #![no_std] use heapless::{ pool, pool::singleton::{Box, Pool}, }; use panic_semihosting as _; use rtic::app; // Declare a pool of 128-byte memory blocks pool!(P: [u8; 128]); #[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] mod app { use crate::{Box, Pool}; use cortex_m_semihosting::debug; use lm3s6965::Interrupt; // Import the memory pool into scope use super::P; #[shared] struct Shared {} #[local] struct Local {} #[init(local = [memory: [u8; 512] = [0; 512]])] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { // Increase the capacity of the memory pool by ~4 P::grow(cx.local.memory); rtic::pend(Interrupt::I2C0); (Shared {}, Local {}, init::Monotonics()) } #[task(binds = I2C0, priority = 2)] fn i2c0(_: i2c0::Context) { // claim a memory block, initialize it and .. let x = P::alloc().unwrap().init([0u8; 128]); // .. send it to the `foo` task foo::spawn(x).ok().unwrap(); // send another block to the task `bar` bar::spawn(P::alloc().unwrap().init([0u8; 128])) .ok() .unwrap(); } #[task] fn foo(_: foo::Context, _x: Box<P>) { // explicitly return the block to the pool drop(_x); debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator } #[task(priority = 2)] fn bar(_: bar::Context, _x: Box<P>) { // this is done automatically so we can omit the call to `drop` // drop(x); } } }
$ cargo run --target thumbv7m-none-eabi --example pool