Skip to content

Commit c2f9ad7

Browse files
committed
ZJIT: Expose bucket counts on DistributionSummary
Profile summaries only exposed the bucket items, so consumers could not tell whether the profiled buckets covered most of the observed executions or just a sliver of them. Keep the per-bucket counts and the `other` count on the summary and add num_seen()/bucket_count()/coverage() so guard-chain construction can decide whether a chain over a subset of the buckets is worth building. No behavior change on its own.
1 parent 2d26d5b commit c2f9ad7

1 file changed

Lines changed: 65 additions & 2 deletions

File tree

zjit/src/distribution.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,18 @@ enum DistributionKind {
109109
pub struct DistributionSummary<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> {
110110
kind: DistributionKind,
111111
buckets: [T; N],
112+
/// How many samples landed in each bucket, in the same order as `buckets`
113+
counts: [NumProfiles; N],
114+
/// How many samples did not fit in any bucket
115+
other: NumProfiles,
112116
// TODO(max): Determine if we need some notion of stability
113117
}
114118

115119
const SKEW_THRESHOLD: f64 = 0.75;
116120

117121
impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> DistributionSummary<T, N> {
118122
pub fn empty() -> Self {
119-
Self { kind: DistributionKind::Empty, buckets: [Default::default(); N] }
123+
Self { kind: DistributionKind::Empty, buckets: [Default::default(); N], counts: [0; N], other: 0 }
120124
}
121125

122126
pub fn new(dist: &Distribution<T, N>) -> Self {
@@ -147,7 +151,7 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
147151
DistributionKind::Megamorphic
148152
}
149153
};
150-
Self { kind, buckets: dist.buckets }
154+
Self { kind, buckets: dist.buckets, counts: dist.counts, other: dist.other }
151155
}
152156

153157
pub fn is_monomorphic(&self) -> bool {
@@ -178,6 +182,31 @@ impl<T: Copy + PartialEq + Default + std::fmt::Debug, const N: usize> Distributi
178182
pub fn buckets(&self) -> &[T] {
179183
&self.buckets
180184
}
185+
186+
/// How many samples landed in `buckets[idx]`. 0 means the bucket is unused.
187+
pub fn bucket_count(&self, idx: usize) -> NumProfiles {
188+
assert!(idx < N, "index {idx} out of bounds for buckets[{N}]");
189+
self.counts[idx]
190+
}
191+
192+
/// Total number of samples this summary was built from, including the ones that did not fit
193+
/// in a bucket.
194+
pub fn num_seen(&self) -> u32 {
195+
self.counts.iter().map(|&c| u32::from(c)).sum::<u32>() + u32::from(self.other)
196+
}
197+
198+
/// Fraction of observed samples that landed in the buckets for which `keep` returns true.
199+
/// Used to decide whether a guard chain over those buckets is worth building: samples in
200+
/// other buckets, and samples that did not fit in any bucket, have to take the fallback.
201+
pub fn coverage(&self, keep: impl Fn(usize, T) -> bool) -> f64 {
202+
let num_seen = self.num_seen();
203+
if num_seen == 0 { return 0.0; }
204+
let covered: u32 = self.counts.iter().enumerate()
205+
.filter(|&(idx, &count)| count > 0 && keep(idx, self.buckets[idx]))
206+
.map(|(_, &count)| u32::from(count))
207+
.sum();
208+
(covered as f64) / (num_seen as f64)
209+
}
181210
}
182211

183212
#[cfg(test)]
@@ -365,4 +394,38 @@ mod distribution_tests {
365394
assert_eq!(summary.kind, DistributionKind::SkewedMegamorphic);
366395
assert_eq!(summary.buckets[0], 12);
367396
}
397+
398+
#[test]
399+
fn summary_exposes_counts_and_num_seen() {
400+
let mut dist = Distribution::<usize, 2>::new();
401+
dist.observe(10);
402+
dist.observe(10);
403+
dist.observe(11);
404+
dist.observe(12); // does not fit; counted in other
405+
let summary = DistributionSummary::new(&dist);
406+
assert_eq!(summary.bucket_count(0), 2);
407+
assert_eq!(summary.bucket_count(1), 1);
408+
assert_eq!(summary.num_seen(), 4);
409+
}
410+
411+
#[test]
412+
fn coverage_counts_only_kept_buckets() {
413+
let mut dist = Distribution::<usize, 2>::new();
414+
dist.observe(10);
415+
dist.observe(10);
416+
dist.observe(11);
417+
dist.observe(12); // does not fit; counted in other
418+
let summary = DistributionSummary::new(&dist);
419+
assert_eq!(summary.coverage(|_, _| true), 0.75);
420+
assert_eq!(summary.coverage(|idx, _| idx == 0), 0.5);
421+
assert_eq!(summary.coverage(|_, item| item == 11), 0.25);
422+
assert_eq!(summary.coverage(|_, _| false), 0.0);
423+
}
424+
425+
#[test]
426+
fn empty_summary_has_no_coverage() {
427+
let summary = DistributionSummary::<usize, 4>::empty();
428+
assert_eq!(summary.num_seen(), 0);
429+
assert_eq!(summary.coverage(|_, _| true), 0.0);
430+
}
368431
}

0 commit comments

Comments
 (0)