Skip to content

Descriptive Statistics

Descriptive statistics

Given a sorted, trimmed array of n samples:

Mean

x¯=1ni=1nxi

Median

The mid-average convention: the middle value for odd n, and the mean of the two middle order statistics for even n. This matches numpy.median and the median NBenchmark uses elsewhere (per-launch aggregation, jitter calibration), so the reported Median and the P50 percentile agree. Every other percentile uses the nearest-rank method (see below); the median is the sole exception, because the mid-average removes the small systematic downward bias nearest-rank has on even n.

Percentiles

Configurable percentile values computed via the nearest-rank method: i = ceil(p × n). The median (p = 0.50) is the one exception - it mid-averages the two middles on even n (see Median above); Q1/Q3 and every other percentile stay nearest-rank. Controlled by MeasurementOptions.ReportedPercentiles (default: P50, P95, P99, P99.9, Max). Each entry is a PercentileEntry with a Percentile (0-1) and Value (nanoseconds). Access a specific percentile with result.GetPercentile(0.95).

Tail metrics are computed from the full pre-trim distribution by default. Percentiles, Min, Max, and the histogram describe the shape of the distribution, so they are computed from the raw (pre-trim) sample set - MeasurementOptions.TailMetricsBasis = Raw, the default. This keeps them honest: the IQR/MAD fence removes exactly the slow tail that P99/P99.9/Max exist to describe, so a GC pause the Realistic profile deliberately timed appears in Max rather than being trimmed out of it. Central-tendency and dispersion statistics (mean, standard deviation, CI, CV, skewness, kurtosis, MAD, median, median CI) always stay on the trimmed set, so a fenced-out spike never moves the mean or inflates the interval. Set TailMetricsBasis = Trimmed (or --tail-basis trimmed) to compute tail metrics from the inlier set instead.

Percentiles describe samples, and a sample may be a batch

When ops-per-sample calibration resolves K > 1 (the norm for sub-10 µs bodies), each sample is the mean of K back-to-back operations. Percentiles, Min, Max, and the histogram are therefore over batch means, not individual operations - a single slow op is averaged with its K-1 neighbours, so the tail percentiles understate true per-operation tail latency. This is the deliberate cost of amortising timer overhead on fast bodies. When you need genuine per-op tail latency, pin OpsPerSample = 1 (accepting that at that scale the reported values are dominated by timer resolution and read overhead - compare against a baseline measured the same way). Bodies that already span ≥ AutoTune.TargetSampleDurationNs (10 µs) keep K = 1, so their percentiles are already per-operation.

Min and Max

samples[0] and samples[n-1] of the sorted tail source (the full pre-trim set by default; see the tail-metrics note above).

Sample standard deviation (Bessel's correction)

s=1n1i=1n(xix¯)2

The n-1 denominator (Bessel's correction) makes s an unbiased estimator of the population standard deviation. For n = 1, the standard deviation is reported as 0.

Standard error of the mean

SEM=sn

SEM measures how precisely the mean is estimated. For n = 1, SEM is 0.

Confidence interval on the mean

The margin of error is the half-width of the confidence interval:

MoE=tα/2,n1×SEM

where tα/2,n1 is the two-tailed critical value of Student's t-distribution at the configured confidence level and n − 1 degrees of freedom.

The confidence interval is:

x¯±MoE=[x¯MoE,x¯+MoE]

Why Student's t and not the normal distribution?

The normal distribution's critical value (e.g. 1.96 for 95%) assumes the population standard deviation is known. In benchmarking it is not - we estimate it from the sample. Student's t compensates by using wider critical values for small sample sizes, shrinking towards the normal as n grows.

With a typical auto-resolved sample count (tens to low hundreds), the t critical value at 95% sits around 1.97-1.98 - very close to the normal 1.960, so the practical difference is small.

Honest caveats

The CI is on the mean and relies on the Central Limit Theorem - the assumption that the sample mean is approximately normally distributed. For n ≥ 30 this is generally safe even when the underlying distribution is not normal. For very small sample counts (e.g. a parameterised benchmark with 10 iterations) the approximation is weaker, but the t-distribution's heavier tails at low degrees of freedom provide some protection.

t-critical values in practice

Confidence leveln = 10 (df=9)n = 30 (df=29)n = 200 (df=199)Normal (df=∞)
90%1.8331.6991.6521.645
95%2.2622.0451.9721.960
99%3.2502.7562.6012.576

Dependency-free implementation

NBenchmark computes the t critical value without any external libraries using exact closed forms for df = 1 and df = 2, and the Cornish-Fisher expansion (Abramowitz & Stegun §26.7.5) for df ≥ 3. The normal quantile uses Acklam's rational approximation (max error < 1.15 × 10⁻⁹).

These approximations are cross-checked against SciPy on every build: the t critical value matches scipy.stats.t.ppf to machine precision for df = 1, 2 and to better than 1% for df ≥ 3 (worst case ≈ 0.79% at df = 3, 99%). See Validation & Accuracy for the full tolerance table.

Confidence interval on the median

The t-interval above is on the mean, but the median is NBenchmark's headline comparison metric (ratios and the Mann-Whitney semantics both key off it). MedianCiLower/MedianCiUpper report a distribution-free confidence interval on the median built from order statistics - no normality assumption.

For n < 50 the rank bounds are exact, from the binomial(n, ½) distribution: the interval [X(l), X(u)] (1-based order statistics) covers the median with probability 1 − 2·CDF(l−1), and l is the largest rank whose lower-tail mass does not exceed α/2. This can only be conservative (coverage ≥ the requested level). For n ≥ 50 the normal approximation to the binomial gives l = ⌊(n − z√n)/2⌋, u = ⌈1 + (n + z√n)/2⌉ with z = Φ⁻¹((1+CL)/2). Ranks are clamped into [1, n]; when even the widest interval cannot reach the requested level (tiny n, high CL) the full range is returned.

The interval is computed on the same (trimmed) set as Median. It appears in the advanced-detail stats block and is always present in JSON.

Coefficient of variation

CV=sx¯

A dimensionless relative measure of variability. A CV of 0.05 means the standard deviation is 5% of the mean - the benchmark is fairly stable. A CV of 0.5 or higher indicates high variability and the results should be treated with caution.

Distribution shape

Three fields describe the shape of the sample distribution, not just its central tendency or spread.

Skewness

g1=n(xix¯)3(n1)(n2)s3
  • Positive skew (right-tailed): a few slow outliers pull the mean above the median. Common in benchmarks where scheduler preemption or GC adds occasional spikes.
  • Negative skew (left-tailed): most samples are slow and a few are fast - unusual in benchmarking, but can appear after compiler warmup where early iterations are slower.
  • Near zero: roughly symmetric distribution.

Skewness is reported as 0 when n < 3.

Kurtosis (excess)

g2=n(n+1)(xix¯)4(n1)(n2)(n3)s43(n1)2(n2)(n3)

This is excess kurtosis (kurtosis minus 3), so the normal distribution benchmarks at 0.

  • Positive excess kurtosis (leptokurtic): heavier tails than a normal distribution. More extreme outliers than expected under normality. A benchmark with occasional GC pauses or page faults often shows this.
  • Negative excess kurtosis (platykurtic): lighter tails, fewer extremes. Rare in benchmarking; can occur when samples are tightly clamped by hardware limits.
  • Near zero: tail weight similar to a normal distribution.

Excess kurtosis is reported as 0 when n < 4.

Median absolute deviation (MAD, scaled)

MAD=median(|ximedian(x)|)×1.4826

MAD is a robust measure of spread - it uses the median rather than the mean, so it is far less sensitive to outliers than the standard deviation. The scaling factor 1.4826 makes MAD consistent with the standard deviation σ for normally distributed data, which means the two can be compared directly: if MAD is noticeably smaller than the standard deviation, outliers are inflating the standard deviation more than the bulk of the data warrant.

Reported as 0 when `n < 1$.

Summary of all reported fields

Core fields on BenchmarkResult

FieldFormula / methodDescription
MedianMid-average P50 (mean of the two middles on even n)Robust central tendency.
Meanx¯=1nxiArithmetic average.
PercentilesIReadOnlyList<PercentileEntry>Configurable percentile values. Default set includes P50 (0.50), P95 (0.95), P99 (0.99), P99.9 (0.999), Max (1.0). Controlled by MeasurementOptions.ReportedPercentiles. Access via GetPercentile(p).
HistogramLatencyHistogram?Latency histogram with bucket boundaries and sample counts. null when EnableHistogram is false or fewer than 2 samples.
Minx1 (sorted)Fastest measured sample.
Maxxn (sorted)Slowest measured sample.
Q1Nearest-rank P25First quartile.
Q3Nearest-rank P75Third quartile.
InterquartileRangeQ3 - Q1Spread of the middle 50% of samples.
LowerFenceDetector-dependentLower outlier boundary; set only by fence-based detectors. IqrFence: Q1k×IQR (default k=1.5). MedianAbsoluteDeviation: mt×scaledMAD (default t=3). null otherwise.
UpperFenceDetector-dependentUpper outlier boundary; set only by fence-based detectors. IqrFence: Q3+k×IQR (default k=1.5). MedianAbsoluteDeviation: m+t×scaledMAD (default t=3). null otherwise.
OutliersRemovedCount of discarded samplesNumber of samples removed by outlier trimming.
NPost-trim lengthSample count after outlier removal.
StandardDeviations=1n1(xix¯)2Spread of measurements (Bessel).
StandardErrors/nPrecision of the mean estimate.
MarginOfErrort×SEMHalf-width of CI on the mean.
ConfidenceIntervalLowerx¯MoELower CI bound.
ConfidenceIntervalUpperx¯+MoEUpper CI bound.
MedianCiLower / MedianCiUpperOrder-statistic intervalDistribution-free confidence interval on the median (exact binomial for n<50, normal approximation above). null when undefined (n<2, dry-run, errored).
MedianShiftHodges-Lehmann + Lehmann CILocation shift vs. baseline (median of pairwise candidate − baseline differences) with a rank-based interval, in ns/op. Positive = candidate slower. null for the baseline or when significance did not run.
CoefficientOfVariations/x¯Relative variability.
Skewnessg1=n(xix¯)3(n1)(n2)s3Sample skewness. Zero for n<3.
Kurtosisg2=n(n+1)(xix¯)4(n1)(n2)(n3)s43(n1)2(n2)(n3)Excess kurtosis. Zero for n<4.
Madmedian(|ximedian(x)|)×1.4826Median absolute deviation (scaled to σ). Zero for n<1.
PValueMann-Whitney UTwo-tailed pairwise p-value vs. baseline. null for the omnibus case (three or more groups - see Omnibus).
SignificanceVerdictp<αWhether the pairwise difference is real (Significant, NotSignificant, or NotTested).
OmnibusKruskal-WallisThe across-all-groups verdict when three or more benchmarks are compared; null otherwise. Holds TestName, Statistic, DegreesOfFreedom, GroupCount, PValue, and Verdict.
SignificanceTestName-Display name of the pairwise significance test used (e.g. "Mann-Whitney U").
OutlierDetector-Display name of the outlier detector applied (e.g. "IQR fence (1.5×)" or "MAD (3×)").
MeanAllocatedBytesMean of iteration deltasMean heap allocation per iteration.
AllocMedianMid-average P50 of iteration deltasMedian allocation per iteration (only when MeasureAllocations = true).
AllocP95Nearest-rank P95 of iteration deltasP95 allocation per iteration (only when MeasureAllocations = true).
AllocMaxMax of iteration deltasMax allocation per iteration (only when MeasureAllocations = true).

Throughput fields

FieldFormulaDescription
OperationsPerSecond1e9 / Mean when timing is in nanosecondsMean operations per second. NaN for errored or dry-run results.
MedianOperationsPerSecond1e9 / Median when timing is in nanosecondsMedian operations per second. NaN for errored or dry-run results.
NanosecondsPerOperationAlias for MeanConvenience alias that expresses the mean timing as nanoseconds per operation.
TotalOperationsMeasuredIterations + WarmupIterations, or AutoTuneDiagnostic.TotalBodyInvocations when auto-tuningTotal body invocations executed across warmup and measurement.

Computed properties

PropertyFormulaDescription
RangeMax - MinFull spread of trimmed samples.
StandardErrorPercentSEM/x¯×100Standard error as a percentage of the mean.
MarginPercentMoE/x¯×100Margin of error as a percentage of the mean.
CoefficientOfVariationPercentCV×100Coefficient of variation as a percentage.

Released under the MIT License.