use std::sync::atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering};
use std::sync::OnceLock;
use std::time::Instant;
const MAX_THREADS: usize = 128;
fn slots() -> &'static [AtomicU64] {
static SLOTS: OnceLock<Box<[AtomicU64]>> = OnceLock::new();
SLOTS.get_or_init(|| (0..MAX_THREADS).map(|_| AtomicU64::new(0)).collect())
}
thread_local! {
static SLOT: std::cell::Cell<usize> = const { std::cell::Cell::new(usize::MAX) };
static IN_POOL: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
static NEXT: AtomicUsize = AtomicUsize::new(0);
static NTHREADS: AtomicUsize = AtomicUsize::new(1);
static FLAG: AtomicU8 = AtomicU8::new(0);
pub(crate) fn enabled() -> bool {
match FLAG.load(Ordering::Relaxed) {
1 => false,
2 => true,
_ => {
let on = std::env::var_os("LINKCELL_POP").is_some();
let _ = FLAG.compare_exchange(
0,
if on { 2 } else { 1 },
Ordering::Relaxed,
Ordering::Relaxed,
);
FLAG.load(Ordering::Relaxed) == 2
}
}
}
pub fn engage() {
FLAG.store(2, Ordering::Relaxed);
}
pub fn efficiencies(useful: &[u64], wall_ns: u64) -> (f64, f64, f64) {
if useful.is_empty() {
return (0.0, 0.0, 0.0);
}
let sum: u64 = useful.iter().copied().sum();
let max = useful.iter().copied().max().unwrap_or(0);
let avg = sum as f64 / useful.len() as f64;
let lb = if max == 0 { 0.0 } else { avg / max as f64 };
let ce = if wall_ns == 0 {
0.0
} else {
max as f64 / wall_ns as f64
};
(lb, ce, lb * ce)
}
fn slot() -> usize {
SLOT.with(|cell| {
let cur = cell.get();
if cur != usize::MAX {
return cur;
}
let id = NEXT.fetch_add(1, Ordering::Relaxed);
cell.set(id);
id
})
}
fn worker_slot() -> usize {
let in_pool = IN_POOL.with(|cell| cell.get());
if in_pool {
slot()
} else {
0
}
}
pub fn prepare() {
if !enabled() {
return;
}
#[cfg(feature = "parallel")]
{
rayon::broadcast(|_| {
IN_POOL.with(|cell| cell.set(true));
let _ = slot();
});
NTHREADS.store(rayon::current_num_threads(), Ordering::Relaxed);
}
#[cfg(not(feature = "parallel"))]
{
let _ = slot();
NTHREADS.store(1, Ordering::Relaxed);
}
}
pub fn reset() {
if !enabled() {
return;
}
for entry in slots() {
entry.store(0, Ordering::Relaxed);
}
}
fn add_slot(slot_id: usize, ns: u64) {
if slot_id < slots().len() {
slots()[slot_id].fetch_add(ns, Ordering::Relaxed);
}
}
pub fn snapshot() -> Vec<u64> {
if !enabled() {
return Vec::new();
}
let n = NTHREADS.load(Ordering::Relaxed).clamp(1, MAX_THREADS);
(0..n).map(|i| slots()[i].load(Ordering::Relaxed)).collect()
}
pub(crate) struct JobTimer {
start: Option<Instant>,
slot: usize,
}
impl JobTimer {
pub(crate) fn new() -> Self {
if !enabled() {
return Self {
start: None,
slot: 0,
};
}
Self {
start: Some(Instant::now()),
slot: worker_slot(),
}
}
}
impl Drop for JobTimer {
fn drop(&mut self) {
if let Some(start) = self.start.take() {
add_slot(self.slot, start.elapsed().as_nanos() as u64);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn efficiencies_match_pop3() {
let (lb, ce, pe) = efficiencies(&[2, 4], 8);
assert!((lb - 0.75).abs() < 1e-12);
assert!((ce - 0.5).abs() < 1e-12);
assert!((pe - 0.375).abs() < 1e-12);
}
}