Skip to content

Add StaticSizeArray as a sibling. - #210

Closed
vchuravy wants to merge 3 commits into
mainfrom
vc/static-size-array
Closed

vchuravy wants to merge 3 commits into
mainfrom
vc/static-size-array

Conversation

@vchuravy

@vchuravy vchuravy commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

This PR adds the public (not exported) type FixedSizeArrays.StaticSizeArray{T,N,D,Mem}: an N-dimensional array over a memory backend Mem<:DenseVector{T} whose dimensions are a tuple of type D of Ints and Static.jl StaticInts, e.g. (static(5), static(4), static(4), nelements). Any dimension can be static (a compile-time constant that takes no space in the array) or dynamic (set at runtime). The design follows StrideArraysCore.jl and JuliaGPU/GPUArrays.jl#814.

It also includes a fix for #200: fill!(::FixedSizeArray, x) loops directly over the memory backend, so that it elides the bounds checks and vectorizes on Julia v1.13+.

Motivation

In Trixi.jl the solution is stored as an array of size (nvariables, nnodes, ..., nelements). The number of variables and nodes is fixed by the equations and the polynomial degree, while the number of elements depends on the mesh and changes at runtime (e.g. with AMR). With static dimensions, the compiler folds the index computations into constants and can unroll the loops over the small dimensions. This matters in particular for:

  • Polyester.jl @batch and LoopVectorization.jl: captured arrays are converted to PtrArrays through StaticArrayInterface.jl. With a plain FixedSizeArray all sizes become dynamic; with a StaticSizeArray, static_size and static_strides keep the static dimensions static.
  • GPU kernels: the array stays isbits when its memory backend is, and the strides of the static dimensions are constants. (Have to revisit this for Add StaticDimsArray: GPU arrays with a mix of static and dynamic dimensions JuliaGPU/GPUArrays.jl#814, this here might be sufficient to get static type information down to kernels)

Design

  • Fields: mem and dims. A StaticInt in dims takes no space, so e.g. a 3-D array with two static dimensions is the size of a FixedSizeVector.
  • Constructors:
    • FixedSizeArrays.wrap(StaticSizeArray, mem, dims), where at most one dimension can be : and is computed from length(mem), e.g. to wrap a Vector again after resize!;
    • undef constructors, e.g. StaticSizeArray{T}(undef, dims); when the type gives the dimensions, the static ones can be passed as plain Ints, so similar(typeof(a), size) works;
    • StaticSizeArray(a::FixedSizeArray, dims), sharing the memory.
  • Indexing: IndexLinear. The Cartesian linear index is a sum of strided terms, whose strides are constants as long as the dimensions before them are static. Unlike the Horner form, this lets loop-invariant terms be hoisted when dimensions are dynamic; with all leading dimensions static, both forms perform the same. size(a, d) is a compile-time constant for a static dimension.
  • Arrays interface:
    • strided arrays interface (strides, elsize, unsafe_convert), copy, fill!, copyto!;
    • similar(a) and similar(a, T) return StaticSizeArrays, while similar(a, T, size) returns a FixedSizeArray, so that range indexing and reductions infer;
    • out-of-place broadcasting (also over views), reshape and vec return FixedSizeArrays.
  • Shared code: the methods that only depend on the memory backend (linear indexing, iterate, fill!, copyto!, reshape, pointers, dataids) are shared with FixedSizeArray through an internal FixedOrStaticSizeArray union.
  • Package extensions:
    • StaticArrayInterface.jl (new weak dependency): static_size, static_strides, known_size, known_strides. StaticArrayInterface.device of a FixedSizeArray or StaticSizeArray is forwarded to its memory backend, so that e.g. LoopVectorization.jl does not treat a GPU memory backend as a CPU pointer.
    • Adapt.jl: adapts the memory backend of a StaticSizeArray and wraps it again, for any memory backend.
    • MPI.jl: StaticSizeArrays can be used as communication buffers.
  • Docs: a usage section, with a comparison to Array, FixedSizeArray, StaticArrays.jl, HybridArrays.jl and StrideArraysCore.jl, plus the reference and the CHANGELOG.

Open question: Static.jl as a hard dependency

Static.jl is a new hard dependency, but only StaticInt, dynamic and known are used. Loading it increases the load time of using FixedSizeArrays from about 10 ms to 48 ms and adds many invalidations, for every user. JuliaGPU/GPUArrays.jl#814 has the same open question. The alternatives are:

  • moving StaticSizeArray into a package extension on Static.jl;
  • defining a minimal StaticInt here and converting to and from Static.jl in an extension.

Testing

All tests pass on Julia v1.10 and v1.12, including Aqua.jl and the doctests. The new FileCheck tests check that indexing with static dimensions uses constant strides, that fill! vectorizes, and that in-place broadcasting is vectorized and free of bounds checks. They run only without --check-bounds=yes. Run that way, three existing heap-allocation FileCheck tests fail the same way on main.


This PR was written with major contributions from Claude Code (an AI assistant) and reviewed by me.

🤖 Generated with Claude Code

@vchuravy
vchuravy added this pull request to stack #211 October 14, 2026 08:12
@vchuravy

Copy link
Copy Markdown
Contributor Author

This might be too specific for FSA, and perhaps we can just introduce a abstract type FastDenseArray end to make it easier to support this forwarding work that is needed.

@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.28058% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 95.41%. Comparing base (c15fd6e) to head (6d8a3d4).

Files with missing lines Patch % Lines
src/StaticSizeArray.jl 98.76% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #210      +/-   ##
==========================================
+ Coverage   93.50%   95.41%   +1.90%     
==========================================
  Files           6       10       +4     
  Lines         231      349     +118     
==========================================
+ Hits          216      333     +117     
- Misses         15       16       +1     

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

@giordano

Copy link
Copy Markdown
Collaborator

Other people expressed before the interest in having a static array which is backed by Memory and not tuple, so that you don't pay the penalty for large arrays (and smaller ones may still be allocated on the stack as the compiler gets better), so this is cool!

vchuravy and others added 2 commits October 15, 2026 08:37
The generic `fill!` does not elide the bounds checks for `FixedSizeArray`s on
Julia v1.13 and later, so it does not vectorize (#200). Looping over the memory
backend is as fast as or faster than both the generic method and forwarding to
`fill!` of the memory backend on Julia v1.10 to v1.14-DEV (the latter is slower
for short arrays on v1.13+).

Also enable the corresponding FileCheck test for `FixedSizeArray`s again, and
skip the one for views also on prereleases of v1.13.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MEJYUviAUshj5fZt8LTe8A
New public (not exported) type `StaticSizeArray{T,N,D,Mem}`: an `N`-dimensional
array over a `DenseVector` memory backend whose dimensions are a tuple of type `D`
of `Int`s and Static.jl `StaticInt`s, e.g. `(static(5), static(4), n)`. Any
dimension can be static (a compile-time constant that takes no space) or dynamic
(set at runtime), as in StrideArraysCore and JuliaGPU/GPUArrays.jl#814.

* constructors: `wrap(StaticSizeArray, mem, dims)` (with at most one dimension
  given as `:`, computed from the length of `mem`), `undef` constructors, and
  `StaticSizeArray(a, dims)` sharing the memory of a `FixedSizeArray`
* fast linear indexing and Cartesian indexing as a sum of strided terms, which
  are constants as long as the dimensions before them are static,
  `LightBoundsError`s, `size(a, d)` is a constant for static dimensions
* strided arrays interface, `similar` (a `StaticSizeArray` without a size or
  from the type, a `FixedSizeArray` with a size, so that range indexing and
  reductions infer), `copy`, `fill!`, `copyto!` forwarding to the memory,
  broadcasting via `FixedSizeArrayBroadcastStyle` (also for views),
  `reshape`/`vec` returning `FixedSizeArray`s
* extensions: Adapt (adapting the memory backend, for any backend), MPI (as
  communication buffer), and the new StaticArrayInterface extension (static sizes
  and strides for StrideArraysCore, LoopVectorization and Polyester; `device`
  of the memory backend, also for `FixedSizeArray`s)
* docs: motivation and a comparison with StaticArrays, HybridArrays and
  StrideArraysCore
* the methods which only depend on the memory backend (linear indexing,
  `iterate`, `fill!`, `copyto!`, `reshape`, pointers, ...) are shared with
  `FixedSizeArray`s via the internal `FixedOrStaticSizeArray` union

Static.jl is a new dependency.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vchuravy
vchuravy force-pushed the vc/static-size-array branch from a83c387 to 4c37971 Compare October 15, 2026 07:42
@vchuravy
vchuravy removed this pull request from stack #211 October 15, 2026 07:44
@vchuravy
vchuravy changed the base branch from vc/gpu-forwarding to main October 15, 2026 07:44
@vchuravy vchuravy changed the title Add StaticSizeArray, with static sizes in all but the last dimension Add StaticSizeArray as a sibling. Oct 15, 2026
@vchuravy
vchuravy requested a review from giordano October 15, 2026 07:46
@giordano

Copy link
Copy Markdown
Collaborator

I'm happy about the feature, but the I'm a bit uncomfortable about the hard dependency on Static with its invalidations

Loading Static.jl increased the load time of `using FixedSizeArrays` from
about 10 ms to 48 ms and added many invalidations, for every user. Now the
code in `src` handles the dimensions of a `StaticSizeArray` only through the
internal functions `dim_value` and `known_dim` (and `to_static_dim` and
`convert_dim`), with methods for `Int`s, and the new package extension
`FixedSizeArraysStaticExt` adds their methods for `StaticInt`s. Static
dimensions are available once Static.jl is loaded; without it, all
dimensions are `Int`s. The StaticArrayInterface extension now also depends on
Static.jl (which StaticArrayInterface.jl loads anyway) and computes the
`StaticInt` strides itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

2 participants