How to avoid vectors of abstract types?

Hello,

I started using Julia intensively a few months ago, and I keep coming across situations where I have vectors of abstract types, which should be avoided for performance reasons. I can’t seem to find satisfying solutions to these situations.

Here is a simplified example of the last time I had this problem:

I’m doing some machine learning experiments, during which I log several metrics, which number and type can differ between experiments. For this, I define an AbstractMetric abstract type, and, for each metric, one concrete type I can dispatch on. For example:

# Abstract type
abstract type AbstractMetric end

function log!(m::AbstractMetric; kwargs...)
    push!(m.history, m(kwargs...))
end
# Loss concrete types
struct Loss <: AbstractMetric
    history::Vector{Float64}
end

Loss() = Loss(Float64[])

function (m::Loss)(; loss, groundtruths, predictions, kwargs...)
    return sum(loss(y, y_pred) for (y, y_pred) in zip(groundtruths, predictions))
end

# Accuracy concrete type
struct Accuracy <: AbstractMetric
    history::Vector{Float64}
end

Accuracy() = Loss(Float64[])

function (m::Accuracy)(; groundtruths, predictions, kwargs...)
    return mean(y == y_pred for (y, y_pred) in zip(groundtruths, predictions))
end

# Other metrics
...

I store all the current metrics in a vector and use a for loop calling the log! method.

Problem: the vector is a Vector{AbstractMetric}.
Is there a better implementation that can avoid that ?

if the length is expected to be small, use a Tuple

2 Likes

Yeah, there are a lot of performance problems that could be avoided if people used tuples instead of arrays and namedtuples instead of dicts for small arrays and dicts…

Have you actually determined that this is an issue, by profiling? If not, then unless one iteration of your training loop is extremely fast then I doubt the overhead from dynamic dispatch is adding much.

1 Like

Thank you !
Tuple seems the way to go here, didn’t think of that.

I’m not sure this in particular is an issue, but profiling shows that metrics computation currently take about 70% of the training loop time. That’s why I’m trying to solve potential performance issues.