use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use prism_q::circuit::Circuit;
use prism_q::circuits;
use prism_q::sim::compiled::default_chunk_size;
use prism_q::{
CompiledSampler, HistogramAccumulator, MarginalsAccumulator, PauliExpectationAccumulator,
compile_measurements,
};
static CURRENT: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
struct TrackingAlloc;
unsafe impl GlobalAlloc for TrackingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc(layout) };
if !ptr.is_null() {
let now = CURRENT.fetch_add(layout.size(), Relaxed) + layout.size();
PEAK.fetch_max(now, Relaxed);
}
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
CURRENT.fetch_sub(layout.size(), Relaxed);
unsafe { System.dealloc(ptr, layout) };
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
if !new_ptr.is_null() {
if new_size >= layout.size() {
let now =
CURRENT.fetch_add(new_size - layout.size(), Relaxed) + new_size - layout.size();
PEAK.fetch_max(now, Relaxed);
} else {
CURRENT.fetch_sub(layout.size() - new_size, Relaxed);
}
}
new_ptr
}
}
#[global_allocator]
static ALLOC: TrackingAlloc = TrackingAlloc;
fn reset_peak() -> usize {
let base = CURRENT.load(Relaxed);
PEAK.store(base, Relaxed);
base
}
fn peak() -> usize {
PEAK.load(Relaxed)
}
const SEED: u64 = 0xDEAD_BEEF;
const QUBITS: usize = 100;
const DEFAULT_SHOTS: [usize; 2] = [20_000_000, 100_000_000];
const TOLERANCE: f64 = 1.5;
fn measured(mut c: Circuit) -> Circuit {
let n = c.num_qubits;
c.num_classical_bits = n;
for i in 0..n {
c.add_measure(i, i);
}
c
}
fn mib(bytes: usize) -> String {
format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0))
}
fn profile_family(
name: &str,
num_meas: usize,
mut sampler: CompiledSampler,
shot_counts: &[usize],
mut run: impl FnMut(&mut CompiledSampler, usize) -> f64,
) -> bool {
let m_words = num_meas.div_ceil(64);
let chunk = default_chunk_size(num_meas);
println!(
"\n{name} (chunk_size={chunk} shots, chunk_buf~{})",
mib(chunk * m_words * 8)
);
let mut deltas = Vec::new();
for &shots in shot_counts {
let base = reset_peak();
let checksum = run(&mut sampler, shots);
let delta = peak().saturating_sub(base);
deltas.push(delta);
println!(
" shots={shots:>13} peak_alloc={:>11} checksum={checksum:.4}",
mib(delta)
);
}
let first = *deltas.first().unwrap_or(&0);
let last = *deltas.last().unwrap_or(&0);
let bounded = last as f64 <= first as f64 * TOLERANCE;
println!(
" -> {}",
if bounded {
"bounded"
} else {
"GROWS WITH SHOTS"
}
);
bounded
}
fn main() {
let shot_counts: Vec<usize> = match std::env::args()
.nth(1)
.and_then(|a| a.parse::<usize>().ok())
{
Some(extra) => DEFAULT_SHOTS.iter().copied().chain([extra]).collect(),
None => DEFAULT_SHOTS.to_vec(),
};
let clifford = measured(circuits::clifford_heavy_circuit(QUBITS, 10, SEED));
let ghz = measured(circuits::ghz_circuit(QUBITS));
let mut ok = true;
ok &= profile_family(
"histogram / ghz_100q",
QUBITS,
compile_measurements(&ghz, SEED).unwrap(),
&shot_counts,
|s, shots| {
let mut acc = HistogramAccumulator::new();
s.sample_chunked(shots, &mut acc);
acc.into_counts().len() as f64
},
);
ok &= profile_family(
"marginals / clifford_d10_100q",
QUBITS,
compile_measurements(&clifford, SEED).unwrap(),
&shot_counts,
|s, shots| {
let mut acc = MarginalsAccumulator::new(QUBITS);
s.sample_chunked(shots, &mut acc);
acc.marginals().iter().sum()
},
);
ok &= profile_family(
"expectation / clifford_d10_100q",
QUBITS,
compile_measurements(&clifford, SEED).unwrap(),
&shot_counts,
|s, shots| {
let mut acc =
PauliExpectationAccumulator::new(vec![vec![0, 1], vec![10, 20], vec![50, 99]]);
s.sample_chunked(shots, &mut acc);
acc.expectations().iter().sum()
},
);
if ok {
println!("\nPASS: peak allocation stays bounded by chunk_size across shot counts");
} else {
eprintln!("\nFAIL: peak allocation grew with total shots");
std::process::exit(1);
}
}