use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering::Relaxed};
use std::sync::OnceLock;
use std::time::Instant;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum S {
Other = 0,
Header,
ModeInfo,
Detokenize,
IntraPred,
InterPred,
InvTxAdd,
LoopFilter,
Adapt,
RefUpdate,
FrameSetup,
Count,
}
pub const N: usize = S::Count as usize;
const NAMES: [&str; N] = [
"other/glue",
"read_headers",
"read_mode_info",
"detokenize",
"intra_pred",
"inter_pred(mc)",
"invtx_add",
"loop_filter",
"adapt_probs",
"ref_update",
"frame_setup(alloc)",
];
const IDLE: usize = N;
static CYC: [AtomicU64; N + 1] = [const { AtomicU64::new(0) }; N + 1];
static COUNT: [AtomicU64; N + 1] = [const { AtomicU64::new(0) }; N + 1];
static ENABLED: AtomicU8 = AtomicU8::new(UNSET);
const UNSET: u8 = 2;
thread_local! {
static STATE: RefCell<(Vec<usize>, u64)> = RefCell::new((vec![IDLE], 0));
}
static CAL: OnceLock<(Instant, u64)> = OnceLock::new();
#[cfg(target_arch = "x86_64")]
#[inline(always)]
fn rdtsc() -> u64 {
unsafe { std::arch::x86_64::_rdtsc() }
}
#[cfg(not(target_arch = "x86_64"))]
#[inline(always)]
fn rdtsc() -> u64 {
static EPOCH: OnceLock<Instant> = OnceLock::new();
EPOCH.get_or_init(Instant::now).elapsed().as_nanos() as u64
}
#[inline(always)]
fn enabled() -> bool {
match ENABLED.load(Relaxed) {
0 => false,
1 => true,
_ => {
let on = std::env::var("VP9_DPROF").is_ok();
ENABLED.store(on as u8, Relaxed);
on
}
}
}
pub fn set_enabled(on: bool) {
ENABLED.store(on as u8, Relaxed);
if on {
let _ = CAL.get_or_init(|| (Instant::now(), rdtsc()));
STATE.with(|st| {
let mut st = st.borrow_mut();
st.0.truncate(1);
st.1 = rdtsc();
});
}
}
pub fn reset() {
for i in 0..=N {
CYC[i].store(0, Relaxed);
COUNT[i].store(0, Relaxed);
}
if enabled() {
STATE.with(|st| {
let mut st = st.borrow_mut();
st.0.truncate(1);
st.1 = rdtsc();
});
}
}
pub fn snapshot() -> [(f64, u64); N] {
let (t0, c0) = *CAL.get_or_init(|| (Instant::now(), rdtsc()));
let secs = t0.elapsed().as_secs_f64().max(1e-9);
let hz = (rdtsc().wrapping_sub(c0)) as f64 / secs;
let ovh = scope_overhead_cycles();
let mut out = [(0.0f64, 0u64); N];
for (i, o) in out.iter_mut().enumerate() {
let calls = COUNT[i].load(Relaxed);
let cyc = CYC[i]
.load(Relaxed)
.saturating_sub(calls.saturating_mul(ovh));
*o = (cyc as f64 / hz * 1e3, calls);
}
out
}
pub fn name(i: usize) -> &'static str {
NAMES.get(i).copied().unwrap_or("?")
}
fn scope_overhead_cycles() -> u64 {
let mut best = u64::MAX;
for _ in 0..4000 {
let a = rdtsc();
let b = rdtsc();
best = best.min(b.wrapping_sub(a));
}
best
}
#[inline]
fn transition(push: Option<usize>) {
STATE.with(|st| {
let mut st = st.borrow_mut();
let now = rdtsc();
let (stack, last) = &mut *st;
if *last != 0 {
let top = *stack.last().unwrap_or(&IDLE);
CYC[top].fetch_add(now.wrapping_sub(*last), Relaxed);
}
*last = now;
match push {
Some(stage) => {
stack.push(stage);
COUNT[stage].fetch_add(1, Relaxed);
}
None => {
if stack.len() > 1 {
stack.pop();
}
}
}
});
}
pub struct Scope {
live: bool,
}
impl Scope {
#[inline]
pub fn new(stage: S) -> Scope {
if !enabled() {
return Scope { live: false };
}
let _ = CAL.get_or_init(|| (Instant::now(), rdtsc()));
transition(Some(stage as usize));
Scope { live: true }
}
}
impl Drop for Scope {
#[inline]
fn drop(&mut self) {
if self.live {
transition(None);
}
}
}
macro_rules! dprof {
($stage:expr, $body:expr) => {{
let _s = $crate::prof::Scope::new($stage);
$body
}};
}
pub(crate) use dprof;
pub fn dump() {
if !enabled() {
return;
}
let snap = snapshot();
let total: f64 = snap.iter().map(|&(ms, _)| ms).sum::<f64>().max(1e-9);
let calls: u64 = snap.iter().map(|&(_, c)| c).sum();
eprintln!("VP9_DPROF — decoder stage profile (exclusive self-time):");
eprintln!(
" {:16} {:>10} {:>7} {:>14} {:>10}",
"stage", "ms", "pct", "calls", "us/call"
);
for (i, &(ms, c)) in snap.iter().enumerate() {
if i != S::Other as usize && c == 0 {
continue;
}
eprintln!(
" {:16} {:10.3} {:6.2}% {:14} {:10.4}",
NAMES[i],
ms,
100.0 * ms / total,
c,
if c > 0 { ms * 1e3 / c as f64 } else { 0.0 }
);
}
eprintln!(
" {:16} {:10.3} {:6.2}% {:14}",
"TOTAL", total, 100.0, calls
);
}