Repository navigation
Static backend selection: sumtype DynamicBackend + withbackend - #313
Conversation
- Define `DiskArrayEngineBackend` in DiskArrays itself and add a MethodError
hint pointing to DiskArrayEngine.jl. Drops the DiskArrayEngine dependency
and extension, which only existed to import the type.
- `DynamicBackend` now holds a `Union{DefaultBackend,DiskArrayEngineBackend}`
so dispatch on it is union-split into a branch instead of a dynamic
dispatch. `set_dynamic_backend!` switches it at runtime.
- Backends unknown to DiskArrays are attached per array with
`withbackend(array, backend)`. Entry points find the backend with
`get_backend(array)`, which walks up `parent` until `parent(a) === a`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh
withbackendThe union-split branch costs ~nothing (measured: same time and allocations as a hard-coded DefaultBackend), so runtime switching no longer needs a "dynamic" preference. The preference now only selects the backend that is active at load time, and `set_backend` also switches the running session instead of asking for a restart. Also force specialization on `f` in the `sum`/`prod`/.../`extrema`/`mean` entry points. `f::Function` is only passed through there, so Julia did not specialize on it and the inner call was a dynamic dispatch: `sum(a)` on a 2-element array goes from 142 ns / 11 allocs to 61 ns / 7 allocs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh
- `unique` visits chunks in a different order than an Array's elements, so test with `issetequal` (of the `f` values for `unique(f, x)`, where the first representative differs) instead of `@test_broken ... ==`, which passed or failed depending on the random data. - Qualify `Base.mapreducedim!`, size the output from `ndims` instead of assuming 3 dimensions, and start the reference from `zero(R)` instead of uninitialized memory. - Only test `median(dims=2)` for ndims >= 2 and `cumsum`/`cumprod` without `dims` for vectors. - `init=0.0` for float data, an Int `init` with `dims` throws in Base too. - Compare chunked float reductions with `≈`, and do not `count` floats. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh
|
Thanks a lot. I agree on all of them, but am still a bit unsure about
It just feels a bit un-natural to me to tie the way you do processing to an array. IMO it would feel much more natural to tie processing engine to an operation instead of to the data. So what about just adding a keyword argument to all user-callable functions defined here to set a backend optionally? Regarding naming, when I saw withbackend(DiskArrayEngineBackend) do
# Inside this block, global backend is set do DAE
b = sum(a)
c = count(a)
endwhich would be very neat to have and which I would prefer over wrapping arrays. Also, I intended to add support for multi-arg mapreduce later to avoid complicated zipping, which would make backend selection a bit harder in the future if we select compute backends based on the data. |
DiskArrays now owns the backend type (JuliaIO/DiskArrays.jl#313), so that it can be a member of its `DynamicBackend` sum type. We only add methods. - Type `a` in the reduction impls to resolve an ambiguity with the `::ComputeBackend` fallbacks in DiskArrays. - Tests select the backend per array with `withbackend` instead of relying on the backend preference, and import `AccessCountDiskArray` from `DiskArrays.TestTypes`. - Temporary `[sources]` entry pointing to the DiskArrays branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh
Stacked on #308, actions my review comments there. Companion PR for DiskArrayEngine: meggart/DiskArrayEngine.jl#70.
Changes
DiskArrayEngineBackendis defined in DiskArrays. AMethodErrorhint points tousing DiskArrayEnginewhen its methods are missing, or says that DiskArrayEngine does not implement the operation when it is already loaded. This removes theDiskArrayEnginedep andDiskArrayEngineExt, which only imported the type (and madeload_backenderror for the"DiskArrayEngine"preference, since the extension cannot be loaded yet while DiskArrays precompiles).DynamicBackendis a sum type:current_backend::Union{DefaultBackend,DiskArrayEngineBackend}. Dispatch is union-split into anisabranch.DynamicBackend, since the branch costs nothing measurable (below). The preference only selects the backend that is active at load time, so the"dynamic"value is gone.set_dynamic_backend!(backend)switches for the session,set_backend(name)switches and saves the preference, no restart needed.withbackend(array, backend)attaches any other backend to an array via aWithBackendDiskArraywrapper. Entry points now callget_backend(array), which walks upparentuntil it finds the wrapper, stops atparent(a) === a, and falls back to the global backend.BroadcastStyleonly sees the type, so it still uses the global backend.f.sum(f::Function, a; kwargs...)and friends only passfthrough, so Julia did not specialize on it and the inner call was a dynamic dispatch. Unrelated to backends, but it was the only overhead left in this path.Overhead
2-element
AccessCountDiskArray, Julia 1.13, minimum times. The global backend is aDynamicBackendin every row but the last two.sum(a)diskarrays_sum_impl(identity, a, get_backend(a))diskarrays_sum_impl(identity, a, DefaultBackend())(hard-coded)sum(a)before specializing onfget_backend(view(withbackend(a, b), 1:2))sum,mapreduceandsum(a .+ 1)all inferFloat64,typeof(a .+ 1)infers concretely, andsum(a)contains no dynamic calls.For comparison, the abstractly typed field from #308, with 4 extra backend methods defined to mimic a loaded ecosystem (with a single method the compiler devirtualizes either design), median times:
DefaultBackendFloat64DynamicBackend(Union, this PR)Float64AnyTests
test_backend_dispatchno longer needs a preference: it useswithbackendwith theTestBackend, checks lookup through aview, runtime switching of the global backend, type stability throughDynamicBackend, and the error hint.Note that
test/backend_tests.jlalready fails on #308 itself, before reaching this testset (flaky@test_broken unique(...)unexpected passes,median(da, dims=2)on a vector, unqualifiedmapreducedim!,cumsumwithoutdims, exact==on float sums,counton a float array, andmapreduce(...; dims=1, init=0)on float data, which throws the sameInexactErrorfor a plainArrayin Base on Julia 1.10, 1.12 and 1.13, so that is not a DiskArrays bug). I left those alone. With them bypassed locally, the dispatch testset and the rest ofruntests.jl(including Aqua) pass.🤖 Generated with Claude Code
https://claude.ai/code/session_018ra7EAJeFE1dtekwf4Tzxh