use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[derive(Copy, Clone, Debug)]
#[repr(usize)]
pub enum Stage {
Parse = 0,
Intra,
Inter,
Transform,
Deblock,
Sao,
Dpb,
Mc,
Combine,
Pad,
SaoCopy,
PicAlloc,
}
pub const N: usize = 12;
const NAMES: [&str; N] = ["parse", "intra", "inter", "transform", "deblock", "sao", "dpb", " |- mc", " |- combine", " |- pad", " |- sao copy", " |- pic alloc"];
const NESTED_IN_PARSE: [Stage; 3] = [Stage::Intra, Stage::Inter, Stage::Transform];
const NESTED_IN_INTER: [Stage; 1] = [Stage::Mc];
const NESTED_IN_MC: [Stage; 2] = [Stage::Combine, Stage::Pad];
const NESTED_IN_SAO: [Stage; 1] = [Stage::SaoCopy];
const NESTED_IN_DPB: [Stage; 1] = [Stage::PicAlloc];
static NS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static CALLS: [AtomicU64; N] = [const { AtomicU64::new(0) }; N];
static ON: AtomicBool = AtomicBool::new(false);
static TAX_MNS: AtomicU64 = AtomicU64::new(0);
pub fn enable() {
const CAL: u64 = 200_000;
let t = std::time::Instant::now();
for _ in 0..CAL {
let s = Scope::start_raw();
std::hint::black_box(&s);
s.stop_raw(Stage::Parse as usize, false);
}
let per = t.elapsed().as_nanos() as u64 * 1000 / CAL;
for i in 0..N {
NS[i].store(0, Ordering::Relaxed);
CALLS[i].store(0, Ordering::Relaxed);
}
TAX_MNS.store(per, Ordering::Relaxed);
ON.store(true, Ordering::Relaxed);
}
#[inline(always)]
pub fn enabled() -> bool {
ON.load(Ordering::Relaxed)
}
pub struct Scope {
t: std::time::Instant,
stage: usize,
}
impl Scope {
#[inline(always)]
fn start_raw() -> Scope {
Scope { t: std::time::Instant::now(), stage: 0 }
}
#[inline(always)]
fn stop_raw(self, stage: usize, count: bool) {
NS[stage].fetch_add(self.t.elapsed().as_nanos() as u64, Ordering::Relaxed);
if count {
CALLS[stage].fetch_add(1, Ordering::Relaxed);
}
}
#[inline(always)]
pub fn new(stage: Stage) -> Option<Scope> {
if enabled() {
Some(Scope { t: std::time::Instant::now(), stage: stage as usize })
} else {
None
}
}
}
impl Drop for Scope {
#[inline(always)]
fn drop(&mut self) {
NS[self.stage].fetch_add(self.t.elapsed().as_nanos() as u64, Ordering::Relaxed);
CALLS[self.stage].fetch_add(1, Ordering::Relaxed);
}
}
pub fn report(total_ns: u64) -> String {
let tax_mns = TAX_MNS.load(Ordering::Relaxed);
let mut out = String::new();
out.push_str("\nstage ms % calls ns/call profiler tax\n");
out.push_str("---------------------------------------------------------------\n");
let mut sum = 0u64;
let mut sum_tax = 0u64;
let nested: u64 = NESTED_IN_PARSE.iter().map(|s| NS[*s as usize].load(Ordering::Relaxed)).sum();
let in_inter: u64 = NESTED_IN_INTER.iter().map(|s| NS[*s as usize].load(Ordering::Relaxed)).sum();
let in_mc: u64 = NESTED_IN_MC.iter().map(|s| NS[*s as usize].load(Ordering::Relaxed)).sum();
let in_sao: u64 = NESTED_IN_SAO.iter().map(|s| NS[*s as usize].load(Ordering::Relaxed)).sum();
let in_dpb: u64 = NESTED_IN_DPB.iter().map(|s| NS[*s as usize].load(Ordering::Relaxed)).sum();
for i in 0..N {
let raw = NS[i].load(Ordering::Relaxed);
let ns = if i == Stage::Parse as usize {
raw.saturating_sub(nested)
} else if i == Stage::Inter as usize {
raw.saturating_sub(in_inter)
} else if i == Stage::Mc as usize {
raw.saturating_sub(in_mc)
} else if i == Stage::Sao as usize {
raw.saturating_sub(in_sao)
} else if i == Stage::Dpb as usize {
raw.saturating_sub(in_dpb)
} else {
raw
};
let calls = CALLS[i].load(Ordering::Relaxed);
if calls == 0 {
continue;
}
let tax = calls * tax_mns / 1000;
sum += ns;
sum_tax += tax;
let flag = if ns > 0 && tax * 4 > ns { " <-- TAX-DOMINATED" } else { "" };
out.push_str(&format!(
"{:<9} {:>7.1} {:>7.1}% {:>9} {:>13.1} {:>9.1} ms{}\n",
NAMES[i],
ns as f64 / 1e6,
if total_ns > 0 { 100.0 * ns as f64 / total_ns as f64 } else { 0.0 },
calls,
ns as f64 / calls as f64,
tax as f64 / 1e6,
flag
));
}
out.push_str("---------------------------------------------------------------\n");
let residue = total_ns.saturating_sub(sum);
out.push_str(&format!(
"{:<9} {:>7.1} {:>7.1}% (untimed: everything not in a stage above)\n",
"residue",
residue as f64 / 1e6,
if total_ns > 0 { 100.0 * residue as f64 / total_ns as f64 } else { 0.0 }
));
out.push_str(&format!(
"{:<9} {:>7.1} total decode, and {:.1} ms of that is this profiler\n",
"total",
total_ns as f64 / 1e6,
sum_tax as f64 / 1e6
));
out.push_str(&format!(
"\nper-scope cost measured at {:.2} ns on this machine. Compare the tax\n\
column against each stage before believing its share (codec-measurement §6):\n\
if the residue is close to the total tax there is nothing hidden in it.\n",
tax_mns as f64 / 1000.0
));
out
}