#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(usize)]
pub enum Stage {
Total = 0,
FillBuffers,
Fdct,
Quantize,
Entropy,
HuffmanOptimize,
Headers,
GetBlock,
RowSetup,
}
impl Stage {
pub const COUNT: usize = 9;
pub fn name(self) -> &'static str {
match self {
Stage::Total => "Total",
Stage::FillBuffers => "FillBuffers",
Stage::Fdct => "Fdct",
Stage::Quantize => "Quantize",
Stage::Entropy => "Entropy",
Stage::HuffmanOptimize => "HuffmanOptimize",
Stage::Headers => "Headers",
Stage::GetBlock => "GetBlock",
Stage::RowSetup => "RowSetup",
}
}
fn from_index(i: usize) -> Stage {
match i {
0 => Stage::Total,
1 => Stage::FillBuffers,
2 => Stage::Fdct,
3 => Stage::Quantize,
4 => Stage::Entropy,
5 => Stage::HuffmanOptimize,
6 => Stage::Headers,
7 => Stage::GetBlock,
_ => Stage::RowSetup,
}
}
}
#[cfg(not(feature = "profile"))]
mod imp {
use super::Stage;
pub struct Guard;
#[inline(always)]
pub fn scope(_stage: Stage) -> Guard {
Guard
}
#[inline(always)]
pub fn reset() {}
pub fn snapshot() -> [(f64, u64); Stage::COUNT] {
[(0.0, 0); Stage::COUNT]
}
pub fn dump() -> alloc::string::String {
alloc::string::String::from(
"profiling disabled — rebuild with `--features profile` to get a breakdown\n",
)
}
}
#[cfg(feature = "profile")]
mod imp {
use super::Stage;
use alloc::format;
use alloc::string::String;
use core::sync::atomic::{AtomicU64, Ordering};
#[allow(clippy::declare_interior_mutable_const)]
const ZERO: AtomicU64 = AtomicU64::new(0);
static CYCLES: [AtomicU64; Stage::COUNT] = [ZERO; Stage::COUNT];
static CALLS: [AtomicU64; Stage::COUNT] = [ZERO; Stage::COUNT];
#[inline(always)]
fn now() -> u64 {
#[cfg(all(
feature = "profile",
any(target_arch = "x86", target_arch = "x86_64")
))]
{
#[allow(unsafe_code)]
unsafe {
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::_rdtsc;
#[cfg(target_arch = "x86")]
use core::arch::x86::_rdtsc;
_rdtsc()
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
0
}
}
pub struct Guard {
stage: usize,
start: u64,
}
impl Drop for Guard {
#[inline(always)]
fn drop(&mut self) {
let elapsed = now().wrapping_sub(self.start);
CYCLES[self.stage].fetch_add(elapsed, Ordering::Relaxed);
CALLS[self.stage].fetch_add(1, Ordering::Relaxed);
}
}
#[inline(always)]
pub fn scope(stage: Stage) -> Guard {
Guard {
stage: stage as usize,
start: now(),
}
}
pub fn reset() {
for i in 0..Stage::COUNT {
CYCLES[i].store(0, Ordering::Relaxed);
CALLS[i].store(0, Ordering::Relaxed);
}
}
pub fn snapshot() -> [(f64, u64); Stage::COUNT] {
let mut out = [(0.0, 0); Stage::COUNT];
for i in 0..Stage::COUNT {
out[i] = (
CYCLES[i].load(Ordering::Relaxed) as f64,
CALLS[i].load(Ordering::Relaxed),
);
}
out
}
pub fn dump() -> String {
let snap = snapshot();
let total = snap[Stage::Total as usize].0.max(1.0);
let mut s = String::from("stage cycles% Mcycles calls\n");
let mut named = 0.0;
for i in 1..Stage::COUNT {
let (cy, calls) = snap[i];
named += cy;
s.push_str(&format!(
"{:<16} {:>7.2}% {:>12.1} {:>10}\n",
Stage::from_index(i).name(),
100.0 * cy / total,
cy / 1e6,
calls
));
}
let residue = total - named;
let scopes: u64 = snap.iter().map(|(_, c)| *c).sum();
s.push_str(&format!(
"{:<16} {:>7.2}% {:>12.1} {:>10}\n",
"residue",
100.0 * residue / total,
residue / 1e6,
"-"
));
s.push_str(&format!(
"{:<16} {:>7.2}% {:>12.1} {:>10}\n",
"Total",
100.0,
total / 1e6,
snap[Stage::Total as usize].1
));
s.push_str(&format!(
"\n{} scope entries; at ~40 cycles/scope the probe itself accounts for \
~{:.1} Mcycles ({:.1}% of Total).\n\
If that is close to the residue, the residue IS the instrument — stop decomposing.\n",
scopes,
(scopes * 40) as f64 / 1e6,
100.0 * (scopes * 40) as f64 / total
));
s
}
}
pub use imp::{dump, reset, scope, snapshot, Guard};