Skip to content

perf(linalg): fewer allocations per factorization (first step of #104) - #116

Merged
tannevaled merged 2 commits into
mainfrom
perf/linalg-allocs
Nov 12, 2026
Merged

tannevaled merged 2 commits into
mainfrom
perf/linalg-allocs

Conversation

@tannevaled

@tannevaled tannevaled commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

First step on #104: two allocation sources that every blocked factorization hits once per panel.

  • GEMM on one worker (internal/kernels/gemm.go). With GOMAXPROCS = 1 (or m = 1), matMul now takes the serial packGemmRows path. It no longer builds the block list, the progress counters and the worker closure, which cost four allocations per call. That path is the one MatMul and 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.
  • The 9.5 MB linalg/linalg.test binary committed with feat(linalg): numpy.linalg in pure Go #46 is deleted, and *.test is ignored.

Allocation counts, 64×64, from go test -run TestPerf -perf.record with GOMAXPROCS=1 (exact under any load; docs/perf.json and perf-table.md are updated):

row before after
linalg.Lstsq / LstsqRcond 276 250
linalg.SVD 265 241
linalg.Pinv / PinvRcond 255 230
linalg.Eigh 166 154
linalg.QR 93 81
linalg.Eig 89 80
linalg.Solve 40 37
linalg.Inv 30 27
linalg.Slogdet 29 28
linalg.Det 25 24
linalg.Cholesky 20 19
Array.MatMul / Array.Dot 128² 13 8

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.

TestGemmOneWorkerSameBits pins the new branch. On random (non-integer) data above GemmThreshold, 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 on stress_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²:

Refs #104

🤖 Generated with Claude Code

tannevaled and others added 2 commits November 12, 2026 21:56
…fastGemm temporary (#104)

Also deletes the 9.5 MB linalg/linalg.test committed with #46 and ignores *.test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he w == 1 branch)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit d8ed921 into main Nov 12, 2026
13 checks passed
@tannevaled
tannevaled deleted the perf/linalg-allocs branch November 12, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant