///| Small dependency-free descriptive statistics used by reproducible decoder
///| experiments. The implementation favours transparent formulas over a
///| heavyweight numerical dependency because benchmark samples are normally
///|
/// short request runs rather than millions of observations.
pub enum StatisticsError {
EmptySample
InvalidQuantile(Double)
} derive(Eq, Debug)
///|
/// A one-pass accumulator using Welford's numerically stable variance update.
pub struct OnlineMoments {
mut count : Int
mut mean : Double
mut squared_deviation_sum : Double
mut minimum : Double
mut maximum : Double
}
///|
pub fn OnlineMoments::empty() -> OnlineMoments {
{
count: 0,
mean: 0.0,
squared_deviation_sum: 0.0,
minimum: 0.0,
maximum: 0.0,
}
}
///|
/// Add one observed value without retaining raw experiment records.
pub fn OnlineMoments::add(self : OnlineMoments, value : Double) -> Unit {
if self.count == 0 {
self.count = 1
self.mean = value
self.minimum = value
self.maximum = value
return
}
self.count = self.count + 1
let delta = value - self.mean
self.mean = self.mean + delta / self.count.to_double()
let after = value - self.mean
self.squared_deviation_sum = self.squared_deviation_sum + delta * after
if value < self.minimum {
self.minimum = value
}
if value > self.maximum {
self.maximum = value
}
}
///|
pub fn OnlineMoments::count(self : OnlineMoments) -> Int {
self.count
}
///|
pub fn OnlineMoments::mean(
self : OnlineMoments,
) -> Result[Double, StatisticsError] {
if self.count == 0 {
Err(EmptySample)
} else {
Ok(self.mean)
}
}
///| Population variance is appropriate for a complete fixed replay suite.
///|
/// Use the unbiased form below when the suite is a sample from production.
pub fn OnlineMoments::population_variance(
self : OnlineMoments,
) -> Result[Double, StatisticsError] {
if self.count == 0 {
Err(EmptySample)
} else {
Ok(self.squared_deviation_sum / self.count.to_double())
}
}
///|
pub fn OnlineMoments::sample_variance(
self : OnlineMoments,
) -> Result[Double, StatisticsError] {
if self.count < 2 {
Err(EmptySample)
} else {
Ok(self.squared_deviation_sum / (self.count - 1).to_double())
}
}
///|
pub fn OnlineMoments::minimum(
self : OnlineMoments,
) -> Result[Double, StatisticsError] {
if self.count == 0 {
Err(EmptySample)
} else {
Ok(self.minimum)
}
}
///|
pub fn OnlineMoments::maximum(
self : OnlineMoments,
) -> Result[Double, StatisticsError] {
if self.count == 0 {
Err(EmptySample)
} else {
Ok(self.maximum)
}
}
///| Copy and insertion-sort a small sample. This deliberately leaves caller
///| ordering untouched, which matters when the same array represents a time
///|
/// trace in a benchmark report.
pub fn sorted_copy(values : Array[Double]) -> Array[Double] {
let sorted : Array[Double] = []
for value in values {
let mut inserted = false
for position in 0.. Result[Double, StatisticsError] {
if values.length() == 0 {
return Err(EmptySample)
}
if quantile < 0.0 || quantile > 1.0 {
return Err(InvalidQuantile(quantile))
}
let sorted = sorted_copy(values)
if sorted.length() == 1 {
return Ok(sorted[0])
}
let location = quantile * (sorted.length() - 1).to_double()
let lower = location.to_int()
let upper = if lower + 1 < sorted.length() { lower + 1 } else { lower }
let fraction = location - lower.to_double()
Ok(sorted[lower] + (sorted[upper] - sorted[lower]) * fraction)
}
///|
/// A compact immutable summary convenient for serializing experiment results.
pub struct SampleSummary {
count : Int
minimum : Double
maximum : Double
mean : Double
population_variance : Double
median : Double
p90 : Double
}
///|
pub fn summarize_sample(
values : Array[Double],
) -> Result[SampleSummary, StatisticsError] {
if values.length() == 0 {
return Err(EmptySample)
}
let moments = OnlineMoments::empty()
for value in values {
moments.add(value)
}
let minimum = match moments.minimum() {
Ok(value) => value
Err(error) => return Err(error)
}
let maximum = match moments.maximum() {
Ok(value) => value
Err(error) => return Err(error)
}
let mean = match moments.mean() {
Ok(value) => value
Err(error) => return Err(error)
}
let population_variance = match moments.population_variance() {
Ok(value) => value
Err(error) => return Err(error)
}
let median = match percentile(values, 0.5) {
Ok(value) => value
Err(error) => return Err(error)
}
let p90 = match percentile(values, 0.9) {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({
count: values.length(),
minimum,
maximum,
mean,
population_variance,
median,
p90,
})
}
///|
pub fn SampleSummary::render(self : SampleSummary, label : String) -> String {
label +
" count=" +
self.count.to_string() +
" min=" +
self.minimum.to_string() +
" mean=" +
self.mean.to_string() +
" median=" +
self.median.to_string() +
" p90=" +
self.p90.to_string() +
" max=" +
self.maximum.to_string()
}