#[derive(Clone, Copy)]
pub enum Stage {
Entropy = 0,
IntraPred = 1,
InterMc = 2,
Reconstruct = 3,
Deblock = 4,
Dequant = 5,
Scatter = 6,
PredBuf = 7,
MvGrid = 8,
Neighbors = 9,
SkipRecon = 10,
Finalize = 11,
Syntax = 12,
DpbClone = 13,
Total = 14,
}
pub const N: usize = 15;
#[cfg(feature = "profile")]
mod imp {
use super::{Stage, N};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::Instant;
const SUB: usize = Stage::Total as usize;
#[cfg(target_arch = "x86_64")]
#[inline(always)]
fn ticks() -> u64 {
unsafe { core::arch::x86_64::_rdtsc() }
}
#[cfg(not(target_arch = "x86_64"))]
#[inline(always)]
fn ticks() -> u64 {
use std::sync::OnceLock;
static EPOCH: OnceLock<Instant> = OnceLock::new();
EPOCH.get_or_init(Instant::now).elapsed().as_nanos() as u64
}
static ANCHOR: Mutex<Option<(Instant, u64)>> = Mutex::new(None);
const NAMES: [&str; N] = [
"entropy/cavlc",
"intra-pred",
"inter-mc",
"reconstruct",
"deblock",
"dequant",
"scatter(store)",
"pred-buf copy",
"mv+grid",
"neighbors",
"skip-recon",
"finalize",
"syntax-parse",
"dpb-clone",
"TOTAL decode()",
];
static NS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static CALLS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
pub struct Guard {
stage: usize,
start: u64,
}
impl Drop for Guard {
#[inline]
fn drop(&mut self) {
let d = ticks().wrapping_sub(self.start);
NS[self.stage].fetch_add(d, Ordering::Relaxed);
CALLS[self.stage].fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
pub fn scope(s: Stage) -> Guard {
Guard {
stage: s as usize,
start: ticks(),
}
}
pub fn reset() {
for a in NS.iter().chain(CALLS.iter()) {
a.store(0, Ordering::Relaxed);
}
*ANCHOR.lock().unwrap() = Some((Instant::now(), ticks()));
}
pub fn name(i: usize) -> &'static str {
NAMES.get(i).copied().unwrap_or("?")
}
pub fn snapshot() -> [(f64, u64); N] {
let load = |i: usize| NS[i].load(Ordering::Relaxed);
let ns_per_tick = ANCHOR
.lock()
.unwrap()
.map(|(t0, c0)| {
let wall = t0.elapsed().as_nanos() as f64;
let cyc = ticks().wrapping_sub(c0) as f64;
if cyc > 0.0 {
wall / cyc
} else {
1.0
}
})
.unwrap_or(1.0);
let mut out = [(0.0f64, 0u64); N];
for (i, o) in out.iter_mut().enumerate() {
*o = (load(i) as f64 * ns_per_tick / 1e6, CALLS[i].load(Ordering::Relaxed));
}
out
}
pub fn dump() {
let s = snapshot();
let total = s[SUB].0.max(1e-9);
let sub_sum: f64 = (0..SUB).map(|i| s[i].0).sum();
let mgmt = (total - sub_sum).max(0.0);
let pct = |ms: f64| 100.0 * ms / total;
eprintln!("\n--- decode stage profile (decode() wall = {total:.1} ms) ---");
for i in 0..SUB {
eprintln!(
" {:<15} {:>8.1} ms {:>5.1}% ({} calls)",
NAMES[i], s[i].0, pct(s[i].0), s[i].1,
);
}
eprintln!(
" {:<15} {:>8.1} ms {:>5.1}% <- the OTHER bucket: mb mgmt / mv-pred / nnz / grid / dequant",
"mgmt/other", mgmt, pct(mgmt),
);
eprintln!(" {:<15} {:>8.1} ms 100.0%", NAMES[SUB], total);
}
}
#[cfg(not(feature = "profile"))]
mod imp {
use super::{Stage, N};
pub struct Guard;
#[inline(always)]
pub fn scope(_s: Stage) -> Guard {
Guard
}
#[inline(always)]
pub fn reset() {}
#[inline(always)]
pub fn dump() {}
#[inline(always)]
pub fn snapshot() -> [(f64, u64); N] {
[(0.0, 0); N]
}
#[inline(always)]
pub fn name(_i: usize) -> &'static str {
""
}
}
pub use imp::{dump, name, reset, scope, snapshot, Guard};