# SOA transform

**URL:** <https://discourse.julialang.org/t/soa-transform/69769>\
**Category:** Performance\
**Created:** [October 14, 2021, 4:58pm UTC](https://discourse.julialang.org/t/soa-transform/69769 "2021-10-14T16:58:41Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 4:58pm UTC](https://discourse.julialang.org/t/soa-transform/69769/1 "2021-10-14T16:58:41Z")

</div>

I’m playing with a SOA transform ([AoS and SoA - Wikipedia](https://en.wikipedia.org/wiki/AoS_and_SoA)) via StructArrays.jl. My code:

```julia
using StructArrays

function test1()
    n = 10
    v = Vector{Tuple{Int, Int}}(undef, n)
    s = 0
    for i in 1:n
        v[i] = Base.setindex(v[i], 2 * i - 1, 1)
        v[i] = Base.setindex(v[i], 2 * i, 2)
        v1 = v[i][1]
        v2 = v[i][2]
        # v1, v2 = v[i]
        s += v1 + v2
    end
    @assert s == n * (2 * n + 1)
end

function test2()
    n = 100000
    v = StructArray{Tuple{Int, Int}}(undef, n)
    s = 0
    for i in 1:n
        v[i] = Base.setindex(v[i], 2 * i - 1, 1)
        v[i] = Base.setindex(v[i], 2 * i, 2)
        v1 = v[i][1]
        v2 = v[i][2]
        # v1, v2 = v[i]
        s += v1 + v2
    end
    @assert s == n * (2 * n + 1)
end

@btime test1()
@btime test2()

```

Result:

```julia
 33.132 ns (1 allocation: 240 bytes)
  134.200 μs (4 allocations: 1.53 MiB)

```

What am I missing?

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 5:12pm UTC](https://discourse.julialang.org/t/soa-transform/69769/2 "2021-10-14T17:12:44Z")

</div>

Stupid mistake on my side. Correcting the n in test1 results in

```julia
266.100 μs (2 allocations: 1.53 MiB)
  134.600 μs (4 allocations: 1.53 MiB)

```

Back to the drawing board, trying to isolate the problem in my real program and sorry for the noise.

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 6:26pm UTC](https://discourse.julialang.org/t/soa-transform/69769/3 "2021-10-14T18:26:14Z")

</div>

Interesting: I code\_warntyped the critical part of my program and saw red. Now the critical part seem to be the functions

```julia
Base.getindex(alloc::Allocator{T, I}, i::I) where {T, I} =
    @inbounds alloc.store[i]

```

with store a Vector{T} and

```julia
function Base.getindex(alloc::SOAllocator{T, I}, i::I)::T where {T, I}
    @inbounds alloc.store[i]
end

```

with store a StructArray{T}. @code\_warntype gives me

```julia
Variables
  #self#::Core.Const(getindex)
  alloc::Allocator{Tuple{Int64, Int64}, Int64}
  i::Int64
  val::Tuple{Int64, Int64}

Body::Tuple{Int64, Int64}
1 ─ $(Expr(:inbounds, true))
│ %2 = Base.getproperty(alloc, :store)::Vector{Tuple{Int64, Int64}}
│ (val = Base.getindex(%2, i))
│ $(Expr(:inbounds, :pop))
└── return val

```

for the first and

```julia
Variables
  #self#::Core.Const(getindex)
  alloc::SOAllocator{Tuple{Int64, Int64}, Int64}
  i::Int64
  val::Any

Body::Tuple{Int64, Int64}
1 ─ %1 = $(Expr(:static_parameter, 1))::Core.Const(Tuple{Int64, Int64})
│ $(Expr(:inbounds, true))
│ %3 = Base.getproperty(alloc, :store)::StructArray{Tuple{Int64, Int64}, N, C, I} where {N, C<:Union{Tuple, NamedTuple}, I}
│ (val = Base.getindex(%3, i))
│ $(Expr(:inbounds, :pop))
│ %6 = Base.convert(%1, val)::Tuple{Vararg{Any, _A}} where _A
│ %7 = Core.typeassert(%6, %1)::Tuple{Int64, Int64}
└── return %7

```

for the second. Has someone any idea what is going wrong here?

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 14, 2021, 6:31pm UTC](https://discourse.julialang.org/t/soa-transform/69769/4 "2021-10-14T18:31:07Z")

</div>

> [@goerch](#):
>
> ```julia
> v[i] = Base.setindex(v[i], 2 * i - 1, 1)
> v[i] = Base.setindex(v[i], 2 * i, 2)
> 
> ```

just a side note, you can write this simply as

```julia
               v[i] = (2 * i - 1, 2 * i)

```

> [@goerch](#):
>
> ```julia
> │ %3 = Base.getproperty(alloc, :store)::StructArray{Tuple{Int64, Int64}, N, C, I} where {N, C<:Union{Tuple, NamedTuple}, I}
> │ (val = Base.getindex(%3, i))
> 
> ```

I would guess that you have some abstract type in your struct, but hard to tell only with this.

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 6:34pm UTC](https://discourse.julialang.org/t/soa-transform/69769/5 "2021-10-14T18:34:53Z")

</div>

I have:

```julia
mutable struct SOAllocator{T, I} <: AbstractAllocator{T, I}
    n::I
    resizable::Bool
    store::StructArray{T}
    first::I
    last::I
end

```

whereas `StructArray`’s definition is

```julia
struct StructArray{T, N, C<:Tup, I} <: AbstractArray{T, N}
    components::C
end

```

But I have no idea, if and how I have to specify the other type parameters.

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 6:38pm UTC](https://discourse.julialang.org/t/soa-transform/69769/6 "2021-10-14T18:38:57Z")

</div>

At least N=1 seems now obvious. Investigating further…

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 14, 2021, 6:45pm UTC](https://discourse.julialang.org/t/soa-transform/69769/7 "2021-10-14T18:45:34Z")

</div>

You can try this, to see if it is the parameters of the struct array that are missing:

> [@goerch](#):
>
> ```julia
> mutable struct SOAllocator{S, T, I} <: AbstractAllocator{T, I}
> n::I
> resizable::Bool
> store::S
> first::I
> last::I
> end
> 
> ```

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 7:57pm UTC](https://discourse.julialang.org/t/soa-transform/69769/8 "2021-10-14T19:57:27Z")

</div>

Here is the only way I found to specify the C type parameter

```julia
v = StructArray{Tuple{Int, Int}, 1}(undef, 1)
println(typeof(v))
v = StructArray{Tuple{Int, Int}, 1, Tuple{Vector{Int}, Vector{Int}}}((Vector{Int}(undef, 1), Vector{Int}(undef, 1)))
println(typeof(v))

```

Unfortunately the second constructor requires an instance of the components. Is there any way around this?

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [October 14, 2021, 8:22pm UTC](https://discourse.julialang.org/t/soa-transform/69769/9 "2021-10-14T20:22:14Z")

</div>

Something like this and external construction of the store seems to do the job:

```julia
mutable struct SOAllocator{T <: Tuple, I, S <: StructVector{T}} <: AbstractAllocator{T, I}
    n::I
    resizable::Bool
    store::S
    first::I
    last::I
end

```

`@code_warntype` now shows

```julia
Variables
  #self#::Core.Const(getindex)
  alloc::SOAllocator{Tuple{Int64, Int64}, Int64, StructVector{Tuple{Int64, Int64}, Tuple{Vector{Int64}, Vector{Int64}}, Int64}}
  i::Int64
  val::Tuple{Int64, Int64}

Body::Tuple{Int64, Int64}
1 ─ %1 = $(Expr(:static_parameter, 1))::Core.Const(Tuple{Int64, Int64})
│ $(Expr(:inbounds, true))
│ %3 = Base.getproperty(alloc, :store)::StructVector{Tuple{Int64, Int64}, Tuple{Vector{Int64}, Vector{Int64}}, Int64}
│ (val = Base.getindex(%3, i))
│ $(Expr(:inbounds, :pop))
│ %6 = Base.convert(%1, val)::Tuple{Int64, Int64}
│ %7 = Core.typeassert(%6, %1)::Tuple{Int64, Int64}
└── return %7

```

Excellent advice as usual!
