Repository navigation
perf(linalg): fewer allocations per factorization (first step of #104) - #116
Merged
Merged
Conversation
…he w == 1 branch) Co-Authored-By: Claude Opus 5.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.
First step on #104: two allocation sources that every blocked factorization hits once per panel.
internal/kernels/gemm.go). With GOMAXPROCS = 1 (or m = 1),matMulnow takes the serialpackGemmRowspath. It no longer builds the block list, the progress counters and the worker closure, which cost four allocations per call. That path is the oneMatMuland the small-product branch already use, and the results are unchanged: the kernels, linalg and root suites pass.fastGemm's temporary (linalg/blas.go). The m·n float64 buffer comes from a pool (linalg/scratch.go) and is zeroed on reuse. Buffers above 8 MiB are not kept, so one huge product does not pin its temporary.linalg/linalg.testbinary committed with feat(linalg): numpy.linalg in pure Go #46 is deleted, and*.testis ignored.Allocation counts, 64×64, from
go test -run TestPerf -perf.recordwith GOMAXPROCS=1 (exact under any load;docs/perf.jsonandperf-table.mdare updated):linalg.Lstsq/LstsqRcondlinalg.SVDlinalg.Pinv/PinvRcondlinalg.Eighlinalg.QRlinalg.Eiglinalg.Solvelinalg.Invlinalg.Slogdetlinalg.Detlinalg.CholeskyArray.MatMul/Array.Dot128²These counts are for one worker, which is how the harness documents its record mode (
GOMAXPROCS=1 go test -run TestPerf -perf.record);perf.json's "16 CPUs" is the machine. With several workers the parallel path is unchanged and still costs its four allocations per GEMM call.TestGemmOneWorkerSameBitspins the new branch. On random (non-integer) data aboveGemmThreshold, in float64 and float32, it requires GOMAXPROCS=1 to give the same bits as GOMAXPROCS=4, including m = 1. Coverage of the branch therefore no longer depends onstress_test.go's random GOMAXPROCS.Timing is deferred: the load here is above 8.
What is left, from a memory profile of SVD, QR, Eigh and Eig at 64²:
bdMerge(linalg/bdc.go, about 12 buffers) anddcMerge(linalg/dc.go, about 7). Removing them needs one workspace passed down the recursion, as LAPACK's dbdsdc/dstedc do.newBlockReflectorandapplyLeftallocate per panel.wrapcopies every result throughndarray.FromSlice. A zero-copy constructor would avoid it (Refs Wrap: an array over a caller's buffer with given strides (zero-copy, for Julia column-major arrays) #76).Refs #104
🤖 Generated with Claude Code