perf: bulk fast paths for NTuple-from-array and rand!(::Array{Complex}) (#61762)
I pointed Claude at [perf.julialang.org](https://perf.julialang.org/)
and asked it to find opportunities to improve unusually slow benchmarks.
See
[JuliaCI/julia-ci-timing@main/analysis](https://github.com/JuliaCI/julia-ci-timing/tree/main/analysis)
for some of the scripts it used.
This is what it came up with:
----
Two small, BaseBenchmarks-driven perf fixes that share a theme — when
the
storage of an array already has the layout you want, dispatch to the
bulk
path on a `reinterpret` view instead of going through the per-element
fallback.
### `NTuple{N,E}(::Union{Array,Memory})` for `N >= 32`
The default `_totuple(::Type{All32{E,N}}, itr)` path collects into an
intermediate `Vector` and splats. The `All32` cap exists so we don't
specialize tuple construction on every `N`; this PR keeps that property
(no per-`N` codegen) by adding a single fast path for `Array`/`Memory`
inputs whose `isbits` element type matches the requested tuple element
type. In that case the storage is layout-identical to the tuple, so a
single `reinterpret` view + scalar load returns the tuple in O(1) (plus
a length check).
The fast path lives in `reinterpretarray.jl` because `reinterpret` on
arrays isn't yet defined when `tuple.jl` is loaded. Over-long inputs are
silently truncated to match the iterator-based fallback. Non-matching
eltypes / non-isbits eltypes fall through to the existing path
unchanged.
```
master this PR
NTuple{40,Float64}(::Vector{Float64}) 1.36 μs 7.3 ns
NTuple{150,Float64}(::Vector{Float64}) 4.92 μs 17.0 ns
```
(0 allocations on the fast path, vs. `N+~3` allocations and `~33 N` B
on master.)
### Bulk `rand!(::Array{Complex{T}})` for `T <: HWReal`
`rand!(::AbstractRNG, ::Array{Complex{T}})` previously fell through to
the generic `AbstractArray` path — two scalar `rand` calls per element.
A packed `Array{Complex{T}}` has the same memory layout as a length-`2N`
`Array{T}`, so reinterpreting and dispatching to the bulk `rand!` for
`T` (SIMD-vectorized for `HWReal` on Xoshiro/TaskLocalRNG, and pointer-
based for MersenneTwister) is correct and substantially faster.
Per @vtjnash's feedback in earlier review, this avoids
`unsafe_wrap`/`unsafe_load` entirely. Instead, the existing bulk
dispatches in `MersenneTwister` and `XoshiroSimd` are widened to also
accept a `NonReshapedReinterpretArray` over a `MutableDenseArrayType`
parent. `pointer`/`unsafe_convert` are already defined for
`ReinterpretArray` and forward to the parent's storage, so the existing
`GC.@preserve A ... pointer(A) ...` machinery keeps working unchanged.
Speedups on a length-1000 `Vector{Complex{T}}`, default RNG:
```
master this PR
Complex{Float16} 3.5 μs 1.5 μs 2.4×
Complex{Float32} 3.4 μs 2.2 μs 1.5×
Complex{Float64} 3.1 μs 3.1 μs ≈
Complex{Int8} 3.1 μs 539 ns 5.7×
Complex{Int16} 3.1 μs 1.1 μs 3.0×
Complex{Int32} 3.1 μs 1.9 μs 1.7×
Complex{Int64} 4.6 μs 4.2 μs 1.1×
Complex{UInt8} 3.1 μs 532 ns 5.9×
Complex{UInt16} 3.1 μs 1.0 μs 2.9×
Complex{UInt32} 3.0 μs 1.9 μs 1.6×
Complex{UInt64} 4.7 μs 4.2 μs 1.1×
```
All variants are 0-allocation. `Complex{Float64}` and the 64-bit integer
variants were already close to the bulk path's throughput on master and
see only a small win.
A reproducibility test was added so equal MT seeds keep producing equal
sequences for `Vector{Complex{T}}`.
---
Disclosure: written with the assistance of generative AI (GitHub
Copilot / Claude).
---------
Co-authored-by: GitHub Copilot <noreply@github.com> I
Ian Butterworth committed
201e056e9ee2398f01ecd2f104f9d8000b04d195
Parent: 690a68d
Committed by GitHub <noreply@github.com>
on 5/18/2026, 2:48:10 AM