What is StrideArrays.jl

It’s an odd question to ask, but I see StrideArrays.jl being used in a lot of extremely performance critical code, but meanwhile the documentation of the project… leaves a lot to be desired.

So: what is StrideArrays.jl? How does it differ to other array types like in Base or StaticArrays? And where is it advantageous to use it?

14 Likes

CC StrideArrays’ package maintainer @Elrod

Extending the question: is there a “what for” summary table for users in disarray, regarding all existing array packages:

StructArrays
StaticArrays
StrideArrays
LazyArrays
AxisArrays
OffsetArrays
BlockArrays
InfiniteArrays
FillArrays
IndirectArrays
MappedArrays
HybridArrays
GetindexArrays
ArrayViews
ArraysOfArrays
UnsafeArrays
StructsOfArrays
ShiftedArrays
RangeArrays
MetadataArrays
AbstractArraysOfArrays
UnalignedVectors
# etc.
9 Likes

In most cases, the GitHub package description and README should help, I guess?

I think a summary table on the main JuliaArrays page would be extremely helpful:

4 Likes

Perhaps JuliaArrays can have a documentation website that describes the packages?

7 Likes

No I know what a stride or step is, thanks.

That’s not what the package StrideArrays.jl are about - at least not with respect to their performance and statically defined size. And it’s those details I’m asking about here.

1 Like

Chris obviously has the last word on this, but I see StrideArrays as somewhere between a “prelude” (in the Haskell/Rust sense of the word) library and a playground for hyper-optimizing array operations on CPU. That’s part of why it doesn’t feel like there’s a coherent API boundary or documentation story: it’s more of a conglomerate of things to make particular downstream workloads its dependencies and users require faster rather than a standalone library (though technically it is one) with a clear mission statement (other than making particular stuff go fast).

To be clear, none of this is meant as a slight against StrideArrays.jl. I think it’s more of a reflection of the diversity of different package types we have in this ecosystem.

2 Likes

I discovered this package while searching for a possibility to allocate mutable arrays on the stack which can be also passed to functions without (heap) allocations. So I plan to try this out in certain situations as as a replacement of StaticArrays. StaticArrays.SArray is not mutable, and StaticArrays.MArray appears to be (heap) allocating in many situations.

I however find the name of the package quite misleading. StackArrays.jl IMHO would describe more precisely what it does.

2 Likes

But it does more than just that, so it should also be called SIMDArrays.jl. I might be wrong, but I believe you can have heap-allocated StrideArrays as well? Thus my point about it being more of a conglomerate than your average package.

StrideArrays will often be heap allocated, too.
They should often SIMD. So should regular Array or StaticArrays, but StrideArrays should SIMD more reliably.

It should support that with the @gc_preserve macro, but escape analysis in the Julia compiler should eventually enable this in more situations for other types, like MArray.

The key is to GC.@preserve the memory, and then replace arrays with PtrArrays, views that don’t own the memory but otherwise act the same.

So what are StrideArrays.jl’s goals and vision?
StrideArrays’s goal is to support strided use cases as well as it can.
I believes code should return the correct answer as quickly as possible with as little effort as possible from the user.

So array operations should SIMD as much as possible, thus StrideArrays does its best to do this.
We should try and minimize the amount of allocations needed, so StrideArrays tries to do its best there.

StrideArrays also prefers a flexible flat representation, over nested layer types. The transpose of a StrideArray isn’t a Transpose{T,<:StrideArray}, but another StrideArray. Same for a view – you get a StrideArray not a SubArray, permutedims returning a StrideArrays view rather than a PermutedDimsArray wrapper, etc.

In short, this is what StrideArrays is actually about:
A flexible representation that can represent all of Array, Transpose, SubArray, PermutedDimsArray, StaticArrays, HybridArrays, etc, and nested combinations thereof.
No need for extra implementations, or introspection to peal off layers.

What StrideArrays is about is this representation. This strided representation.
Hence, the name.

Other features like SIMD and helping to cut down heap allocations are because these sorts of optimizations simply make sense to do, and are easy enough – or are simpler with one flat representation to deal with.

But the package was/is an experimental playground, so maybe that’s why the documentation and API boundary don’t have a good story?
PRs are always welcome to improve any deficiencies.

I’d also hope it is for the most part relatively easy to use. Polyester.jl turns arrays into StrideArrays under the hood, and I don’t think many people notice (other than that their code suddenly allocates a lot less vs Threads.@threads) or complain, which says something about it working reasonably well as a drop in replacement.

The biggest difference vs regular arrays is that slicing makes views by default, not copies.
Also, you should probably start Julia with --check-bounds=yes while developing.

23 Likes

Do you mean expressions like A[1:k] does not create copies? This seems very useful. How is this compared to @views?

julia> using StrideArrays, Test

julia> using Test

julia> a = StrideArray(zeros(77));

julia> b = a[10:20];

julia> b[4] = 7;

julia> @test a[13] == 7 # 0-based indexing > 1-based
Test Passed

julia> @test typeof(a) === typeof(b)
Test Passed

The difference vs @views is that it should be better at preserving the type of the original array, meaning that hopefully it won’t cause as much unnecessary code specialization.

With Vector{Float64}, the view would have type SubArray{Float64, 1, Vector{Float64}, Tuple{UnitRange{Int64}}, true}, meaning type inference through llvm code gen all need to run again for no real reason.

1 Like

For me the typeof test doesn’t pass, what could be the reason?
It’s StrideArray{Float64, 1, (1,), Tuple{Int64}, Tuple{Nothing}, ...} for a and StrideArray{Float64, 1, (1,), Tuple{Int64}, Tuple{Int64}, ...} for b.

Thanks @Elrod for your description.

Regarding the ‘more likely to SIMD’ aspect - is this a factor of the size of the array being part of the type system?

As for the possibility of sometimes being stack allocated - how does it do that?

Try updating to the latest version.

1 Like

Thank you!

No, it’s that it uses LoopVectorization.jl for things like broadcasting.

A bit hacky/manually. You need to declare statically sized arrays (or, soon, small arrays).
You’d need to @gc_preserve them when passing through function boundaries.

Once the Julia compiler enables escape analysis for this, the @gc_preserve will no longer be necessary (and StaticArrays.MArray will work, too).

Note that StrideArrays.@gc_preserve will also work on StaticArrays.MArray, but you may as well use a StaticStrideArray instead.

2 Likes

I had a look at the github site and did not see answers to the questions posed here, particularly the key “what is” question. I don’t think this makes the package much different from other Julia packages, though.

Narrow documentation can limit language acceptance and thus sustainability. Julia is not without competition. On one side we have compiled languages such as Fortran, C++, etc., for which many excellent textbooks are available. And on the interactive side we have Python, R, etc., for which detailed and precise documentation tends to be closely coupled with the code.

I realize that Julia is still fairly young, and that many of its users are busy solving their own problems. Perhaps patterns will change. I hope so, because I think Julia holds some promise.

1 Like