using XFEExtension.NetCore.XUnit.Runtime; namespace XFEExtension.NetCore.XUnit.Benchmarking; /// /// 提供基准样本的异常值识别、描述统计、置信区间、预热稳定性和趋势计算。 /// public static class BenchmarkStatisticsCalculator { /// /// 使用 Tukey 上侧围栏标记异常值,并根据清洗后的样本计算 99.9% 置信区间统计。 /// /// 按采集顺序排列的每操作纳秒样本。 /// 判定收敛所允许的最大相对置信区间半宽。 /// 计算后的统计模型,以及与输入样本索引一一对应的异常值标记。 public static (BenchmarkStatistics Statistics, bool[] Outliers) Calculate(IReadOnlyList samples, double maxRelativeError) { if (samples.Count == 0) return (new BenchmarkStatistics(), []); var sorted = samples.Order().ToArray(); var q1 = Percentile(sorted, 0.25); var q3 = Percentile(sorted, 0.75); var upperFence = q3 + 1.5 * (q3 - q1); var outliers = samples.Select(value => value > upperFence).ToArray(); var filtered = samples.Where((_, index) => !outliers[index]).ToArray(); if (filtered.Length < 2) filtered = samples.ToArray(); Array.Sort(filtered); var mean = filtered.Average(); var variance = filtered.Length <= 1 ? 0 : filtered.Sum(value => Math.Pow(value - mean, 2)) / (filtered.Length - 1); var standardDeviation = Math.Sqrt(variance); var criticalValue = CriticalValue999(filtered.Length - 1); var error = filtered.Length <= 1 ? 0 : criticalValue * standardDeviation / Math.Sqrt(filtered.Length); return (new BenchmarkStatistics { MeanNanoseconds = mean, ErrorNanoseconds = error, StandardDeviationNanoseconds = standardDeviation, MedianNanoseconds = Percentile(filtered, 0.50), P95Nanoseconds = Percentile(filtered, 0.95), MinNanoseconds = filtered[0], MaxNanoseconds = filtered[^1], OutlierCount = outliers.Count(static value => value), Converged = filtered.Length >= 2 && (error == 0 || mean > 0 && error / mean <= maxRelativeError) }, outliers); } /// /// 比较最近两个三样本窗口的中位数,判断预热是否达到 1% 以内的稳定窗口。 /// /// 按执行顺序排列的预热样本。 /// 至少存在六个样本且最近两个窗口稳定时为 public static bool IsWarmupStable(IReadOnlyList samples) { if (samples.Count < 6) return false; var previous = Median(samples.Skip(samples.Count - 6).Take(3)); var current = Median(samples.Skip(samples.Count - 3)); if (previous == 0) return current == 0; return Math.Abs(current - previous) / Math.Abs(previous) <= 0.01; } /// /// 通过线性相关性和首尾相对变化检测样本中的明显时间趋势。 /// /// 按采集时间顺序排列的清洗样本。 /// 相关系数绝对值至少为 0.70 且拟合变化至少为均值的 5% 时为 public static bool HasSignificantTrend(IReadOnlyList samples) { if (samples.Count < 8) return false; var meanX = (samples.Count - 1) / 2d; var meanY = samples.Average(); if (meanY == 0) return false; double covariance = 0; double varianceX = 0; double varianceY = 0; for (var index = 0; index < samples.Count; index++) { var deltaX = index - meanX; var deltaY = samples[index] - meanY; covariance += deltaX * deltaY; varianceX += deltaX * deltaX; varianceY += deltaY * deltaY; } if (varianceX == 0 || varianceY == 0) return false; var correlation = covariance / Math.Sqrt(varianceX * varianceY); var slope = covariance / varianceX; var relativeChange = Math.Abs(slope * (samples.Count - 1) / meanY); return Math.Abs(correlation) >= 0.70 && relativeChange >= 0.05; } /// /// 对已经按升序排列的样本执行线性插值百分位数计算。 /// /// 按升序排列的样本。 /// 介于 0 与 1 之间的目标百分位。 /// 插值得到的百分位数;空样本返回 0。 public static double Percentile(IReadOnlyList sortedSamples, double percentile) { if (sortedSamples.Count == 0) return 0; if (sortedSamples.Count == 1) return sortedSamples[0]; var position = (sortedSamples.Count - 1) * percentile; var lower = (int)Math.Floor(position); var upper = (int)Math.Ceiling(position); if (lower == upper) return sortedSamples[lower]; return sortedSamples[lower] + (sortedSamples[upper] - sortedSamples[lower]) * (position - lower); } private static double Median(IEnumerable values) { var sorted = values.Order().ToArray(); return Percentile(sorted, 0.5); } private static double CriticalValue999(int degreesOfFreedom) => degreesOfFreedom switch { <= 1 => 636.62, <= 2 => 31.60, <= 3 => 12.92, <= 4 => 8.61, <= 5 => 6.87, <= 7 => 5.41, <= 10 => 4.59, <= 14 => 4.14, <= 20 => 3.85, <= 30 => 3.65, <= 60 => 3.46, <= 120 => 3.37, _ => 3.291 }; }