Commit ee953f5
perf: one filler instead of thirteen, and eight bytes per draw instead of one (#56)
* perf: one listing instead of two questions a file, and one collection instead of two
Three findings from the performance report, and two of them came back with the
report's own suggestion measured and refused.
preflight asked the filesystem twice for every planned file. It reads the
output directory once. A run of 100 000 files with --dry-run goes from
15.7-19.7 s to 0.49-0.56 s, order alternated - the check was 97% of it. Ten
thousand names measured on their own: 1.937 s of stat calls against 8.9 ms for
one listing.
That is also a fix rather than only a speedup, and the guard for it says so.
os.Stat follows a link, so a link pointing at nothing answered "no name here"
and the run replaced it without a word. A directory ENTRY is what a taken name
is, whatever it points at. With the old question put back, the new guard
reports that the run went ahead over a name somebody else's link was holding.
A directory that cannot be LISTED is still asked about file by file. Both
systems allow write permission without read, a run into such a directory has
always worked, and reading nothing there and calling it empty would let the run
write over what is inside. That fallback has its own guard, which skips on
Windows because denying a listing there needs an ACL.
The plan ceiling forced a collection to take every reading, so a run of one
kilobyte paid for two of them - measured with GODEBUG=gctrace=1, exactly two on
every run however small. It asks /gc/heap/allocs:bytes first, at 251 ns against
519 us, and only collects when that says it might be over. The shortcut is
sound by an inequality rather than by an estimate: the live heap cannot have
grown by more than has been allocated.
The report asked for /gc/heap/live:bytes and that metric is WRONG here. It
reports the heap as of the last collection, and measured on 2026-09-05 all four
existing ceiling guards stay green with it, because 25 MB of allocation makes
the collector run on its own and the lagging reading catches up by luck. With
the collector switched off the luck goes: a plan six times the ceiling is
accepted. The new guard turns the collector off for exactly that reason.
hashFile reads in 256 KiB pieces. The report asked for a 1 MB buffer through
io.CopyBuffer and that does nothing at all: os.File implements io.WriterTo, so
CopyBuffer hands it the whole job and throws the buffer away - 128 KiB, 256 KiB
and 1 MiB with a plain file all take the same 167-172 ms that io.Copy takes.
Hidden behind a reader that offers only Read, 256 KiB takes 161 ms against 199.
The size is measured too: 64 KiB is 182 ms and nothing above a quarter of a
megabyte can be told apart, so sixteen workers cost four megabytes.
planChildren is sized up front, since the total is known from the groups.
TotalBytes being walked twice is NOT done, and that is a measurement rather
than an oversight: one walk over 100 000 planned files has a median of 0 s and
a maximum of 541 us, so two of them cost half a millisecond of a nineteen
second run. Widening a signature for that would be a change nothing can see.
engine.go went past the length ceiling, and the guard asks for a split by what
the parts do rather than for a bigger number - so preflight and the questions
it asks about names are their own file now. Two ceilings came down with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* perf: one filler instead of thirteen, and eight bytes per draw instead of one
Thirteen packages each carried their own copy of the bulk random filler
loop, in four different shapes. Measured over 64 MiB through a 32 KiB
buffer, interleaved with the order reversed between repetitions:
one draw per byte 182 MB/s zip, targz, wav
eight via a temporary array 1468 MB/s bmp, gif, ico, opc, png, tiff
eight via a shift loop 845 MB/s avif, jpg, jxl, webp
one store into the buffer 2499 MB/s the shape they all use now
All thirteen now call core.FillRandomBE or core.FillRandomLE. Two
functions rather than one with a flag, because choosing the wrong byte
order is not a style mistake - it silently rewrites every file a format
has ever produced, and an argument would put that one typo away.
BREAKING: zip, targz and wav files have different bytes. Their padding
is where those formats spend almost the whole file, so almost every byte
changes. Size, structure and readability are untouched. End to end on
64 MB, ranges disjoint: zip 2.81x, targz 2.74-3.32x.
wav is in that list for uniformity and not for speed, by the owner's
decision after the measurement: its padding is audio modulo the frame
size, so exactly two bytes of a WAV differ and the time is unchanged.
The other ten packages moved with no byte change at all - the shift loop
IS little endian, which the performance report did not notice, so those
four collapse to one store for free. Checked across 24 formats at five
sizes and two seeds: three moved, twenty identical.
Also closes a blind spot the golden set had. Forcing the filler to emit a
constant moved 33 of the 54 pinned cases and not one WAV: wav_32kib lands
on a size the audio fills exactly and never reaches the filler, so that
path had no pinned witness. wav_with_the_padding_chunk is that witness.
Guard: TestBulkRandomBytesComeFromOnePlace, two mutations, both caught.
It names the one honest UintN caller as an exception and fails if that
exception outlives its code. A third mutation proves the golden set
covers the shared filler's short tail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>1 parent 479c494 commit ee953f5
18 files changed
Lines changed: 244 additions & 96 deletions
File tree
- internal
- core
- format
- avif
- bmp
- gif
- ico
- jpg
- jxl
- opc
- png
- targz
- tiff
- wav
- webp
- zip
- guard
- testdata
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
17 | 40 | | |
18 | 41 | | |
19 | 42 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
515 | 515 | | |
516 | 516 | | |
517 | 517 | | |
518 | | - | |
| 518 | + | |
519 | 519 | | |
520 | 520 | | |
521 | 521 | | |
522 | 522 | | |
523 | 523 | | |
524 | 524 | | |
525 | 525 | | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
372 | 372 | | |
373 | 373 | | |
374 | 374 | | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
| 375 | + | |
380 | 376 | | |
381 | 377 | | |
382 | 378 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
358 | 358 | | |
359 | 359 | | |
360 | 360 | | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | | - | |
365 | | - | |
| 361 | + | |
366 | 362 | | |
367 | 363 | | |
368 | 364 | | |
| |||
372 | 368 | | |
373 | 369 | | |
374 | 370 | | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | 371 | | |
387 | 372 | | |
388 | 373 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
| 336 | + | |
341 | 337 | | |
342 | 338 | | |
343 | 339 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
543 | 543 | | |
544 | 544 | | |
545 | 545 | | |
546 | | - | |
547 | | - | |
548 | | - | |
549 | | - | |
550 | | - | |
551 | | - | |
552 | | - | |
553 | | - | |
| 546 | + | |
554 | 547 | | |
555 | 548 | | |
556 | 549 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
566 | 566 | | |
567 | 567 | | |
568 | 568 | | |
569 | | - | |
| 569 | + | |
570 | 570 | | |
571 | 571 | | |
572 | 572 | | |
573 | 573 | | |
574 | 574 | | |
575 | 575 | | |
576 | 576 | | |
577 | | - | |
578 | | - | |
579 | | - | |
580 | | - | |
581 | | - | |
582 | | - | |
583 | | - | |
584 | | - | |
585 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | 27 | | |
29 | 28 | | |
30 | 29 | | |
| |||
412 | 411 | | |
413 | 412 | | |
414 | 413 | | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
| 414 | + | |
420 | 415 | | |
421 | 416 | | |
422 | 417 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
470 | 470 | | |
471 | 471 | | |
472 | 472 | | |
473 | | - | |
474 | | - | |
475 | | - | |
476 | | - | |
477 | | - | |
| 473 | + | |
478 | 474 | | |
479 | 475 | | |
480 | 476 | | |
| |||
0 commit comments