@@ -20,7 +20,7 @@ import {
2020 measureWithContentDetection ,
2121} from "../../src/utils/measure" ;
2222import { createNormalizedLogo } from "../../src/utils/normalize" ;
23- import { fmtNs , fmtP , type TTestResult , welchTTest } from "./welch" ;
23+ import { fmtNs , fmtP , welchTTest } from "./welch" ;
2424
2525const origCreateElement = document . createElement . bind ( document ) ;
2626document . createElement = ( ( tag : string , options ?: ElementCreationOptions ) => {
@@ -64,14 +64,21 @@ const svgFiles = readdirSync(LOGOS_DIR)
6464 . filter ( ( f ) => f . endsWith ( ".svg" ) )
6565 . sort ( ) ;
6666
67- console . log ( `Loading ${ svgFiles . length } real SVGs from static/logos/…` ) ;
67+ const QUICK = ! ! ( process . env . BENCH_QUICK || process . env . CI ) ;
6868
69- const allLogos : RenderedLogo [ ] = [ ] ;
70- for ( const file of svgFiles ) {
71- const buf = Buffer . from ( await Bun . file ( join ( LOGOS_DIR , file ) ) . arrayBuffer ( ) ) ;
72- const { img, width, height } = await loadSvgAtWidth ( buf , 400 ) ;
73- allLogos . push ( { name : file . replace ( / \. s v g $ / , "" ) , img, width, height } ) ;
74- }
69+ console . log (
70+ `Loading ${ svgFiles . length } real SVGs from static/logos/…${ QUICK ? " (quick mode)" : "" } ` ,
71+ ) ;
72+
73+ const allLogos : RenderedLogo [ ] = await Promise . all (
74+ svgFiles . map ( async ( file ) => {
75+ const buf = Buffer . from (
76+ await Bun . file ( join ( LOGOS_DIR , file ) ) . arrayBuffer ( ) ,
77+ ) ;
78+ const { img, width, height } = await loadSvgAtWidth ( buf , 400 ) ;
79+ return { name : file . replace ( / \. s v g $ / , "" ) , img, width, height } ;
80+ } ) ,
81+ ) ;
7582
7683const byPixels = [ ...allLogos ] . sort (
7784 ( a , b ) => a . width * a . height - b . width * b . height ,
@@ -104,10 +111,10 @@ const normalized20 = Array.from({ length: 20 }, (_, i) => {
104111
105112const logos20 = allLogos . slice ( 0 , 20 ) ;
106113
107- const TIME_BUDGET_MS = 2_000 ;
108- const MIN_SAMPLES = 30 ;
114+ const TIME_BUDGET_MS = QUICK ? 800 : 2_000 ;
115+ const MIN_SAMPLES = QUICK ? 20 : 30 ;
109116const MAX_SAMPLES = 2_000 ;
110- const WARMUP_MS = 200 ;
117+ const WARMUP_MS = QUICK ? 80 : 200 ;
111118
112119function collectSamples ( fn : ( ) => void ) : number [ ] {
113120 const warmupEnd = Bun . nanoseconds ( ) + WARMUP_MS * 1e6 ;
@@ -188,92 +195,13 @@ const benchMount20Baseline = () => {
188195 }
189196} ;
190197
191- const benchReNormalize20 = ( ) => {
192- for ( let i = 0 ; i < 20 ; i ++ ) {
193- const idx = i % allLogos . length ;
194- const logo = createNormalizedLogo (
195- sources [ idx ] ! ,
196- realMeasurements [ idx ] ! ,
197- DEFAULT_BASE_SIZE ,
198- DEFAULT_SCALE_FACTOR ,
199- DEFAULT_DENSITY_FACTOR ,
200- ) ;
201- blackhole ( getVisualCenterTransform ( logo , DEFAULT_ALIGN_BY ) ) ;
202- }
203- } ;
204-
205198const keyBenchmarks : Record < string , ( ) => void > = {
206199 "content detection (1 logo)" : benchMeasure ,
207200 "render pass (20 logos)" : benchGetVCT20 ,
208201 "mount 20 logos (no detection)" : benchMount20Baseline ,
209202 "mount 20 logos (defaults)" : benchMount20 ,
210203} ;
211204
212- interface ABComparison {
213- name : string ;
214- a : { label : string ; fn : ( ) => void } ;
215- b : { label : string ; fn : ( ) => void } ;
216- }
217-
218- const medianMeasurement = realMeasurements [ allLogos . indexOf ( medianLogo ) ] ! ;
219-
220- const abComparisons : ABComparison [ ] = [
221- {
222- name : "densityAware: true vs false" ,
223- a : {
224- label : "true" ,
225- fn : benchMeasure ,
226- } ,
227- b : {
228- label : "false" ,
229- fn : ( ) =>
230- measureWithContentDetection (
231- medianLogo . img ,
232- DEFAULT_CONTRAST_THRESHOLD ,
233- false ,
234- ) ,
235- } ,
236- } ,
237- {
238- name : "alignBy: visual-center-y vs bounds" ,
239- a : { label : "visual-center-y" , fn : benchGetVCT20 } ,
240- b : {
241- label : "bounds" ,
242- fn : ( ) => {
243- for ( let i = 0 ; i < 20 ; i ++ )
244- getVisualCenterTransform ( normalized20 [ i ] ! , "bounds" ) ;
245- } ,
246- } ,
247- } ,
248- {
249- name : "cropToContent: true vs false" ,
250- a : {
251- label : "true" ,
252- fn : ( ) => {
253- if ( medianMeasurement . contentBox )
254- blackhole (
255- cropToDataUrl ( medianLogo . img , medianMeasurement . contentBox ) ,
256- ) ;
257- } ,
258- } ,
259- b : {
260- label : "false (noop)" ,
261- fn : ( ) => { } ,
262- } ,
263- } ,
264- {
265- name : "layout update: full mount vs cached" ,
266- a : {
267- label : "full mount" ,
268- fn : benchMount20 ,
269- } ,
270- b : {
271- label : "cached re-normalize" ,
272- fn : benchReNormalize20 ,
273- } ,
274- } ,
275- ] ;
276-
277205console . log ( ) ;
278206console . log ( "─" . repeat ( 72 ) ) ;
279207console . log ( " KEY BENCHMARKS" ) ;
@@ -293,77 +221,130 @@ for (const [name, fn] of Object.entries(keyBenchmarks)) {
293221 ) ;
294222}
295223
296- console . log ( ) ;
297- console . log ( "─" . repeat ( 72 ) ) ;
298- console . log ( " FEATURE COMPARISONS (Welch's t-test, two-tailed)" ) ;
299- console . log ( " Legend: * p<0.05 ** p<0.01 *** p<0.001" ) ;
300- console . log ( "─" . repeat ( 72 ) ) ;
301-
302- interface ABResult {
303- name : string ;
304- aLabel : string ;
305- bLabel : string ;
306- aMean : string ;
307- bMean : string ;
308- pctChange : number ;
309- result : TTestResult ;
310- }
311-
312- const abResults : ABResult [ ] = [ ] ;
313-
314- for ( const comp of abComparisons ) {
315- const samplesA = collectSamples ( comp . a . fn ) ;
316- const samplesB = collectSamples ( comp . b . fn ) ;
317- const result = welchTTest ( samplesA , samplesB ) ;
318- const sA = stats ( samplesA ) ;
319- const sB = stats ( samplesB ) ;
320- const pctChange = sB . mean !== 0 ? ( ( sA . mean - sB . mean ) / sB . mean ) * 100 : 0 ;
321-
322- const sig = result . significant ? `YES ${ result . marker } ` : "NO" ;
323- const sign = pctChange > 0 ? "+" : "" ;
324- console . log ( ` > ${ comp . name } ` ) ;
325- console . log (
326- ` ${ comp . a . label } : ${ fmtNs ( sA . mean ) } vs ${ comp . b . label } : ${ fmtNs ( sB . mean ) } (${ sign } ${ pctChange . toFixed ( 1 ) } %)` ,
327- ) ;
328- console . log ( ` p=${ fmtP ( result . p ) } significant=${ sig } ` ) ;
329- console . log ( ) ;
330-
331- abResults . push ( {
332- name : comp . name ,
333- aLabel : comp . a . label ,
334- bLabel : comp . b . label ,
335- aMean : fmtNs ( sA . mean ) ,
336- bMean : fmtNs ( sB . mean ) ,
337- pctChange,
338- result,
339- } ) ;
340- }
341-
342224const md : string [ ] = [
343225 "## react-logo-soup Benchmark Report" ,
344226 "" ,
345227 `Test fixtures: ${ allLogos . length } real SVGs from static/logos/. ` +
346228 `${ TIME_BUDGET_MS } ms budget per bench, ${ MIN_SAMPLES } -${ MAX_SAMPLES } samples.` ,
347229 "" ,
348- "### Feature Comparisons (Welch's t-test)" ,
349- "" ,
350- "| Test | A | B | Delta | Sig |" ,
351- "|:-----|--:|--:|------:|:----|" ,
230+ "| Benchmark | Mean | ± Stddev | Samples |" ,
231+ "|:----------|-----:|---------:|--------:|" ,
352232] ;
353233
354- for ( const r of abResults ) {
355- const sig = r . result . significant ? `YES ${ r . result . marker } ` : "NO" ;
356- const sign = r . pctChange > 0 ? "+" : "" ;
234+ for ( const [ name , samples ] of Object . entries ( benchSamples ) ) {
235+ const s = stats ( samples ) ;
357236 md . push (
358- `| ${ r . name } | ${ r . aMean } | ${ r . bMean } | ${ sign } ${ r . pctChange . toFixed ( 1 ) } % | ${ fmtP ( r . result . p ) } ${ sig } |` ,
237+ `| ${ name } | ${ fmtNs ( s . mean ) } | ${ fmtNs ( s . stddev ) } | ${ samples . length } |` ,
359238 ) ;
360239}
361240
362241md . push ( "" ) ;
363- md . push (
364- "A/B columns match the order in the test name. Sig: `*` p<0.05, `**` p<0.01, `***` p<0.001." ,
365- ) ;
366- md . push ( "" ) ;
242+
243+ if ( ! QUICK ) {
244+ const medianMeasurement = realMeasurements [ allLogos . indexOf ( medianLogo ) ] ! ;
245+
246+ const benchReNormalize20 = ( ) => {
247+ for ( let i = 0 ; i < 20 ; i ++ ) {
248+ const idx = i % allLogos . length ;
249+ const logo = createNormalizedLogo (
250+ sources [ idx ] ! ,
251+ realMeasurements [ idx ] ! ,
252+ DEFAULT_BASE_SIZE ,
253+ DEFAULT_SCALE_FACTOR ,
254+ DEFAULT_DENSITY_FACTOR ,
255+ ) ;
256+ blackhole ( getVisualCenterTransform ( logo , DEFAULT_ALIGN_BY ) ) ;
257+ }
258+ } ;
259+
260+ const abComparisons = [
261+ {
262+ name : "densityAware: true vs false" ,
263+ a : { label : "true" , fn : benchMeasure } ,
264+ b : {
265+ label : "false" ,
266+ fn : ( ) =>
267+ measureWithContentDetection (
268+ medianLogo . img ,
269+ DEFAULT_CONTRAST_THRESHOLD ,
270+ false ,
271+ ) ,
272+ } ,
273+ } ,
274+ {
275+ name : "alignBy: visual-center-y vs bounds" ,
276+ a : { label : "visual-center-y" , fn : benchGetVCT20 } ,
277+ b : {
278+ label : "bounds" ,
279+ fn : ( ) => {
280+ for ( let i = 0 ; i < 20 ; i ++ )
281+ getVisualCenterTransform ( normalized20 [ i ] ! , "bounds" ) ;
282+ } ,
283+ } ,
284+ } ,
285+ {
286+ name : "cropToContent: true vs false" ,
287+ a : {
288+ label : "true" ,
289+ fn : ( ) => {
290+ if ( medianMeasurement . contentBox )
291+ blackhole (
292+ cropToDataUrl ( medianLogo . img , medianMeasurement . contentBox ) ,
293+ ) ;
294+ } ,
295+ } ,
296+ b : { label : "false (noop)" , fn : ( ) => { } } ,
297+ } ,
298+ {
299+ name : "layout update: full mount vs cached" ,
300+ a : { label : "full mount" , fn : benchMount20 } ,
301+ b : { label : "cached re-normalize" , fn : benchReNormalize20 } ,
302+ } ,
303+ ] ;
304+
305+ console . log ( ) ;
306+ console . log ( "─" . repeat ( 72 ) ) ;
307+ console . log ( " FEATURE COMPARISONS (Welch's t-test, two-tailed)" ) ;
308+ console . log ( " Legend: * p<0.05 ** p<0.01 *** p<0.001" ) ;
309+ console . log ( "─" . repeat ( 72 ) ) ;
310+
311+ md . push (
312+ "### Feature Comparisons (Welch's t-test)" ,
313+ "" ,
314+ "| Test | A | B | Delta | Sig |" ,
315+ "|:-----|--:|--:|------:|:----|" ,
316+ ) ;
317+
318+ for ( const comp of abComparisons ) {
319+ const samplesA = collectSamples ( comp . a . fn ) ;
320+ const samplesB = collectSamples ( comp . b . fn ) ;
321+ const result = welchTTest ( samplesA , samplesB ) ;
322+ const sA = stats ( samplesA ) ;
323+ const sB = stats ( samplesB ) ;
324+ const pctChange = sB . mean !== 0 ? ( ( sA . mean - sB . mean ) / sB . mean ) * 100 : 0 ;
325+
326+ const sig = result . significant ? `YES ${ result . marker } ` : "NO" ;
327+ const sign = pctChange > 0 ? "+" : "" ;
328+ console . log ( ` > ${ comp . name } ` ) ;
329+ console . log (
330+ ` ${ comp . a . label } : ${ fmtNs ( sA . mean ) } vs ${ comp . b . label } : ${ fmtNs ( sB . mean ) } (${ sign } ${ pctChange . toFixed ( 1 ) } %)` ,
331+ ) ;
332+ console . log ( ` p=${ fmtP ( result . p ) } significant=${ sig } ` ) ;
333+ console . log ( ) ;
334+
335+ md . push (
336+ `| ${ comp . name } | ${ fmtNs ( sA . mean ) } | ${ fmtNs ( sB . mean ) } | ${ sign } ${ pctChange . toFixed ( 1 ) } % | ${ fmtP ( result . p ) } ${ sig } |` ,
337+ ) ;
338+ }
339+
340+ md . push (
341+ "" ,
342+ "A/B columns match the order in the test name. Sig: `*` p<0.05, `**` p<0.01, `***` p<0.001." ,
343+ "" ,
344+ ) ;
345+ } else {
346+ console . log ( "\n Skipping AB comparisons in quick mode." ) ;
347+ }
367348
368349const outDir = process . env . BENCH_OUT_DIR ?? "tmp" ;
369350mkdirSync ( outDir , { recursive : true } ) ;
0 commit comments