Skip to content

feat!: default to Matrix buffers with optional backing array type#32

Merged
zsoerenm merged 4 commits into
mainfrom
drop-fixedsizearrays
Jul 13, 2026
Merged

feat!: default to Matrix buffers with optional backing array type#32
zsoerenm merged 4 commits into
mainfrom
drop-fixedsizearrays

Conversation

@zsoerenm

Copy link
Copy Markdown
Member

Summary

SignalChannel no longer mandates FixedSizeArrays. Benchmarking showed FixedSizeArrays gave no throughput benefit here — the channel passes references, and the bulk copies compile to memmove regardless of array type (reference-passing is if anything marginally faster with plain Matrix).

The buffer type is now a type parameter that defaults to Matrix{T}, so plain matrices just work, while any AbstractMatrix{T} (FixedSizeArrays, StaticArrays, …) remains available as an opt-in.

Changes

  • SignalChannel{T,N}SignalChannel{T,N,M} where M<:AbstractMatrix{T} defaults to Matrix{T}.
  • put! accepts any AbstractMatrix{T}: stored by reference when it is already M (zero-copy, the default case), otherwise constructed via M(v). The old "FixedSizeMatrixDefault only" throwing guard is removed.
  • RechunkState{T,N,M}; rechunk, mux, and add propagate the input channel's backing type.
  • FixedSizeArrays dropped from package dependencies → now test-only ([extras]). Users who add SignalChannels no longer pull it in.
  • New test/backing_type.jl exercises the FSA opt-in end-to-end; existing tests migrated to the default Matrix API. Benchmarks migrated too.

Opt-in example

using FixedSizeArrays: FixedSizeMatrixDefault
chan = SignalChannel{ComplexF32,4,FixedSizeMatrixDefault{ComplexF32}}(1024)

Breaking change

The default eltype is now Matrix{T} instead of FixedSizeMatrixDefault{T}. Code relying on take! returning a FixedSizeMatrixDefault must either construct channels with the explicit backing type SignalChannel{T,N,FixedSizeMatrixDefault{T}} or adapt to Matrix{T}.

Testing

Full suite passes locally (julia --threads=4): SignalChannel 46, Backing matrix type parameter 15, Rechunk 113, Channel Utilities 43, Channel Combine 38, Stream Utilities 13, periodograms 8, SoapySDR Extension 6 (+2 pre-existing @test_broken).

🤖 Generated with Claude Code

SignalChannel, RechunkState, and the transforms (rechunk, mux, add) gain a
backing matrix type parameter M that defaults to Matrix{T}. Plain matrices can
now be put! directly; FixedSizeArrays is no longer required and becomes an
opt-in backing type, e.g. SignalChannel{T,N,FixedSizeMatrixDefault{T}}.

Benchmarks showed FixedSizeArrays gave no throughput benefit here: the channel
passes references and the bulk copies compile to memmove regardless of the
array type. FixedSizeArrays is therefore dropped from the package dependencies
and is now a test-only dependency.

BREAKING CHANGE: SignalChannel is now SignalChannel{T,N,M}; the default eltype
is Matrix{T} instead of FixedSizeMatrixDefault{T}. Code relying on take!
returning a FixedSizeMatrixDefault must construct channels with the explicit
backing type SignalChannel{T,N,FixedSizeMatrixDefault{T}} or adapt to Matrix{T}.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.34884% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.00%. Comparing base (7fc04ec) to head (80a2180).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/rechunk.jl 92.85% 1 Missing ⚠️
src/signal_channel.jl 94.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #32      +/-   ##
==========================================
+ Coverage   74.31%   75.00%   +0.68%     
==========================================
  Files           8        8              
  Lines         545      552       +7     
==========================================
+ Hits          405      414       +9     
+ Misses        140      138       -2     

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

…annel APIs

AirspeedVelocity runs the PR head's benchmark scripts against both the PR and
the baseline (main). Since the backing-array-type change is breaking, the head
scripts must stay runnable on the old FixedSizeMatrixDefault-only API.

Add a shared HAS_BACKING_TYPE_PARAM detector and an as_buffer helper in
benchmarks.jl that feeds each package version its native zero-copy buffer type
(Matrix on the new API, FixedSizeMatrixDefault on the old one), and relax buffer
parameter annotations to AbstractMatrix. Verified the head scripts run against
both the new working tree and the registered v6.2.2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Benchmark Results (Julia v1)

Time benchmarks
main 80a2180... main / 80a2180...
channel/buffer=1 3.8 ± 0.03 ms 3.89 ± 0.034 ms 0.979 ± 0.012
channel/buffer=1024 0.215 ± 0.031 ms 0.186 ± 0.0099 ms 1.15 ± 0.18
channel/buffer=16 0.415 ± 0.017 ms 0.396 ± 0.014 ms 1.05 ± 0.056
channel/buffer=256 0.211 ± 0.013 ms 0.185 ± 0.013 ms 1.14 ± 0.11
channel/buffer=4 1.08 ± 0.019 ms 1.08 ± 0.015 ms 0.995 ± 0.022
channel/buffer=64 0.246 ± 0.013 ms 0.221 ± 0.011 ms 1.11 ± 0.081
rechunk/10000_to_2048/buffer=1 6.7 ± 0.26 ms 6.16 ± 0.29 ms 1.09 ± 0.066
rechunk/10000_to_2048/buffer=16 4.32 ± 0.23 ms 3.78 ± 0.28 ms 1.14 ± 0.1
rechunk/10000_to_2048/buffer=4 4.62 ± 0.23 ms 4.06 ± 0.23 ms 1.14 ± 0.085
rechunk/10000_to_2048/buffer=64 4.48 ± 0.11 ms 3.96 ± 0.26 ms 1.13 ± 0.079
rechunk/2048_to_10000/buffer=1 1.01 ± 0.05 ms 0.859 ± 0.11 ms 1.18 ± 0.16
rechunk/2048_to_10000/buffer=16 1.06 ± 0.087 ms 0.863 ± 0.12 ms 1.23 ± 0.2
rechunk/2048_to_10000/buffer=4 1.04 ± 0.055 ms 0.847 ± 0.14 ms 1.22 ± 0.21
rechunk/2048_to_10000/buffer=64 1.23 ± 0.26 ms 1.01 ± 0.14 ms 1.22 ± 0.31
rechunk_state/batch/batch_2048_to_512_1ch 1.53 ± 0.099 ms 1.31 ± 0.066 ms 1.17 ± 0.096
rechunk_state/batch/batch_2048_to_512_4ch 5.99 ± 0.15 ms 5.4 ± 0.36 ms 1.11 ± 0.08
rechunk_state/batch/batch_512_to_2048_1ch 0.307 ± 0.018 ms 0.288 ± 0.017 ms 1.06 ± 0.087
rechunk_state/batch/batch_passthrough_2048_1ch 3.92 ± 0.48 μs 3.94 ± 1.2 μs 0.995 ± 0.33
rechunk_state/batch/iterative_2048_to_512_1ch 1.53 ± 0.068 ms 1.31 ± 0.085 ms 1.16 ± 0.091
rechunk_state/batch/iterative_2048_to_512_4ch 5.97 ± 0.21 ms 5.48 ± 0.34 ms 1.09 ± 0.079
rechunk_state/batch/iterative_512_to_2048_1ch 0.306 ± 0.021 ms 0.295 ± 0.017 ms 1.04 ± 0.094
rechunk_state/batch/iterative_passthrough_2048_1ch 3.91 ± 0.45 μs 3.87 ± 0.63 μs 1.01 ± 0.2
rechunk_state/channels/2048_to_1024_1ch 1.5 ± 0.059 ms 1.28 ± 0.1 ms 1.17 ± 0.1
rechunk_state/channels/2048_to_1024_2ch 2.98 ± 0.19 ms 2.76 ± 0.11 ms 1.08 ± 0.08
rechunk_state/channels/2048_to_1024_4ch 5.84 ± 0.17 ms 5.31 ± 0.21 ms 1.1 ± 0.053
rechunk_state/channels/2048_to_1024_8ch 11.8 ± 0.19 ms 10.6 ± 0.83 ms 1.12 ± 0.089
rechunk_state/downsample/10000_to_1024_1ch 7.37 ± 0.29 ms 6.91 ± 1.4 ms 1.07 ± 0.22
rechunk_state/downsample/2048_to_512_1ch 1.57 ± 0.081 ms 1.3 ± 0.072 ms 1.2 ± 0.092
rechunk_state/downsample/2048_to_512_4ch 6.15 ± 0.2 ms 5.68 ± 0.29 ms 1.08 ± 0.064
rechunk_state/passthrough/2048_to_2048_1ch 3.86 ± 0.34 μs 4.17 ± 0.6 μs 0.926 ± 0.16
rechunk_state/passthrough/2048_to_2048_4ch 4.14 ± 0.43 μs 4.49 ± 0.37 μs 0.923 ± 0.12
rechunk_state/types/ComplexF64_2048_to_1024 2.64 ± 0.22 ms 2.41 ± 0.1 ms 1.09 ± 0.1
rechunk_state/types/Float32_2048_to_1024 0.704 ± 0.058 ms 0.614 ± 0.04 ms 1.15 ± 0.12
rechunk_state/types/Float64_2048_to_1024 1.5 ± 0.066 ms 1.3 ± 0.07 ms 1.16 ± 0.081
rechunk_state/types/Int16_2048_to_1024 0.333 ± 0.033 ms 0.285 ± 0.019 ms 1.17 ± 0.14
rechunk_state/upsample/1024_to_10000_1ch 0.647 ± 0.042 ms 0.582 ± 0.028 ms 1.11 ± 0.09
rechunk_state/upsample/512_to_2048_1ch 0.303 ± 0.018 ms 0.285 ± 0.021 ms 1.06 ± 0.1
rechunk_state/upsample/512_to_2048_4ch 1.57 ± 0.056 ms 1.34 ± 0.1 ms 1.17 ± 0.1
rechunk_state/zero_copy/near_passthrough_2048_1ch 1.5 ± 0.08 ms 1.31 ± 0.081 ms 1.15 ± 0.094
rechunk_state/zero_copy/near_passthrough_2048_4ch 6.53 ± 0.39 ms 6.27 ± 0.37 ms 1.04 ± 0.087
rechunk_state/zero_copy/near_passthrough_8192_4ch 0.0477 ± 0.079 s 0.0352 ± 0.026 s 1.36 ± 2.5
rechunk_state/zero_copy/passthrough_2048_1ch 3.92 ± 0.57 μs 4.04 ± 0.64 μs 0.972 ± 0.21
rechunk_state/zero_copy/passthrough_2048_4ch 4.44 ± 0.29 μs 4.51 ± 0.65 μs 0.985 ± 0.15
rechunk_state/zero_copy/passthrough_8192_4ch 4.97 ± 0.3 μs 5.04 ± 0.92 μs 0.986 ± 0.19
tee/buffer=1 0.635 ± 0.016 ms 0.616 ± 0.014 ms 1.03 ± 0.035
tee/buffer=16 0.119 ± 0.0033 ms 0.0978 ± 0.01 ms 1.22 ± 0.13
tee/buffer=4 0.2 ± 0.02 ms 0.207 ± 0.02 ms 0.964 ± 0.13
tee/buffer=64 0.0718 ± 0.0029 ms 0.0921 ± 0.0074 ms 0.78 ± 0.07
time_to_load 1.45 ± 0.01 s 1.46 ± 0.0012 s 0.998 ± 0.0071
Memory benchmarks
main 80a2180... main / 80a2180...
channel/buffer=1 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
channel/buffer=1024 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
channel/buffer=16 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
channel/buffer=256 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
channel/buffer=4 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
channel/buffer=64 18 allocs: 1.2 kB 18 allocs: 1.19 kB 1.01
rechunk/10000_to_2048/buffer=1 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/10000_to_2048/buffer=16 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/10000_to_2048/buffer=4 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/10000_to_2048/buffer=64 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/2048_to_10000/buffer=1 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/2048_to_10000/buffer=16 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/2048_to_10000/buffer=4 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk/2048_to_10000/buffer=64 15 allocs: 0.906 kB 15 allocs: 0.906 kB 1
rechunk_state/batch/batch_2048_to_512_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/batch_2048_to_512_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/batch_512_to_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/batch_passthrough_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/iterative_2048_to_512_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/iterative_2048_to_512_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/iterative_512_to_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/batch/iterative_passthrough_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/channels/2048_to_1024_1ch 1 allocs: 16 B 1 allocs: 16 B 1
rechunk_state/channels/2048_to_1024_2ch 1 allocs: 16 B 1 allocs: 16 B 1
rechunk_state/channels/2048_to_1024_4ch 1 allocs: 16 B 1 allocs: 16 B 1
rechunk_state/channels/2048_to_1024_8ch 1 allocs: 16 B 1 allocs: 16 B 1
rechunk_state/downsample/10000_to_1024_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/downsample/2048_to_512_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/downsample/2048_to_512_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/passthrough/2048_to_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/passthrough/2048_to_2048_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/types/ComplexF64_2048_to_1024 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/types/Float32_2048_to_1024 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/types/Float64_2048_to_1024 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/types/Int16_2048_to_1024 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/upsample/1024_to_10000_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/upsample/512_to_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/upsample/512_to_2048_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/near_passthrough_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/near_passthrough_2048_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/near_passthrough_8192_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/passthrough_2048_1ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/passthrough_2048_4ch 0 allocs: 0 B 0 allocs: 0 B
rechunk_state/zero_copy/passthrough_8192_4ch 0 allocs: 0 B 0 allocs: 0 B
tee/buffer=1 0.032 k allocs: 1.81 kB 0.032 k allocs: 1.81 kB 1
tee/buffer=16 0.032 k allocs: 1.81 kB 0.032 k allocs: 1.81 kB 1
tee/buffer=4 0.032 k allocs: 1.81 kB 0.032 k allocs: 1.81 kB 1
tee/buffer=64 0.032 k allocs: 1.81 kB 0.032 k allocs: 1.81 kB 1
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 kB 1

zsoerenm and others added 2 commits July 13, 2026 13:59
The two-method put! (put!(::SignalChannel{T,N,M}, ::M) fast path plus an
AbstractMatrix fallback) failed to dispatch to the fast path: M is a diagonal
type variable shared with the channel type, so Julia did not consider the
::M method more specific than the ::AbstractMatrix fallback. Every put! hit the
fallback and copied the buffer via M(v), allocating one buffer per call and
regressing channel/rechunk throughput 2-500x (caught by the PR benchmark).

Replace it with a single put! method plus a convert-style _as_backing(::Type{M},
v) helper (modeled on Base.convert's (::Type{T}, ::T) / (::Type{T}, x) pair),
which dispatches the identity case reliably. Matching buffers are now stored by
reference (0 allocations); only genuinely foreign matrix types are constructed
into M. Add a regression test asserting take!(chan) === put! input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PipeChannels 1.0.1 fixes a data-loss race where a consumer iterating/take!-ing
could drop the final item when close() raced with the last put!. SignalChannels'
zero-copy put! makes that window more likely to hit (e.g. flaky tee output), so
require >= 1.0.1 (which excludes the fix-less 1.0.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zsoerenm
zsoerenm merged commit a49755e into main Jul 13, 2026
7 checks passed
@zsoerenm
zsoerenm deleted the drop-fixedsizearrays branch July 13, 2026 21:40
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