I am writing code that will involve solving an ODE that is composed of heterogeneous components. This requires evaluating the right hand side by iterating over those heterogeneous components.
For this, I use a struct Device
which is composed of a DynamicDevice
type. This DynamicDevice type is an abstract type that could be TypeA, TypeB, etc. Then, I will use multiple dispatch to write a right hand side function evaluation for each component type.
If I create a generic Vector that holds a bunch of Devices, however, this leads to type instability and memory allocations. In my mind, the Device’s should look the same in memory, it is only the subfield Device.type
that has different type.
Is there any way/pattern to write this to avoid type instability yet being able to store a collection of heterogeneous structs?
The code below has an example to reproduce what I describe. I benchmark with BenchmarkTools and I have denoted each of these benchmarks with P1, P2, etc.
P1 does not lead to type instability since devicesA
is a
5-element Vector{Device{TypeA}}
I guess the compiler sees that in this case all the elements are of the same type and thus optimizes the structure? In P3 however, devicesB is
5-element Vector{Device}:
and this leads to impaired performance/memory issues.
Also, why do P2 and P4 allocate memory?
using BenchmarkTools
N = 5
abstract type DynamicDevice end
struct TypeA <: DynamicDevice
data::Float64
end
struct Device{D <: DynamicDevice}
type::D
ptr::Int
end
# pointers system
struct System
device_type::Vector{Device}
end
# residual function
function f!(x, device_type::TypeA, ptr)
x[ptr] = 1.0
end
function f!(x, devices)
for i in 1:length(devices)
device = devices[i]
f!(x, device.type, device.ptr)
end
end
function f!(x, system::System)
f!(x, system.device_type)
end
x = zeros(Float64, N)
devicesA = [Device(TypeA(1.0), 1) for _ in 1:N]
# P1
@btime f!(x, devicesA)
dev = TypeA(1.0)
# P2
@btime f!(x, dev, 1)
devicesB = Vector{Device}(undef, N)
for i in 1:N
devicesB[i] = Device(TypeA(1.0), 1)
end
system = System(devicesB)
#P3
@btime f!(x, system)
dev = TypeA(1.0)
#P4
@btime f!(x, dev, 1)