Skip to content

Skip the dense copy of the sparse arguments in a sparse-plus-scalar broadcast such as A .+ 1 - #1105

Open
ViralBShah wants to merge 1 commit into
mainfrom
vs/densesum-scatter
Open

ViralBShah wants to merge 1 commit into
mainfrom
vs/densesum-scatter

Conversation

@ViralBShah

Copy link
Copy Markdown
Member

Since #1004, a broadcast sum of a sparse array and a scalar, such as A .+ 1, is dense. It gets there by copying every sparse argument into a dense array, then broadcasting over the copies into a second dense array. For a sparse argument with few stored entries, that copy is half the memory and more than half the time.

Before (main; random n×n sparse A, B of density d; minimum time over 20 runs)

n, d A .+ 1 A .+ B .- 2
1000, 0.001 2.11 ms, 15.3 MiB 3.67 ms, 22.9 MiB
1000, 0.01 2.20 ms, 15.3 MiB 3.65 ms, 22.9 MiB
5000, 0.001 34.3 ms, 381.5 MiB 60.5 ms, 572.2 MiB
5000, 0.01 34.8 ms, 381.5 MiB 62.4 ms, 572.2 MiB
2000, 0.03 6.04 ms, 61.0 MiB 9.62 ms, 91.6 MiB
2000, 0.5 7.42 ms, 61.0 MiB 12.4 ms, 91.6 MiB

After

n, d A .+ 1 A .+ B .- 2
1000, 0.001 0.87 ms, 7.6 MiB 0.96 ms, 7.6 MiB
1000, 0.01 1.05 ms, 7.6 MiB 3.67 ms, 22.9 MiB
5000, 0.001 14.6 ms, 190.7 MiB 16.2 ms, 190.7 MiB
5000, 0.01 22.2 ms, 190.7 MiB 61.9 ms, 572.2 MiB
2000, 0.03 3.85 ms, 30.5 MiB 9.51 ms, 91.6 MiB
2000, 0.5 7.45 ms, 61.0 MiB 12.3 ms, 91.6 MiB

Sums with a dense array, such as A .+ M and v .+ M, are unchanged.

The new path evaluates the broadcast once with each sparse argument replaced by its implicit zero. That fills every entry of the result that no stored entry reaches. It then evaluates the broadcast again only at the entries a stored entry is broadcast to. The function is a tree of + and -, so evaluating it twice at an entry is unobservable. Results match dense broadcast in type and isequal, including NaN and -0.0. I checked that against dense on 36 expressions over Float64, ComplexF64, Bool and Int entries, with sparse vectors and 1-row and 1-column matrices broadcast across the result.

Each re-evaluated entry looks up every sparse argument. So the new path runs only while the entries reached, times the number of sparse arguments, stay below 1/32 of the result; otherwise the dense copy is cheaper. Above that density, as in the 0.5 row and for A .+ B .- 2 at 0.01, the old path runs.

Left out:

  • Sums with a dense array argument. Measured near the cutoff, the copy was not their main cost, and the new path was slower.
  • Adjoints, transposes and views of sparse arrays, which are still copied.
  • Element types that are not Number. Their implicit zero can have another type, which would change the result type.

🤖 Generated with Claude Code

…roadcast such as `A .+ 1`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.77%. Comparing base (a49c313) to head (4d1cdae).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/higherorderfns.jl 84.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1105      +/-   ##
==========================================
- Coverage   94.83%   94.77%   -0.06%     
==========================================
  Files          16       16              
  Lines       10252    10300      +48     
==========================================
+ Hits         9722     9762      +40     
- Misses        530      538       +8     

☔ 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.

This branch has not been deployed

No deployments
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