Fixes for low hanging performance "bugs" (AI driven) - #358
Open
KristofferC wants to merge 8 commits into
Open
Conversation
Recursive lexicographic refinement allocated a fresh merge-sort scratch vector for every subgroup. Thread one workspace through the recursion instead. MWE: benchmark/sortperm_recursive.jl (100,000 rows, 6 Float64 columns, cardinality 16; Julia 1.12.6, minimum of 20 samples). Before: 12.874 ms, 2.87 MiB, 34,473 allocations After: 12.340 ms, 1.62 MiB, 11 allocations
Integer-key refinement allocated and filled a new histogram for every subgroup. Keep one histogram alongside the merge-sort scratch vector and resize/fill it in place. MWE: benchmark/sortperm_recursive.jl (100,000 rows, 6 Int columns, cardinality 16; Julia 1.12.6, minimum of 40 samples; baseline is the preceding commit). Before: 2.356 ms, 2.06 MiB, 17,504 allocations After: 2.206 ms, 960.53 KiB, 12 allocations
The generic Tables fallback pushed rows one at a time without reserving component capacity. Apply sizehint! when the iterator reports a length, while retaining the existing row-wise conversion path. MWE: benchmark/append_rows.jl (1,000,000 NamedTuple rows, 4 columns; Julia 1.12.6, minimum of 10 samples). Before: 9.555 ms, 115.14 MiB, 120 allocations After: 8.034 ms, 30.56 MiB, 8 allocations
Shape validation mapped axes over every component before reducing the results. For homogeneous concrete component tuples, validate in a type-stable loop and return early on a mismatch.
MWE: benchmark/wide_constructor.jl (128 Vector{Float64} columns; Julia 1.12.6, minimum BenchmarkTools estimate).
Before: 1.983 us, 1.23 KiB, 3 allocations
After: 56.233 ns, 0 bytes, 0 allocations
Wide homogeneous component tuples already encode their shared array type. Derive the row tuple type directly instead of mapping eltype and splatting a large temporary tuple; retain the fallback for abstract and heterogeneous components.
MWE: benchmark/wide_constructor.jl (inferred 128-column Vector{Float64} StructArray; Julia 1.12.6, minimum BenchmarkTools estimate; baseline is the preceding commit).
Before: 4.244 us, 4.61 KiB, 13 allocations
After: 56.233 ns, 0 bytes, 0 allocations
Recursive Tuple-tail comparison crosses Julia's specialization limit for wide StructArrays and boxes each comparison. Compare homogeneous concrete component tuples with a type-stable loop while retaining recursive dispatch for heterogeneous tuples. MWE: benchmark/group_wide.jl (100,000 rows, 64 Int columns, cardinality 16; Julia 1.12.6, minimum of 10 samples). Before: 365.092 ms, 355.32 MiB, 698,520 allocations After: 728.416 us, 0 bytes, 0 allocations
Scalar getindex already materializes a tuple of component values. Tuple and NamedTuple row types can construct directly from it, avoiding the specialization cliff from a wide vararg splat while preserving their conversions. MWE: benchmark/wide_getindex.jl (128 Float64 columns, 100 rows; Julia 1.12.6, minimum BenchmarkTools estimate). Before: 2.509 us, 4.16 KiB, 131 allocations After: 54.795 ns, 0 bytes, 0 allocations
NamedTuple component collections hit the same wide-tuple specialization limit during axes validation. Above that limit, use the encoded homogeneous value-tuple type to select the allocation-free loop; retain the unrolled path for narrow static arrays and the generic fallback for heterogeneous or abstract fields. MWE: benchmark/wide_constructor.jl (inferred 128-column named StructArray; Julia 1.12.6, minimum BenchmarkTools estimate). Before: 3.869 us, 3.36 KiB, 5 allocations After: 79.459 ns, 0 bytes, 0 allocations
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.
This PR consists of a set of commits, each quite small, that each has a significant impact on either allocations or runtime. Each commits has a corresponding benchmark added and shows the change in performance. A table of the impact of each commit is shown below. Note that this PR was mostly generated via AI but is still opened in the hope that it nonetheless is useful.