@@ -31,8 +31,19 @@ function holmAdjust(pValues) {
3131 return adjusted ;
3232}
3333
34+ function thresholdPValue ( oldRates , newHistogram , scale , maxRegression ) {
35+ const factor = 1 - maxRegression / 100 ;
36+ if ( factor <= 0 ) return 1 ;
37+ const thresholdHistogram = createRateHistogram (
38+ oldRates . map ( ( rate ) => rate * factor ) , scale , 3 ) ;
39+ const result = thresholdHistogram . welchTest ( newHistogram ) ;
40+ if ( Number . isNaN ( result . pValue ) ) return 1 ;
41+ return result . tStatistic > 0 ?
42+ result . pValue / 2 : 1 - result . pValue / 2 ;
43+ }
44+
3445function isRegressionFailure ( row , maxRegression ) {
35- return row . pAdjusted < 0.05 &&
46+ return row . pThresholdAdjusted < 0.05 &&
3647 row . improvement + row . ci95 < - maxRegression ;
3748}
3849
@@ -80,22 +91,32 @@ function analyzeCompare(samples, scale, maxRegression) {
8091 result . confidenceInterval . lower ) / 2 ;
8192 return ( half / ( oldMean * scale ) ) * 100 ;
8293 } ;
83- rows . push ( {
94+ const row = {
8495 ci95 : ciPercent ( w95 ) ,
8596 ci99 : ciPercent ( w99 ) ,
8697 ci999 : ciPercent ( w999 ) ,
8798 improvement,
8899 name,
89100 pValue : Number . isNaN ( w95 . pValue ) ? 1 : w95 . pValue ,
90101 stars,
91- } ) ;
102+ } ;
103+ if ( maxRegression !== undefined ) {
104+ row . pThreshold = thresholdPValue (
105+ oldRates , newHistogram , scale , maxRegression ) ;
106+ }
107+ rows . push ( row ) ;
92108 }
93109
94110 const adjusted = holmAdjust ( rows . map ( ( { pValue } ) => pValue ) ) ;
111+ const thresholdAdjusted = maxRegression === undefined ? null :
112+ holmAdjust ( rows . map ( ( { pThreshold } ) => pThreshold ) ) ;
95113 let underpowered = 0 ;
96114 for ( let index = 0 ; index < rows . length ; index ++ ) {
97115 const row = rows [ index ] ;
98116 row . pAdjusted = adjusted [ index ] ;
117+ if ( thresholdAdjusted !== null ) {
118+ row . pThresholdAdjusted = thresholdAdjusted [ index ] ;
119+ }
99120 row . inconclusive = maxRegression > 0 &&
100121 row . stars . trim ( ) === '' &&
101122 row . ci95 > maxRegression ;
@@ -146,8 +167,13 @@ function analyzeCompare(samples, scale, maxRegression) {
146167 `After Holm-Bonferroni correction across ${ rows . length } comparison` +
147168 `${ rows . length === 1 ? '' : 's' } , ${ significant } remain` +
148169 `${ significant === 1 ? 's' : '' } significant at 5%.` ,
149- '--max-regression uses the corrected values.' ,
150170 ) ;
171+ if ( maxRegression !== undefined ) {
172+ output . push (
173+ `For --max-regression, one-sided p-values against the ` +
174+ `${ maxRegression } % threshold were corrected separately.` ,
175+ ) ;
176+ }
151177
152178 if ( maxRegression > 0 && underpowered > 0 ) {
153179 output . push ( '' ) ;
@@ -159,21 +185,22 @@ function analyzeCompare(samples, scale, maxRegression) {
159185 ) ;
160186 }
161187
162- const failures = maxRegression > 0 ?
188+ const failures = maxRegression !== undefined ?
163189 rows . filter ( ( row ) => isRegressionFailure ( row , maxRegression ) ) : [ ] ;
164190 if ( failures . length > 0 ) {
165191 output . push ( '' ) ;
166192 output . push (
167193 `FAIL: ${ failures . length } benchmark${ failures . length === 1 ? '' : 's' } ` +
168194 ` regressed by more than ${ maxRegression } % (the 95% interval excludes ` +
169- `the threshold and significance is family-wise corrected across ` +
195+ `the threshold and its one-sided test is family-wise corrected across ` +
170196 `${ rows . length } comparisons):` ,
171197 ) ;
172198 for ( const failure of failures ) {
173199 output . push (
174200 ` ${ failure . name } ${ failure . improvement . toFixed ( 2 ) } % ` +
175201 `(95% CI up to ${ ( failure . improvement + failure . ci95 ) . toFixed ( 2 ) } %, ` +
176- `adjusted p=${ failure . pAdjusted . toExponential ( 2 ) } )` ,
202+ `adjusted threshold p=` +
203+ `${ failure . pThresholdAdjusted . toExponential ( 2 ) } )` ,
177204 ) ;
178205 }
179206 }
0 commit comments