Hey everyone,
Apologies for the frequent questions, but I just dont quite get behind the compiler as of yet. I have the following problem: I have a custom struct and some data, and I want to call functions based on the tuple (struct, data). Something like this:
using Distributions
using BenchmarkTools
abstract type Supertype{F<:VariateForm} end
struct MyStruct{F,T} <: Supertype{F}
feat1::Vector{T}
feat2::Matrix{T}
distr::Vector{Distribution{F}}
MyStruct{F,T}(feat1, feat2, distr) where {F,T} = new(feat1, feat2, distr)
end
#Methods
MyStruct(feat1::AbstractVector{T}, feat2::AbstractMatrix{T}, distr::AbstractVector{<:Distribution{F}}) where {F,T} = MyStruct{F,T}(feat1, feat2, distr)
#Initialize object and some random data
MyObject = MyStruct([.4, .6], [0.9 0.1; 0.1 0.9], [Normal(0,1), Normal(5,1)])
obs = rand(10^6,1)
Now I define an intermediate function:
#Function1
function fun1(mystruct::Supertype, observations::AbstractArray)
hcat(map(distribution -> logpdf.(distribution, observations), mystruct.distr)...)
end
log_evidence = fun1(MyObject, obs)
Based on the output of this function, I want to do some further calculation. I can do that either by directly using the output of this function, or by calling the function within a function (and thus keeping the (struct, data) tuple:
#Function2 - directly take input of fun1 / fun2 independent of struct in minimal example
function fun2(mystruct::Supertype, log_evidence::AbstractArray)
alpha = zeros( size(log_evidence) )
for t in 1:size(log_evidence)[1]
alpha[t,:] = log_evidence[t,:]
end
end
#Function3 - Calculate fun1 within function3
function fun3(mystruct::Supertype, observations::AbstractArray)
log_evidence = fun1(mystruct, obs)
alpha = zeros( size(log_evidence) )
for t in 1:size(log_evidence)[1]
alpha[t,:] = log_evidence[t,:]
end
end
Obviously, fun3
is slower, but even though the function within fun3
hardly consumes memory or time, fun3
consumes 3 times as much memory as fun2
and is 25 times slower, see:
fun1(MyObject, obs)
fun2(MyObject, log_evidence)
fun3(MyObject, obs)
@benchmark fun1(MyObject, obs)
@benchmark fun2(MyObject, log_evidence)
@benchmark fun3(MyObject, obs)
(1) What is the reason for this gigantic discrepancy? I dont quite get how so much memory can be allocated if the actual inner function does not even consume 10% of the additional memory of fun3
against fun2
, nevermind the speed discrepancy. There seem to be many threads on this but I still failed to grasp it, i.e. I dont get why this should be a āglobalā variable problem?
(2) Also, I could just use directly fun2
, but would rather keep the order of input (mystruct, data) for all similar functions. I could archieve similar things if I first define āfun2ā and then apply Multiple Dispatch on it like this:
#Not implemented
function fun2(MyObject, obs)
fun2(MyObject, fun1(MyObject, obs) )
end
This gives similar performance for some reason while I can keep the input order (mystruct, data). However, I did not get my head around using Multiple dispatch for this problem, as mystruct has to be the same in both functions, and log_evidence and obs have the same type, i.e. I dont know how I would discriminate these two cases such that the compiler knows which method to use?
Thank you again and apologies for the wall of text!
Best regards,
##################################################################################
Edit: A solution would be by defining fun2 differently and then do MD, like this:
#Not implemented - fun2 independent of struct in minimal example
function fun2(distr, log_evidence::AbstractArray)
alpha = zeros( size(log_evidence) )
for t in 1:size(log_evidence)[1]
alpha[t,:] = log_evidence[t,:]
end
end
function fun2(MyObject, obs)
fun2(MyObject.distr, fun1(MyObject, obs) )
end
but I would rather avoid this as the idea is to apply āfun2ā for different Types with the same observationsā¦