perf: verify and cleanup hash over every core, and the race gate learns to notice - #54
Merged
Merged
Conversation
…ns to notice Hashing the files a manifest claims is what verify and cleanup are made of, and it is embarrassingly parallel. It ran on one goroutine. Measured on 6.1 GB in 96 files of 64 MB, binary before and after, order alternated: tfg verify goes from 4280-4303 ms to 536-556 ms, a spread under one percent either side. The hashing on its own scales 1.92x, 3.56x, 6.63x and 9.33x at 2, 4, 8 and 16 workers, so the plateau sits at the hardware thread count and the width asks GOMAXPROCS rather than carrying a constant. On a corpus larger than memory, where the disk is the limit, it is 1.58x. O116 turned this down on 2026-08-20 and the number it turned down was real: on 3000 files of 1 kB the whole of verify is about a second, so half a second was all there was to win. That reading describes small files. The owner reopened the decision with the measurement above in hand. Nothing about what the two commands report changes. Both binaries produce byte-identical output on a directory holding a missing file, a wrong size, a wrong hash and a stranger - JSON and plain, stdout and stderr, same exit code. What makes the answers deterministic is that the workers cannot fail. Every path is resolved first, on one goroutine, in order, so the first entry that leaves the directory is the first one there is - if that refusal could arrive from a worker, stopping the others would mean a lower index never got asked and the same manifest would name a different file on different days. Two guards, both proven by mutation: - the answers come back in the order the manifest lists them. cleanup PRINTS that list to a person and then deletes from it, and a list assembled out of completion order holds the same files - so every other cleanup guard stays green while somebody reads one order and the tool acts in another. - the race detector is run for every file that declares concurrency. The workflow claimed the two lists could not drift because the map guard reddens first. It does, but only while the file is missing from the map, and adding it there is what that guard tells you to do. Walking into it is how it was found. The depth ceiling asked for the worker loop to be flattened rather than raised, so the body of the goroutine is its own function. Two crowding ceilings came down, because moving the per file work out of Verify took one function under sixty lines and one out of the complexity band. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hashing the files a manifest claims is what
verifyandcleanupare made of, and it is embarrassingly parallel. It ran on one goroutine.Measured
tfg verifyon 6.1 GB in 96 files of 64 MB, binary before and after, order alternated:7.9x, with a spread under one percent either side. The hashing on its own scales 1.92x / 3.56x / 6.63x / 9.33x at 2 / 4 / 8 / 16 workers, so the plateau sits at the hardware thread count and the width asks
GOMAXPROCSrather than carrying a constant. On a corpus larger than memory, where the disk is the limit, it is 1.58x.O116turned this down on 2026-08-20 and the number it turned down was real: on 3000 files of 1 kB the whole of verify is about a second, so half a second was all there was to win. That reading describes small files. The owner reopened the decision with the measurement above in hand.Nothing about the answers changes
Both binaries produce byte-identical output on a directory holding a missing file, a wrong size, a wrong hash and a stranger - JSON and plain, stdout and stderr, same exit code 7.
What makes the answers deterministic is that the workers cannot fail. Every path is resolved first, on one goroutine, in order, so the first entry that leaves the directory is the first one there is. If that refusal could arrive from a worker, stopping the others would mean a lower index never got asked and the same manifest would name a different file on different days.
Two guards, both proven by mutation
cleanupprints that list to a person and then deletes from it, and a list assembled out of completion order holds the same files - so every other cleanup guard stays green while somebody reads one order and the tool acts in another.Checks
Full suite green,
preflight --quick12 of 12,-raceclean on 501 files with sixteen workers, five mutations all caught, three stale mutation patterns repointed after the refactor.The depth ceiling asked for the worker loop to be flattened rather than raised, so the body of the goroutine is its own function. Two crowding ceilings came down.
.github/workflows/**, sogh pr mergewill refuse - this one needs the browser.🤖 Generated with Claude Code