SIGN IN SIGN UP

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