# \[ANN\] FixedSizeArrays.jl: What Array probably should have been

**URL:** <https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724>\
**Category:** Package Announcements\
**Tags:** announcement, arrays\
**Created:** [June 7, 2025, 4:45pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724 "2025-06-07T16:45:43Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 10, 2025, 11:13am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/41 "2025-06-10T11:13:05Z")

</div>

> [@rfourquet](#):
>
> When would you recommend `FixedSizeVector` over `Memory` ?

`Memory` doesn’t have multi-dimensional array semantic, it’s only a flat data container. `FixedSizeArray` is a light wrapper around `Memory` (or any other `Memory`-like backend actually) to provide that semantic.

---

<div class="post-metadata">

**Author:** ![rfourquet](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rfourquet/32/3610_2.png) [@rfourquet](https://discourse.julialang.org/u/rfourquet)\
**Post date:** [June 10, 2025, 11:20am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/42 "2025-06-10T11:20:23Z")

</div>

Sure, but I was referring specifically to `FixedSizeVector`. `Memory` seems to have the same 1-d array semantic as `FixedSizeVector`. IOW, I’m interested in contexts where only 1-d will ever be needed.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 10, 2025, 11:54am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/43 "2025-06-10T11:54:03Z")

</div>

> [@Mason](#):
>
> Could you expand on why you’d want to use the `const`-`mutable` pattern here? The only reason I can think of wanting to do this would be ~1ns faster hashing, and being able to attach finalizers to a specific `FixedSizeArray` rather than it’s `.mem` field.

Sure. If you store one of the thingies in a heap object that permits inline storage (e.g. another array-like thing, or as a field in a struct), then this copies one pointer for `AlternativeFixedSizeArray`, and stores the entire struct (including the size tuple) for `FixedSizedArray`.

If you store one of the thingies in a heap object that does not permit inline storage, e.g. because it is Any-typed, or you pass to a non-inferred / nospecialize context, then the FixedSizedArray must be boxed, i.e. an entire new object allocated. This entire new object will then be unboxed again when passing to inferred contexts, and will be boxed anew when passing to uninferred contexts. This boxing/unboxing is slow!

If you pass one of the thingies to a non-inlined function, then AlternativeFixedSizedArrays only passes a pointer in a register, whereas FixedSizedArrays needs to deal with more complex calling conventions. I’m not 100% up to date with current julia calling conventions, but afaiu this should typically go via stack, which is slower than register. In C calling convention, passing the `immutable struct` version by-value would kinda suck.

==========

I’m not saying that `mutable struct` with const fields is necessarily better than `struct` here – it’s just that there are tradeoffs involved, and this specific setting looks to me like one of the unusual situations where it’s close (most cases are a no-brainer for immutable struct over mutable-with-const-fields).

==========

The ideal memory layout would of course be if the size-tuple was inlined into the Memory (that must be heap-allocated with identity, for GC purposes, anyways), i.e. something like

```julia
mutable struct MemoryArray{N}
const ptr::Ptr{Nothing} #cannot implement that in julia, must be built-in like GenericMemory
const len::Int
parentForGC::Any #sometimes needed for GC purposes
const size::NTuple{Int}
end

```

But I think we can’t really get this.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 10, 2025, 5:29pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/44 "2025-06-10T17:29:19Z")

</div>

> [@foobar\_lv2](#):
>
> Can you elaborate on the reasons for your decision to do
> 
> ```julia
> struct FixedSizeArray{T,N,Mem<:DenseVector{T}} <: DenseArray{T,N}
> mem::Mem
> size::NTuple{N,Int}
> end
> 
> ```
> 
> as opposed to
> 
> ```julia
> mutable struct AlternativeFixedSizeArray{T,N,Mem<:DenseVector{T}} <: DenseArray{T,N}
> const mem::Mem
> const size::NTuple{N,Int}
> end
> 
> ```

I suppose FixedSizeArrays.jl could, hypothetically, provide an additional type like you propose, perhaps named `FixedSizeArrayObjectIdentity`. However I’m sure we wouldn’t merge a PR like that before seeing some motivating benchmarks comparing the new type with the old one.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 10, 2025, 5:33pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/45 "2025-06-10T17:33:22Z")

</div>

> [@rfourquet](#):
>
> IOW, I’m interested in contexts where only 1-d will ever be needed.

In that case probably not much of a difference.

The problems you showed with `map` are probably related to the fact `Base.Array` is a “privileged” container type, when there isn’t another obvious alternative.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 10, 2025, 7:59pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/46 "2025-06-10T19:59:41Z")

</div>

I get an error when trying to `convert` a `Vector` to a `FixedSizeVector`. Is this a bug, or is there a reason for this?

```julia
julia> FixedSizeVectorDefault{Int}([1, 2, 3])
3-element FixedSizeArray{Int64, 1, Memory{Int64}}:
 1
 2
 3

julia> convert(FixedSizeVectorDefault{Int}, [1, 2, 3])
ERROR: MethodError: Cannot `convert` an object of type Vector{Int64} to an object of type FixedSizeArray{Int64, 1, Memory{Int64}}
The function `convert` exists, but no method is defined for this combination of argument types.

```

By the way, when is it allowed to use `FixedSizeVector`? With a `Vector` as argument, it seems to be as good as `FixedSizeVectorDefault`:

```julia
julia> FixedSizeVector([1, 2, 3])
3-element FixedSizeArray{Int64, 1, Memory{Int64}}:
 1
 2
 3

```

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 10, 2025, 8:01pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/47 "2025-06-10T20:01:27Z")

</div>

> [@matthias314](#):
>
> I get an error when trying to `convert` a `Vector` to a `FixedSizeVector`. Is this a bug, or is there a reason for this?

There’s a reason: [Before release: Remove `convert(::T, x) where {T \<: FixedSizeArray}` methods · Issue #120 · JuliaArrays/FixedSizeArrays.jl · GitHub](https://github.com/JuliaArrays/FixedSizeArrays.jl/issues/120)

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 10, 2025, 8:02pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/48 "2025-06-10T20:02:09Z")

</div>

> [@Mason](#):
>
> Yes, the `FixedSizeArray` itself can be allocated inline, but that just means you put a pointer and an `NTuple` inline. The actual contents of the array themselves are not stored inline, so no continuous memory stuff like with StaticArrays.

> [@foobar\_lv2](#):
>
> Stack/register allocation is of course nice if you have many short-lived FixedSizeArrays that are backed by longer-living Memory instances. But I don’t see this happening in real life.

I think it’s an advantage that `FixedSizeArray` is an immutable struct because as far as I can tell, there’s nothing that demands its backend be heap allocated or have mutable identity. It’s possible for `Mem<:DenseVector{T}` to be an immutable, inline-allocated vector with the necessary static length parameter, which in turn makes `FixedSizeArray` an immutable, inline-allocated, multidimensional array with static length information via `Mem`. An `SVector` or `NTuple` wouldn’t work because:

1. Neither [`SArray<:StaticArray`](https://github.com/JuliaArrays/StaticArraysCore.jl/pull/24) nor `NTuple` can subtype `DenseVector`
2. An `NTuple` (or anything built on it) with an abstract element type is strictly an abstract supertype of concrete, compact, heterogeneous `Tuple`s, unlike a concrete `Array` with possible `isbits`-`Union` inline optimization. That abstract type does hurt when we need `SVector`s with `Union{T, Nothing}` elements.

We probably would need another core backend `StaticMemory{L, T}` to make that possibility a reality.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 10, 2025, 8:13pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/49 "2025-06-10T20:13:01Z")

</div>

> [@matthias314](#):
>
> By the way, when is it allowed to use `FixedSizeVector`?

To be clear, `FixedSizeVector{T}` is not a concrete type because the memory backend is left free. In places where you need to specify a concrete type (e.g. for introspection) you want to use `FixedSizeVectorDefault` or anyway set the memory type, but constructors should use the default memory backend (`Memory` in Julia v1.11+) if not otherwise specified.

---

<div class="post-metadata">

**Author:** ![digital\_carver](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/digital_carver/32/33818_2.png) [@digital\_carver](https://discourse.julialang.org/u/digital_carver)\
**Post date:** [June 10, 2025, 8:29pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/50 "2025-06-10T20:29:59Z")

</div>

Since this is a common thing people might try, it might be worth either adding `convert` methods that just error, or adding error hints to this particular `MethodError`, linking to this issue and asking people to explicitly use the constructor.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 10, 2025, 8:59pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/51 "2025-06-10T20:59:25Z")

</div>

EDIT: added length 500 and also `MutableSmallVector`

I’ve run some benchmarks (with Julia 1.11.5). Here are examples for lengths 32 and 500 with `Int16` elements. The timings are for 1000 operations of the given kind, numbers in brackets indicate allocations.

It appears that `FixedSizeVector` is noticeably faster than `Vector` for addition and much faster for in-place addition and comparisons. (For both kinds of additions the differences are much smaller or disappear for length 500.) However, `FixedSizeVector` is somewhat slower for `sum` and `count` and much slower when comparing two equal vectors. Creating a `FixedSizeVector` from a iterator is also slow (if I’ve done that correctly).

#### Length \<= 32 (or == 32 if not possible otherwise)

| type | create vec | create itr | `sum` | `v + w` | `v .+= w` | `count(>(c), v)` | `v == w` | `v == w` max |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `Vector{Int16}` | 31.259 μs **(2)** | 23.356 μs **(2)** | 7.509 μs | 43.406 μs **(2)** | 21.584 μs | 11.049 μs | 6.174 μs | 7.009 μs |
| `FixedSizeVectorDefault{Int16}` | 24.222 μs **(1)** | 72.832 μs **(1)** | 14.467 μs | 29.580 μs **(1)** | 8.780 μs | 12.652 μs | 3.224 μs | 23.179 μs |
| `MVector{32, Int16}` | 10.568 μs **(1)** | 9.750 μs **(1)** | 2.347 μs | 5.875 μs | 21.765 μs | 2.758 μs | 3.329 μs | 15.119 μs |
| `MutableFixedVector{32, Int16}` | 9.698 μs **(1)** | 8.549 μs **(1)** | 3.085 μs | 2.821 μs | 4.490 μs | 1.594 μs | 2.487 μs | 2.719 μs |
| `MutableSmallVector{32, Int16}` | 19.352 μs **(1)** | 10.478 μs **(1)** | 3.057 μs | 3.157 μs | 4.512 μs | 2.020 μs | 2.679 μs | 3.229 μs |

#### Length \<= 500 (or == 500 if not possible otherwise)

| type | create vec | create itr | `sum` | `v + w` | `v .+= w` | `count(>(c), v)` | `v == w` | `v == w` max |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `Vector{Int16}` | 61.644 μs **(2)** | 69.742 μs **(2)** | 39.312 μs | 102.721 μs **(2)** | 123.097 μs | 46.504 μs | 6.287 μs | 30.817 μs |
| `FixedSizeVectorDefault{Int16}` | 82.601 μs **(1)** | 655.020 μs **(1)** | 47.316 μs | 100.777 μs **(1)** | 93.896 μs | 59.326 μs | 3.907 μs | 342.495 μs |
| `MVector{500, Int16}` | 81.885 μs **(1)** | 65.354 μs **(1)** | 19.550 μs | 104.291 μs | 367.615 μs | 21.174 μs | 4.126 μs | 198.523 μs |
| `MutableFixedVector{500, Int16}` | 89.185 μs **(1)** | 90.337 μs **(1)** | 57.662 μs | 95.209 μs | 117.661 μs | 22.247 μs | 40.584 μs | 53.424 μs |
| `MutableSmallVector{500, Int16}` | 188.431 μs **(1)** | 80.513 μs **(1)** | 123.028 μs | 114.714 μs | 138.296 μs | 29.382 μs | 69.503 μs | 69.516 μs |

create vec = create from `Vector`  
create itr = create from iterator (using `collect_as` for `FixedSizeVector`)  
`v == w` max = comparing two equal vectors

Note: `sum` for `MVector` does not widen the element type to `Int`. The sum of two `MVector`s is an `MVector` by default; I’ve converted it to `SVector`.

The benchmark code is `benchmark/benchmark_vec.jl` in the SmallCollections.jl repository.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 11, 2025, 12:51am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/52 "2025-06-11T00:51:10Z")

</div>

> [@rfourquet](#):
>
> one issue I spotted with `Memory` is that `a .+ a` yields a `Vector` when `a::Memory`

To be perfectly clear, FixedSizeArrays.jl does not have that issue.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [June 11, 2025, 1:12am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/53 "2025-06-11T01:12:27Z")

</div>

Are we even supposed to do math on `Memory`? I thought it was just a backend mutable buffer with an runtime-fixed size, not a full data structure with a semantic size.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 11, 2025, 1:40am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/54 "2025-06-11T01:40:29Z")

</div>

Are you sure you reported the units correctly? 2-8 microseconds to sum 32 int16s sounds like a lot of time. What’s the CPU, and the Julia version?

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [June 11, 2025, 1:42am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/55 "2025-06-11T01:42:04Z")

</div>

> Are we even supposed to do math on `Memory`?

Sure! It’s a very simple data-structure (conceptually at least, it has a few layout tricks like small union optimization), but it fully fleshes out the interface of a 1D fixed size mutable Vector.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 11, 2025, 2:32am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/56 "2025-06-11T02:32:05Z")

</div>

> [@giordano](#):
>
> Are you sure you reported the units correctly?

As I wrote, the timings are for 1000 operations of the given kind.  
So for a single operation you have to replace µs by ns.

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [June 11, 2025, 8:25am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/57 "2025-06-11T08:25:40Z")

</div>

I rewrote some of my code using `FixedSizeArrays`, and got a 5% speedup in a real-world problem. Nice!

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [June 11, 2025, 10:26am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/58 "2025-06-11T10:26:47Z")

</div>

Thanks for the report! How about memory allocations? I’m curious to see if they also went down, and that may explain the marginally better performance.

---

<div class="post-metadata">

**Author:** ![araujoms](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/araujoms/32/217734_2.png) [@araujoms](https://discourse.julialang.org/u/araujoms)\
**Post date:** [June 11, 2025, 10:31am UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/59 "2025-06-11T10:31:35Z")

</div>

The number of allocations went down, but curiously the amount of allocated memory went up.

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [June 11, 2025, 3:28pm UTC](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724/60 "2025-06-11T15:28:43Z")

</div>

At some point I thought I learned that `Array{T, D}` for `D != 1` was special-cased in the compiler to be (effectively?) immutable. But if this package has utility for `D` other than 1, then it must be that said special-casing only covers part of the surface area.

Does any such hack actually exist or have I been making this up? If it exists, what are the extent and limitations of its implementation?

* * *

Thanks for the package. It appears to be a minor tragedy that `Array` wasn’t this all along and we didn’t instead make a distinct type for a resizable `AbstractVector`. Now on to my next soapbox!

(`LinearAlgebra.jl` should have had its own dedicated vector/matrix types instead of pirating `Array`.)

[Previous page](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724.md?page=2)

[Next page](https://discourse.julialang.org/t/ann-fixedsizearrays-jl-what-array-probably-should-have-been/129724.md?page=4)
