Skip to content

Commit 8958925

Browse files
johnnychen94piever
andauthored
add a note about reinterpret's memory layout (#199)
* add a note about `reinterpret`'s memory layout * rephrase the words * apply suggestions * move to advanced section * add example for dims keyword * one more note on `dims=ndims(v)` * explain the memory order and the view perspective * Minor wording change Co-authored-by: Pietro Vertechi <pietro.vertechi@protonmail.com>
1 parent 0a0032c commit 8958925

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,82 @@ julia> s
342342
Foo(44, "d")
343343
Foo(55, "e")
344344
```
345+
346+
## Advanced: StructArrays versus struct-of-arrays layout in higher-dimensional array
347+
348+
Regular arrays of structs can sometimes be reinterpreted as arrays of primitive values with an added
349+
initial dimension.
350+
351+
```julia
352+
julia> v = [1.0+3im, 2.0-im]
353+
2-element Vector{ComplexF64}:
354+
1.0 + 3.0im
355+
2.0 - 1.0im
356+
357+
julia> reinterpret(reshape, Float64, v)
358+
2×2 reinterpret(reshape, Float64, ::Vector{ComplexF64}) with eltype Float64:
359+
1.0 2.0
360+
3.0 -1.0
361+
```
362+
363+
However, the situation is more complex for the `StructArray` format, where `s = StructArray(v)` is
364+
stored as two separate `Vector{Float64}`. `reinterpret` on `StructArray` returns an
365+
"array-of-structs" layout, as the reinterpretation works element-wise:
366+
367+
```julia
368+
julia> s = StructArray([1.0+3im, 2.0-im])
369+
2-element StructArray(::Vector{Float64}, ::Vector{Float64}) with eltype ComplexF64:
370+
1.0 + 1.0im
371+
2.0 - 1.0im
372+
373+
julia> reinterpret(reshape, Float64, s) # The actual memory is `([1.0, 2.0], [3.0, -1.0])`
374+
2×2 reinterpret(reshape, Float64, StructArray(::Vector{Float64}, ::Vector{Float64})) with eltype Float64:
375+
1.0 2.0
376+
3.0 -1.0
377+
```
378+
379+
If you already have a `StructArray`, the easiest way is to get the higher-dimensional
380+
"struct-of-arrays" layout is to directly stack the components in memory order:
381+
382+
```julia
383+
julia> using StackViews # lazily cat/stack arrays in a new tailing dimension
384+
385+
julia> StackView(StructArrays.components(s)...)
386+
2×2 StackView{Float64, 2, 2, Tuple{Vector{Float64}, Vector{Float64}}}:
387+
1.0 3.0
388+
2.0 -1.0
389+
```
390+
391+
StructArrays also provides `dims` keyword to reinterpret a given memory block without creating new
392+
memory:
393+
394+
```julia
395+
julia> v = Float64[1 3; 2 -1]
396+
2×2 Matrix{Float64}:
397+
1.0 3.0
398+
2.0 -1.0
399+
400+
julia> s = StructArray{ComplexF64}(v, dims=1)
401+
2-element StructArray(view(::Matrix{Float64}, 1, :), view(::Matrix{Float64}, 2, :)) with eltype ComplexF64:
402+
1.0 + 2.0im
403+
3.0 - 1.0im
404+
405+
julia> s = StructArray{ComplexF64}(v, dims=2)
406+
2-element StructArray(view(::Matrix{Float64}, :, 1), view(::Matrix{Float64}, :, 2)) with eltype ComplexF64:
407+
1.0 + 3.0im
408+
2.0 - 1.0im
409+
410+
julia> s[1] = 0+0im; s # `s` is a reinterpretation view and doesn't copy memory
411+
2-element StructArray(view(::Matrix{Float64}, :, 1), view(::Matrix{Float64}, :, 2)) with eltype ComplexF64:
412+
0.0 + 0.0im
413+
2.0 - 1.0im
414+
415+
julia> v # thus `v` will be modified as well
416+
2×2 Matrix{Float64}:
417+
0.0 0.0
418+
2.0 -1.0
419+
```
420+
421+
For column-major arrays, reinterpreting along the last dimension (`dims=ndims(v)`) makes every
422+
component of `s` a view of contiguous memory and thus is more efficient. In the previous example,
423+
when `dims=2` we have `s.re == [1.0, 2.0]`, which reflects the first column of `v`.

0 commit comments

Comments
 (0)