@@ -61,14 +61,14 @@ interface IMeasurement {
6161 sizeBytes ?: number ;
6262}
6363const measurements : IMeasurement [ ] = [ ] ;
64- // Allow specifying iterations via env BENCH_ITERATIONS or command arg --iterations N (jest passes args; we scan process.argv)
64+ // Allow specifying iterations via env BENCH_ITERATIONS. Defaults to 0 to avoid running the benchmark unless explicitly enabled.
6565function detectIterations ( ) : number {
66- let iter = 1 ;
66+ let iter = 0 ;
6767 const envParsed : number = parseInt ( process . env . BENCH_ITERATIONS || '' , 10 ) ;
6868 if ( ! isNaN ( envParsed ) && envParsed > 0 ) {
6969 iter = envParsed ;
7070 }
71- return iter || 5 ;
71+ return iter ;
7272}
7373const ITERATIONS : number = detectIterations ( ) ;
7474
@@ -223,16 +223,16 @@ function benchZipSyncScenario(
223223}
224224
225225// the benchmarks are skipped by default because they require external tools (tar, zip) to be installed
226- describe . skip ( `archive benchmarks (iterations=${ ITERATIONS } )` , ( ) => {
227- it ( 'tar ' , ( ) => {
226+ describe ( `archive benchmarks (iterations=${ ITERATIONS } )` , ( ) => {
227+ it ( 'gtar ' , ( ) => {
228228 if ( ! isTarAvailable ( ) ) {
229229 console . log ( 'Skipping tar test because tar is not available' ) ;
230230 return ;
231231 }
232232 if ( ! tempDir ) throw new Error ( 'Temp directory is not set up.' ) ;
233- bench ( 'tar ' , {
234- pack : ( { archive, demoDir } ) => execSync ( `tar -cf "${ archive } " -C "${ demoDir } " .` ) ,
235- unpack : ( { archive, unpackDir } ) => execSync ( `tar -xf "${ archive } " -C "${ unpackDir } "` ) ,
233+ bench ( 'gtar ' , {
234+ pack : ( { archive, demoDir } ) => execSync ( `gtar -cf "${ archive } " -C "${ demoDir } " .` ) ,
235+ unpack : ( { archive, unpackDir } ) => execSync ( `gtar -xf "${ archive } " -C "${ unpackDir } "` ) ,
236236 archive : path . join ( tempDir , 'archive.tar' ) ,
237237 unpackDir : path . join ( tempDir , 'unpacked-tar' ) ,
238238 populateUnpackDir : 'full' ,
@@ -388,87 +388,80 @@ afterAll(() => {
388388 ]
389389 }
390390 ] ;
391- interface ITableRow {
392- group : string ;
393- isBaseline : boolean ;
394- s : IStats ;
395- deltaMeanPct : number ;
396- }
397- const tableRows : ITableRow [ ] = [ ] ;
398- for ( const g of groupsDef ) {
399- const baselinePack : IStats | undefined = stats . find ( ( s ) => s . kind === g . baseline && s . phase === 'pack' ) ;
400- const baselineUnpack : IStats | undefined = stats . find (
401- ( s ) => s . kind === g . baseline && s . phase === 'unpack'
402- ) ;
403- for ( const member of g . members ) {
404- for ( const phase of [ 'pack' , 'unpack' ] as const ) {
405- const s = stats . find ( ( st ) => st . kind === member && st . phase === phase ) ;
406- if ( ! s ) continue ;
407- const baseline = phase === 'pack' ? baselinePack : baselineUnpack ;
408- const deltaMeanPct = baseline ? ( ( s . mean - baseline . mean ) / baseline . mean ) * 100 : 0 ;
409- tableRows . push ( { group : g . title , isBaseline : member === g . baseline , s, deltaMeanPct } ) ;
391+ // Build per-group markdown tables (no Group column) for each phase
392+ function buildGroupTable (
393+ group : { title : string ; baseline : string ; members : string [ ] } ,
394+ phase : 'pack' | 'unpack'
395+ ) : string [ ] {
396+ // Human readable bytes formatter
397+ function formatBytes ( bytes : number ) : string {
398+ const units = [ 'B' , 'KB' , 'MB' , 'GB' ] ;
399+ let value = bytes ;
400+ let i = 0 ;
401+ while ( value >= 1024 && i < units . length - 1 ) {
402+ value /= 1024 ;
403+ i ++ ;
410404 }
405+ const formatted = value >= 100 ? value . toFixed ( 0 ) : value >= 10 ? value . toFixed ( 1 ) : value . toFixed ( 2 ) ;
406+ return `${ formatted } ${ units [ i ] } ` ;
411407 }
412- }
413-
414- function buildTable ( rowsData : ITableRow [ ] , phaseFilter : 'pack' | 'unpack' ) : string [ ] {
415408 const headers =
416- phaseFilter === 'pack'
417- ? [
418- 'Group' ,
419- 'Archive' ,
420- 'iter' ,
421- 'min(ms)' ,
422- 'mean(ms)' ,
423- 'Δmean%' ,
424- 'p95(ms)' ,
425- 'max(ms)' ,
426- 'std(ms)' ,
427- 'size(bytes)'
428- ]
429- : [ 'Group' , 'Archive' , 'iter' , 'min(ms)' , 'mean(ms)' , 'Δmean%' , 'p95(ms)' , 'max(ms)' , 'std(ms)' ] ;
430- const rows : string [ ] [ ] = [ headers ] ;
431- for ( const row of rowsData . filter ( ( r ) => r . s . phase === phaseFilter ) ) {
432- const baseCols = [
433- row . isBaseline ? row . group : '' ,
434- row . s . kind + ( row . isBaseline ? '*' : '' ) ,
435- String ( row . s . n ) ,
436- row . s . min . toFixed ( 2 ) ,
437- row . s . mean . toFixed ( 2 ) ,
438- ( row . deltaMeanPct >= 0 ? '+' : '' ) + row . deltaMeanPct . toFixed ( 1 ) ,
439- row . s . p95 . toFixed ( 2 ) ,
440- row . s . max . toFixed ( 2 ) ,
441- row . s . std . toFixed ( 2 )
409+ phase === 'pack'
410+ ? [ 'Archive' , 'min (ms)' , 'mean (ms)' , 'p95 (ms)' , 'max (ms)' , 'std (ms)' , 'speed×' , 'size' ]
411+ : [ 'Archive' , 'min (ms)' , 'mean (ms)' , 'p95 (ms)' , 'max (ms)' , 'std (ms)' , 'speed×' ] ;
412+ const lines : string [ ] = [ ] ;
413+ lines . push ( '| ' + headers . join ( ' | ' ) + ' |' ) ;
414+ const align : string [ ] = headers . map ( ( header , idx ) => ( idx === 0 ? '---' : '---:' ) ) ;
415+ lines . push ( '| ' + align . join ( ' | ' ) + ' |' ) ;
416+ const baselineStats : IStats | undefined = stats . find (
417+ ( s ) => s . kind === group . baseline && s . phase === phase
418+ ) ;
419+ for ( const member of group . members ) {
420+ const s : IStats | undefined = stats . find ( ( st ) => st . kind === member && st . phase === phase ) ;
421+ if ( ! s ) continue ;
422+ const isBaseline : boolean = member === group . baseline ;
423+ const speedFactor : number = baselineStats ? baselineStats . mean / s . mean : 1 ;
424+ const cols : string [ ] = [
425+ ( isBaseline ? '**' : '' ) + s . kind + ( isBaseline ? '**' : '' ) ,
426+ s . min . toFixed ( 2 ) ,
427+ s . mean . toFixed ( 2 ) ,
428+ s . p95 . toFixed ( 2 ) ,
429+ s . max . toFixed ( 2 ) ,
430+ s . std . toFixed ( 2 ) ,
431+ speedFactor . toFixed ( 2 ) + 'x'
442432 ] ;
443- if ( phaseFilter === 'pack' ) {
444- baseCols . push ( row . s . sizeMean !== undefined ? Math . round ( row . s . sizeMean ) . toString ( ) : '' ) ;
433+ if ( phase === 'pack' ) {
434+ cols . push ( s . sizeMean !== undefined ? formatBytes ( Math . round ( s . sizeMean ) ) : '' ) ;
445435 }
446- rows . push ( baseCols ) ;
436+ lines . push ( '| ' + cols . join ( ' | ' ) + ' |' ) ;
447437 }
448- const colWidths : number [ ] = headers . map ( ( header , i ) =>
449- rows . reduce ( ( w , r ) => Math . max ( w , r [ i ] . length ) , 0 )
450- ) ;
451- return rows . map ( ( r ) => r . map ( ( c , i ) => c . padStart ( colWidths [ i ] , ' ' ) ) . join ( ' ' ) ) ;
438+ return lines ;
452439 }
453- const packTable : string [ ] = buildTable ( tableRows , 'pack' ) ;
454- const unpackTable : string [ ] = buildTable ( tableRows , 'unpack' ) ;
455440 const outputLines : string [ ] = [ ] ;
456- outputLines . push ( '\nBenchmark Results (iterations=' + ITERATIONS + '):' ) ;
457- outputLines . push ( 'PACK PHASE:' ) ;
458- outputLines . push ( packTable [ 0 ] ) ;
459- outputLines . push ( '-' . repeat ( packTable [ 0 ] . length ) ) ;
460- for ( let i = 1 ; i < packTable . length ; i ++ ) outputLines . push ( packTable [ i ] ) ;
461- outputLines . push ( '* baseline (pack)' ) ;
441+ outputLines . push ( '# Benchmark Results' ) ;
442+ outputLines . push ( '' ) ;
443+ outputLines . push (
444+ 'This document contains performance measurements for packing and unpacking a synthetic dataset using traditional archive tools (tar, zip) and various zipsync modes. The dataset consists of two directory trees (subdir1, subdir2) populated with text files. Each scenario was executed multiple iterations; metrics shown are aggregated timing statistics. The speed× column shows how many times faster a scenario is compared to the baseline in that group (values >1 = faster, <1 = slower). Baseline rows are shown in bold.'
445+ ) ;
446+ outputLines . push ( '' ) ;
447+ outputLines . push ( `Iterations: ${ ITERATIONS } ` ) ;
462448 outputLines . push ( '' ) ;
463- outputLines . push ( 'UNPACK PHASE:' ) ;
464- outputLines . push ( unpackTable [ 0 ] ) ;
465- outputLines . push ( '-' . repeat ( unpackTable [ 0 ] . length ) ) ;
466- for ( let i = 1 ; i < unpackTable . length ; i ++ ) outputLines . push ( unpackTable [ i ] ) ;
467- outputLines . push ( '* baseline (unpack)' ) ;
449+ for ( const g of groupsDef ) {
450+ outputLines . push ( `## ${ g . title } ` ) ;
451+ outputLines . push ( '' ) ;
452+ outputLines . push ( '### Pack Phase' ) ;
453+ outputLines . push ( '' ) ;
454+ outputLines . push ( ...buildGroupTable ( g , 'pack' ) ) ;
455+ outputLines . push ( '' ) ;
456+ outputLines . push ( '### Unpack Phase' ) ;
457+ outputLines . push ( '' ) ;
458+ outputLines . push ( ...buildGroupTable ( g , 'unpack' ) ) ;
459+ outputLines . push ( '' ) ;
460+ }
468461 const resultText = outputLines . join ( '\n' ) ;
469462 console . log ( resultText ) ;
470463 try {
471- const resultFile = path . join ( __dirname , '..' , 'temp' , `benchmark-results.txt ` ) ;
464+ const resultFile = path . join ( __dirname , '..' , 'temp' , `benchmark-results.md ` ) ;
472465 fs . writeFileSync ( resultFile , resultText , { encoding : 'utf-8' } ) ;
473466 console . log ( `Benchmark results written to: ${ resultFile } ` ) ;
474467 } catch ( e ) {
@@ -488,7 +481,7 @@ function isZipAvailable(): boolean {
488481}
489482function isTarAvailable ( ) : boolean {
490483 try {
491- const checkTar = process . platform === 'win32' ? 'where tar ' : 'command -v tar ' ;
484+ const checkTar = process . platform === 'win32' ? 'where gtar ' : 'command -v gtar ' ;
492485 execSync ( checkTar , { stdio : 'ignore' } ) ;
493486 return true ;
494487 } catch {
0 commit comments