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.
//! examples/pool.rs
#![no_main]
#![no_std]
#![deny(warnings)]
#![feature(type_alias_impl_trait)]
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) {
// Increase the capacity of the memory pool by ~4
P::grow(cx.local.memory);
rtic::pend(Interrupt::I2C0);
(Shared {}, Local {})
}
#[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]
async 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)]
async 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