mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-09-27 04:10:25 +00:00
195 lines
6.1 KiB
Rust
195 lines
6.1 KiB
Rust
//! Basic side-by-side example of the Earliest Deadline First scheduler
|
|
//!
|
|
//! This test spawns a number of background "ambient system load" workers
|
|
//! that are constantly working, and runs two sets of trials.
|
|
//!
|
|
//! The first trial runs with no deadline set, so our trial task is at the
|
|
//! same prioritization level as the background worker tasks.
|
|
//!
|
|
//! The second trial sets a deadline, meaning that it will be given higher
|
|
//! scheduling priority than background tasks, that have no deadline set
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use core::sync::atomic::{compiler_fence, Ordering};
|
|
|
|
use defmt::unwrap;
|
|
use embassy_executor::Spawner;
|
|
use embassy_time::{Duration, Instant, Timer};
|
|
use {defmt_rtt as _, panic_probe as _};
|
|
|
|
#[embassy_executor::main]
|
|
async fn main(spawner: Spawner) {
|
|
embassy_nrf::init(Default::default());
|
|
|
|
// Enable flash cache to remove some flash latency jitter
|
|
compiler_fence(Ordering::SeqCst);
|
|
embassy_nrf::pac::NVMC.icachecnf().write(|w| {
|
|
w.set_cacheen(true);
|
|
});
|
|
compiler_fence(Ordering::SeqCst);
|
|
|
|
//
|
|
// Baseline system load tunables
|
|
//
|
|
|
|
// how many load tasks? More load tasks means more tasks contending
|
|
// for the runqueue
|
|
let tasks = 32;
|
|
// how long should each task work for? The longer the working time,
|
|
// the longer the max jitter possible, even when a task is prioritized,
|
|
// as EDF is still cooperative and not pre-emptive
|
|
//
|
|
// 33 ticks ~= 1ms
|
|
let work_time_ticks = 33;
|
|
// what fraction, 1/denominator, should the system be busy?
|
|
// bigger number means **less** busy
|
|
//
|
|
// 2 => 50%
|
|
// 4 => 25%
|
|
// 10 => 10%
|
|
let denominator = 2;
|
|
|
|
// Total time window, so each worker is working 1/denominator
|
|
// amount of the total time
|
|
let time_window = work_time_ticks * u64::from(tasks) * denominator;
|
|
|
|
// Spawn all of our load workers!
|
|
for i in 0..tasks {
|
|
spawner.spawn(unwrap!(load_task(i, work_time_ticks, time_window)));
|
|
}
|
|
|
|
// Let all the tasks spin up
|
|
defmt::println!("Spinning up load tasks...");
|
|
Timer::after_secs(1).await;
|
|
|
|
//
|
|
// Trial task worker tunables
|
|
//
|
|
|
|
// How many steps should the workers under test run?
|
|
// More steps means more chances to have to wait for other tasks
|
|
// in line ahead of us.
|
|
let num_steps = 100;
|
|
|
|
// How many ticks should the worker take working on each step?
|
|
//
|
|
// 33 ticks ~= 1ms
|
|
let work_ticks = 33;
|
|
// How many ticks should the worker wait on each step?
|
|
//
|
|
// 66 ticks ~= 2ms
|
|
let idle_ticks = 66;
|
|
|
|
// How many times to repeat each trial?
|
|
let trials = 3;
|
|
|
|
// The total time a trial would take, in a perfect unloaded system
|
|
let theoretical = (num_steps * work_ticks) + (num_steps * idle_ticks);
|
|
|
|
defmt::println!("");
|
|
defmt::println!("Starting UNPRIORITIZED worker trials");
|
|
for _ in 0..trials {
|
|
//
|
|
// UNPRIORITIZED worker
|
|
//
|
|
defmt::println!("");
|
|
defmt::println!("Starting unprioritized worker");
|
|
let start = Instant::now();
|
|
for _ in 0..num_steps {
|
|
let now = Instant::now();
|
|
while now.elapsed().as_ticks() < work_ticks {}
|
|
Timer::after_ticks(idle_ticks).await;
|
|
}
|
|
let elapsed = start.elapsed().as_ticks();
|
|
defmt::println!(
|
|
"Trial complete, theoretical ticks: {=u64}, actual ticks: {=u64}",
|
|
theoretical,
|
|
elapsed
|
|
);
|
|
let ratio = ((elapsed as f32) / (theoretical as f32)) * 100.0;
|
|
defmt::println!("Took {=f32}% of ideal time", ratio);
|
|
Timer::after_millis(500).await;
|
|
}
|
|
|
|
Timer::after_secs(1).await;
|
|
|
|
defmt::println!("");
|
|
defmt::println!("Starting PRIORITIZED worker trials");
|
|
for _ in 0..trials {
|
|
//
|
|
// PRIORITIZED worker
|
|
//
|
|
defmt::println!("");
|
|
defmt::println!("Starting prioritized worker");
|
|
let start = Instant::now();
|
|
// Set the deadline to ~2x the theoretical time. In practice, setting any deadline
|
|
// here elevates the current task above all other worker tasks.
|
|
let meta = embassy_executor::Metadata::for_current_task().await;
|
|
meta.set_deadline_after(theoretical * 2);
|
|
|
|
// Perform the trial
|
|
for _ in 0..num_steps {
|
|
let now = Instant::now();
|
|
while now.elapsed().as_ticks() < work_ticks {}
|
|
Timer::after_ticks(idle_ticks).await;
|
|
}
|
|
|
|
let elapsed = start.elapsed().as_ticks();
|
|
defmt::println!(
|
|
"Trial complete, theoretical ticks: {=u64}, actual ticks: {=u64}",
|
|
theoretical,
|
|
elapsed
|
|
);
|
|
let ratio = ((elapsed as f32) / (theoretical as f32)) * 100.0;
|
|
defmt::println!("Took {=f32}% of ideal time", ratio);
|
|
|
|
// Unset the deadline, deadlines are not automatically cleared, and if our
|
|
// deadline is in the past, then we get very high priority!
|
|
meta.unset_deadline();
|
|
|
|
Timer::after_millis(500).await;
|
|
}
|
|
|
|
defmt::println!("");
|
|
defmt::println!("Trials Complete.");
|
|
}
|
|
|
|
#[embassy_executor::task(pool_size = 32)]
|
|
async fn load_task(id: u32, ticks_on: u64, ttl_ticks: u64) {
|
|
let mut last_print = Instant::now();
|
|
let mut last_tick = last_print;
|
|
let mut variance = 0;
|
|
let mut max_variance = 0;
|
|
loop {
|
|
let tgt = last_tick + Duration::from_ticks(ttl_ticks);
|
|
assert!(tgt > Instant::now(), "fell too behind!");
|
|
|
|
Timer::at(tgt).await;
|
|
let now = Instant::now();
|
|
// How late are we from the target?
|
|
let var = now.duration_since(tgt).as_ticks();
|
|
max_variance = max_variance.max(var);
|
|
variance += var;
|
|
|
|
// blocking work
|
|
while now.elapsed().as_ticks() < ticks_on {}
|
|
|
|
if last_print.elapsed() >= Duration::from_secs(1) {
|
|
defmt::trace!(
|
|
"Task {=u32} variance ticks (1s): {=u64}, max: {=u64}, act: {=u64}",
|
|
id,
|
|
variance,
|
|
max_variance,
|
|
ticks_on,
|
|
);
|
|
max_variance = 0;
|
|
variance = 0;
|
|
last_print = Instant::now();
|
|
}
|
|
|
|
last_tick = tgt;
|
|
}
|
|
}
|