perf: CSR-flatten L/U factors for cache locality and fewer allocs#24
Open
andig wants to merge 2 commits into
Open
perf: CSR-flatten L/U factors for cache locality and fewer allocs#24andig wants to merge 2 commits into
andig wants to merge 2 commits into
Conversation
Both the Forrest-Tomlin factor (ft.go) and the default sparse LU (sparselu.go) stored L (and U, for sparseLU) as array-of-slices with per-step append growth: one heap allocation per elimination step, and pointer-chased, scattered memory in the solve loops. Flatten the immutable parts into CSR-style contiguous arrays keyed by a per-step offset table: - ft.go: L-file -> lFR/lFV/lPtr. L is immutable between refactorizations (replaceColumn never touches it), so this is safe. forwardLR/btran now stream contiguous; clone copies 3 flat arrays instead of 2*m sub-slices. clone -32% time (112us->76us), -597 allocs (1460->863) at m=300. - sparselu.go: L and U -> lFlat*/uFlat*/lPtr/uPtr. sparseLU is immutable after build (eta updates are external; fields read only in this file), so solve/solveT just stream flat. luFactorize allocations on case 020: 1.72M -> 337k (5.1x fewer); total solve allocations 2.47M -> 1.22M. Case 020 wall-clock is unchanged (~7.3s): it is compute-bound on the LU arithmetic, not GC-bound. The win is halved allocation count -> lower GC pressure and pause jitter on the serving path. All golden cases and simplex tests pass. Benchmarks: add BenchmarkLUFactorizeSynth (self-contained synthetic kernel; guards the per-factorize alloc count without external gob data) and BenchmarkFTReplaceReal/BenchmarkFTClone (engine-cadence FT costs, unlike BenchmarkFTReplace which lets the R-file grow unbounded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Summary
Both factorizations stored the L-file (and U, for the default sparse LU) as an array-of-slices with per-step
appendgrowth — one heap allocation per elimination step, and pointer-chased, scattered memory in the solve loops. This flattens the immutable parts into CSR-style contiguous arrays keyed by a per-step offset table.Changes
simplex/ft.go— Forrest-Tomlin L-file →lFR/lFV/lPtrL is immutable between refactorizations (
replaceColumnnever touches it), so flattening is safe.forwardLR/btrannow stream contiguous memory;clonecopies 3 flat arrays instead of 2×m sub-slices.clone: −32% time (112µs → 76µs), −597 allocs (1460 → 863) at m=300.simplex/sparselu.go— default LU L and U →lFlat*/uFlat*/lPtr/uPtrsparseLUis immutable after build (eta updates are external; these fields are read only within this file), sosolve/solveTjust stream the flat arrays.luFactorizeallocations on case 020: 1.72M → 337k (5.1× fewer).Performance note
Case 020 wall-clock is unchanged (~7.3s) — it is compute-bound on the LU arithmetic, not GC-bound. The win is the halved allocation count → lower GC pressure and pause jitter on the serving path (
madviseCPU 0.40s → 0.30s).Benchmarks added
BenchmarkLUFactorizeSynth— self-contained synthetic kernel (factor + solve + solveT); guards the per-factorize allocation count without external gob data.BenchmarkFTReplaceReal/BenchmarkFTClone— engine-cadence FT costs.BenchmarkFTReplaceRealrefactorizes everymaxEtaslike the engine, unlike the existingBenchmarkFTReplacewhich lets the R-file grow unbounded (unrepresentative 41× slower).All golden cases and simplex tests pass.
🤖 Generated with Claude Code