StructArrays.jl getindex cost memory

using StructArrays
using BenchmarkTools

abstract type AbstractSoilParam{T} end

@kwdef mutable struct ParamCampbell{T} <: AbstractSoilParam{T}
  θ_sat::T = 0.287       # [m3 m-3]
  # θ_res::T = 0.075     # [m3 m-3]
  ψ_sat::T = -10.0       # [cm]
  Ksat::T = 34 / 3600    # [cm s-1]
  b::T = 4.0             # [-]
end

mutable struct SoilParam{T}
  par::StructVector{ParamCampbell{T}} # multiple layers
end

begin
  N = 10
  θ_sat = fill(0.287, N)       # [m3 m-3]
  ψ_sat = fill(-10.0, N)       # [cm]
  Ksat = fill(34 / 3600, N)    # [cm s-1]
  b = fill(4.0, N)
  par = StructVector{ParamCampbell{Float64}}((θ_sat, ψ_sat, Ksat, b))
  param = SoilParam{Float64}(par)
end

function test_param(param::SoilParam{Float64}, N::Int)
  @inbounds for k in 1:10_000
    for i in 1:N
      _par = param.par[i]
    end
  end
end
julia> @btime test_param($param, N)
  2.558 ms (100000 allocations: 4.58 MiB)

Any idea how to fix this?

If you’re going to use StructVector you don’t need ParamCampbell to be mutable struct. When you index the vector, it creates a struct which allocates, as Julia cannot yet get rid of it.

Simply defining it as struct should fix the allocations.

Just tried. Remove mutable not work.

This one works:

function solution(par::StructVector{ParamCampbell{T}}, N::Int) where {T<:Real}
  @inbounds for k in 1:10_000
    for i in 1:N
      _par = par[i]
    end
  end
end
@btime solution($par, N)
# 3.200 ns (0 allocations: 0 bytes)

But have no idea why test_param not.

It seems that, in this case, Julia simply compiles it to an empty function, thus not allocating. I’m not sure why indexing causes allocations.

The problem is that StructVector{ParamCampbell{T}} is not a concrete type, since StructVector needs more type parameters to be fully specified.

You could write

struct SoilParam{T, SV <: StructVector{ParamCampbell{T}}}
  par::SV
end

instead.

4 Likes