Skip to content

Skip the row cache in sorted getindex when few columns are selected - #870

Open
ViralBShah wants to merge 3 commits into
mainfrom
vs/getindex-nocache
Open

ViralBShah wants to merge 3 commits into
mainfrom
vs/getindex-nocache

Conversation

@ViralBShah

@ViralBShah ViralBShah commented Sep 20, 2026

Copy link
Copy Markdown
Member

Fixes #12. A[I, J] with a sorted vector I allocates and sweeps a cache of length size(A, 1) in getindex_I_sorted_bsearch_I and getindex_I_sorted_linear, however little is requested, so a loop over columns costs O(n·m) instead of O(nnz). JuliaLang/julia#42647 addressed the linear kernel but was closed unmerged when the stdlib moved.

julia> A = spdiagm(ones(10^7)); I = collect(1:258);

julia> @time A[I, 1];   # 7 ms, 76 MiB before; 0.2 μs, 304 bytes after

The cache is what makes these kernels fast when many columns are selected (each distinct row is located in I once rather than once per stored entry; removing it outright is 10x+ slower for S[I, :]), so it stays. This adds getindex_I_sorted_nocache, a two-pass count-and-fill kernel with no scratch space that locates each stored row in I by galloping from the previous hit (short brackets finish with a linear scan), so a search costs the log of the distance moved rather than of length(I) and one kernel serves both the bsearch and the linear regime. getindex_I_sorted picks it when 32 * nnzJ * log2(gap) < m, with gap the length of I per stored entry of an average selected column; nnzJ is read off colptr in O(nJ). The factor is set at the cache's best case, every selected column storing the same rows with I = 1:m for m up to 5e7, where one search per distinct row is reused by all columns: just under the gate the cacheless kernel takes 0.4–0.55x the cached time there and 0.03–0.8x on sprand, so it is not slower than the current code on any input I could construct. It builds the same result with no scratch, so memory never goes up. On a 1000x1000 9-point Laplacian, 5-point and 3D 7-point Laplacians, tridiagonal, banded, arrowhead and block-diagonal matrices (single columns, red-black splits, subdomain blocks, random subsets), every call above 1 ms keeps its current kernel and timing, and few-column calls drop from 0.07–0.9 ms to microseconds. The algorithm choice among the three existing kernels is unchanged.

nightly 1.14.0-DEV, M-series before after
diag 1e7: A[[1], 1] 0.64 ms, 76 MiB 0.0001 ms, 304 B
diag 1e7: A[collect(1:258), 1] 7.0 ms, 76 MiB 0.0002 ms, 304 B
diag 1e7: A[collect(1:10^6), collect(1:1000)] 6.9 ms, 76 MiB 0.46 ms, 24 KiB
diag 1e7: A[I, j] for j = 1:1000 3198 ms, 74.5 GiB 0.20 ms, 258 KiB
sprand 1e5, d=1e-4: S[1:2:end, [7]] (vector I) 0.12 ms, 784 KiB 0.02 ms, 352 B
sprand 1e5, d=1e-4: S[1:2:end, :] (vector I, cached path) 14.0 ms, 9.2 MiB 14.3 ms, 9.2 MiB
9-point Laplacian 1e6: A[R, R[k:k+19]], R random N/10 0.67 ms 0.004 ms
9-point Laplacian 1e6: A[r, r], r random N/1000 0.90 ms 0.11 ms

Tests: the new kernel runs in the existing test_getindex_algs grid against dense (stored zeros, duplicates and unsorted I/J), plus an allocation bound on a 10^6-row matrix showing scalar, single and few-column J reach it in both regimes. Full suite passes on nightly.

Not addressed here: when m > nnz(A) the dispatcher picks getindex_I_sorted_bsearch_A, which walks I for every column; with nI = 5e5 and 1e4 columns of a hypersparse 1e6 matrix that is 1.96 s against 0.8 ms for the new kernel. That branch is unchanged and is a follow-up. No dispatch or API change; performance only, so no backport.

Written by Claude Code (Claude Fable 5.1).

🤖 Generated with Claude Code

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.06%. Comparing base (3a77d00) to head (bc7afa9).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #870      +/-   ##
==========================================
- Coverage   93.01%   92.06%   -0.96%     
==========================================
  Files          12       12              
  Lines        9076     9148      +72     
==========================================
- Hits         8442     8422      -20     
- Misses        634      726      +92     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ViralBShah and others added 2 commits September 20, 2026 12:54
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…regimes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ViralBShah

Copy link
Copy Markdown
Member Author

@andreasnoack This is a really old one. Please do chime in (even after merging) with thoughts if any.

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.

Avoid large cache array in getindex_I_sorted_bsearch_I

1 participant