# Significant decrease in performance after seemingly irrelevant changes

**URL:** <https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796>\
**Category:** Performance\
**Created:** [June 28, 2019, 10:18am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796 "2019-06-28T10:18:50Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 28, 2019, 10:18am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/1 "2019-06-28T10:18:50Z")

</div>

Hi!

I recently revised some aspects of a large project I’m working on, just moving around some data/functions without altering the behavior of the code. However, it resulted in significant decrease in performance in seemingly other parts of the code.

The performance decrease stems from the calcInput! function of a struct I have (see the bottom of this post). There are some changes in how this function behaves (e.g. how \gamma parameter is handled), I am having performance issues in places never expected.

As an example, consider near the last line, `u ./= asum`. Profiling in the old code with dt = 0.001 gives:

```julia
8 ...BaseController.jl:37; calcInput!(::SpecificationM...
              7 ./broadcast.jl:751; materialize!
               1 ./abstractarray.jl:75; axes
                1 ./array.jl:155; size
               6 ./broadcast.jl:792; copyto!
                6 ./broadcast.jl:837; copyto!
                 6 ./simdloop.jl:73; macro expansion
                  5 ./broadcast.jl:838; macro expansion
                   2 ./array.jl:769; setindex!
                   3 ./broadcast.jl:507; getindex
                    2 ./broadcast.jl:546; _broadcast_getindex
                     2 ./broadcast.jl:570; _getindex
                      2 ./broadcast.jl:540; _broadcast_getindex
                       2 ./array.jl:731; getindex
                    1 ./broadcast.jl:547; _broadcast_getindex
                     1 ./broadcast.jl:574; _broadcast_getindex_evalf
                      1 ./float.jl:401; /
                  1 ./int.jl:53; macro expansion

```

While the new code produces:

```julia
1117 ...aseController.jl:37; calcInput!(::Specification...
              16 ./broadcast.jl:1163; broadcasted(::Function, :...
              382 ./broadcast.jl:1166; broadcasted(::Function, :...
               330 ./broadcast.jl:1168; broadcasted
                31 ./broadcast.jl:176; Base.Broadcast.Broadcaste...
                 20 ./broadcast.jl:176; Type
                  20 ./broadcast.jl:167; Type
              21 ./broadcast.jl:751; materialize!(::Array{Floa...
               11 ./broadcast.jl:792; copyto!
                4 ./broadcast.jl:836; copyto!
                 4 ./broadcast.jl:819; preprocess
                  4 ./broadcast.jl:822; preprocess_args
                   4 ./broadcast.jl:823; preprocess_args
                    4 ./broadcast.jl:820; preprocess
                     4 ./broadcast.jl:813; broadcast_unalias
                6 ./broadcast.jl:837; copyto!
                 6 ./simdloop.jl:73; macro expansion
                  6 ./broadcast.jl:838; macro expansion
                   5 ./broadcast.jl:507; getindex
                    2 ./broadcast.jl:546; _broadcast_getindex
                     2 ./broadcast.jl:570; _getindex
                      2 ./broadcast.jl:540; _broadcast_getindex
                       2 ./array.jl:731; getindex
                    3 ./float.jl:401; _broadcast_getindex
                   1 ./int.jl:53; +
                1 ./simdloop.jl:0; copyto!

```

Just that line of division now takes an entire second in running my code, whereas I am operating on the same data types! Note that the way parameters are allocated and passed to this function, e.g. the input `u`, as well as the number of times the function is called, has not changed. Even the line before, checking `asum > 0`, now takes 57 ticks instead of the old 1, and profiling gives no indication on where that time is spent:

```julia
57 ...aseController.jl:36; calcInput!(::Specification...
              6 ./operators.jl:286; >(::Float64, ::Int64)
               5 ./float.jl:448; <
               1 ./float.jl:488; <
                1 ./float.jl:452; <

```

Any ideas on what I should look into that can produce such a behavior?

Thanks,  
Tusike

Here is the code for the struct with the calcInput! function.  
Before changes:

```julia
mutable struct SimpleBaseController <: AbstractBaseController
    BTs::Vector{AbstractBarrierTransform}
    Δ::Vector{Float64}

    # pre-allocate variables used in calculations
    dρdx::Vector{Float64}
    vi::Vector{Float64}

    function SimpleBaseController(BTs::Vector{AbstractBarrierTransform}, Δ::Vector{Float64})
        new(BTs, Δ)
    end
end

function init(specManager::SpecificationManager, bc::SimpleBaseController, agent::Agent)
    # pre-allocate variables used in calculations
    bc.dρdx = Vector{Float64}(undef, agent.n)
    bc.vi = Vector{Float64}(undef, agent.m)
end

function calcInput!(specManager::SpecificationManager, bc::SimpleBaseController, agent::Agent, γ::Vector{Float64}, ρ::Vector{Float64}, t::Float64, u::Vector{Float64})
    u .= 0.0
    asum = 0.0
    for i = 1:specManager.M
        specManager.APs[i].dρdx!(specManager.APs[i], agent.x, bc.dρdx)
        κ, Γ = calcκ(bc.BTs[i], γ[i], ρ[i], t)
        agent.dynamics.applyG!(agent.x, bc.dρdx, bc.vi)
        if (ρ[i] < Γ)
            ai = (Γ - ρ[i])/(Γ - γ[i])
            u .+= (ai*κ/(bc.Δ[i] + dot(bc.vi,bc.vi)))*bc.vi
        else
            ai = 0.0
        end
        asum += ai
    end

    if (asum > 0)
        u ./= asum
    end
end

```

After changes:

```julia
mutable struct SimpleBaseController <: AbstractBaseController
    BTs::Vector{AbstractBarrierTransform}
    Δ::Vector{Float64}

    # pre-allocate variables used in calculations
    dρdx::Vector{Float64}
    vi::Vector{Float64}

    function SimpleBaseController(BTs::Vector{AbstractBarrierTransform}, Δ::Vector{Float64})
        new(BTs, Δ)
    end
end

function init(specManager::SpecificationManager, bc::SimpleBaseController, agent::Agent)
    # pre-allocate variables used in calculations
    bc.dρdx = Vector{Float64}(undef, agent.dynamics.n)
    bc.vi = Vector{Float64}(undef, agent.dynamics.m)
end

function calcInput!(specManager::SpecificationManager, bc::SimpleBaseController, agent::Agent, ρ::Vector{Float64}, tIndex::Int, t::Float64, u::Vector{Float64})
    u .= 0.0
    asum = 0.0
    for i = 1:specManager.M
        specManager.TSs[i].AP.dρdx!(specManager.TSs[i].AP, agent.x, bc.dρdx)
        κ, Γ = calcκ(bc.BTs[i], specManager.TSs[i].γ[tIndex], ρ[i], t)
        agent.dynamics.applyG!(agent.x, bc.dρdx, bc.vi)
        if (ρ[i] < Γ)
            ai = (Γ - ρ[i])/(Γ - specManager.TSs[i].γ[tIndex])
            u .+= (ai*κ/(bc.Δ[i] + dot(bc.vi,bc.vi)))*bc.vi
        else
            ai = 0.0
        end
        asum += ai
    end

    if (asum > 0)
        u ./= asum
    end
end

```

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 28, 2019, 10:53am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/2 "2019-06-28T10:53:04Z")

</div>

Just as a heads up. I wanted to look into what happens so I copy pasted the provided code into the REPL and got a bunch of errors of things not being defined. I now move on with my day without looking more into it.

I would estimate that the chance of getting help is about 10x larger if you give a minimal example that works when copy pasted into the REPL.

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 28, 2019, 11:00am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/3 "2019-06-28T11:00:33Z")

</div>

I perfectly understand that, however, as mentioned this is part of a large project which I cannot post as a whole. And since I have no idea where the decrease in performance stems from, I could not create a minimal working example. That being said, of course I am only looking for any ideas on what I could or should investigate and what could lead to the described strange behavior, instead of a complete solution to the problem. (For example, from people who are able to conclude something based on the profile reports).

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 28, 2019, 11:03am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/4 "2019-06-28T11:03:48Z")

</div>

Have you run `code_warntype` on it?

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 28, 2019, 11:12am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/5 "2019-06-28T11:12:30Z")

</div>

No, I did not know about @code\_warntype, but I have printed out the type of the variables involved, and e.g. in the `u ./= asum` line `u` is always a vector of Float64’s, and asum is a Float64. But I will run that macro and let you know what I get.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 28, 2019, 11:13am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/6 "2019-06-28T11:13:20Z")

</div>

> [@Tusike](#):
>
> but I have printed out the type of the variables involved

Yeah, these are the runtime types but `@code_warntype` tells you about the inferred types which are computed before the code is run and what is used to optimize the code.

I recommend reading the performance section in the manual: [Performance Tips · The Julia Language](https://docs.julialang.org/en/v1/manual/performance-tips/index.html).

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 28, 2019, 11:33am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/7 "2019-06-28T11:33:41Z")

</div>

Great, thanks, it’s becoming much clearer now! The @code\_warntype does indeed suggest I have issues which I did not have before due to now accessing e.g. the \gamma values (which must be Float64) through an abstract type at %35. I am not sure how to resolve this yet, but at least I definitely have a direction to follow.

Thanks again!!

```julia
Body::Any
│╻╷╷ materialize!21 1 ── %1 = (Base.arraysize)(u, 1)::Int64
││╻╷╷╷ axes │ %2 = (Base.slt_int)(%1, 0)::Bool
│││┃│││ map │ (Base.ifelse)(%2, 0, %1)
││╻ copyto! │ invoke Base.Broadcast.fill!(_8::Array{Float64,1}, 0.0::Float64)
│╻ getproperty23 │ %5 = (Base.getfield)(specManager, :M)::Int64
││╻╷╷╷ Type │ %6 = (Base.sle_int)(1, %5)::Bool
│││╻ unitrange_last │ (Base.sub_int)(%5, 1)
││││ │ %8 = (Base.ifelse)(%6, %5, 0)::Int64
││╻╷╷ isempty │ %9 = (Base.slt_int)(%8, 1)::Bool
││ └─── goto #3 if not %9
││ 2 ── goto #4
││ 3 ── goto #4
│ 4 ┄─ %13 = φ (#2 => true, #3 => false)::Bool
│ │ %14 = φ (#3 => 1)::Int64
│ │ %15 = φ (#3 => 1)::Int64
│ │ %16 = (Base.not_int)(%13)::Bool
│ └─── goto #18 if not %16
│ 5 ┄─ %18 = φ (#4 => 0.0, #17 => %95)::Any
│ │ %19 = φ (#4 => %14, #17 => %101)::Int64
│ │ %20 = φ (#4 => %15, #17 => %102)::Int64
│╻ getproperty24 │ %21 = (Base.getfield)(specManager, :TSs)::Array{AbstractTemporalSpecification,1}
│╻ getindex │ %22 = (Base.arrayref)(true, %21, %19)::AbstractTemporalSpecification
│╻ getproperty │ %23 = (Base.getfield)(%22, :AP)::Any
│ │ %24 = (Base.getproperty)(%23, :dρdx!)::Any
│╻ getproperty │ %25 = (Base.getfield)(specManager, :TSs)::Array{AbstractTemporalSpecification,1}
│╻ getindex │ %26 = (Base.arrayref)(true, %25, %19)::AbstractTemporalSpecification
│╻ getproperty │ %27 = (Base.getfield)(%26, :AP)::Any
││ │ %28 = (Base.getfield)(agent, :x)::Array{Float64,1}
││ │ %29 = (Base.getfield)(bc, :dρdx)::Array{Float64,1}
│ │ (%24)(%27, %28, %29)
│╻ getproperty25 │ %31 = (Base.getfield)(bc, :BTs)::Array{Utilities.AbstractBarrierTransform,1}
│╻ getindex │ %32 = (Base.arrayref)(true, %31, %19)::Utilities.AbstractBarrierTransform
│╻ getproperty │ %33 = (Base.getfield)(specManager, :TSs)::Array{AbstractTemporalSpecification,1}
│╻ getindex │ %34 = (Base.arrayref)(true, %33, %19)::AbstractTemporalSpecification
│╻ getproperty │ %35 = (Base.getfield)(%34, :γ)::Any
│ │ %36 = (Base.getindex)(%35, tIndex)::Any
│╻ getindex │ %37 = (Base.arrayref)(true, ρ, %19)::Float64
│ │ %38 = AgentManager.calcκ::Core.Compiler.Const(Utilities.calcκ, false)
│ │ %39 = (isa)(%32, Utilities.LinSigmoidBarrierTransform)::Bool
│ │ %40 = (isa)(%36, Float64)::Bool
│ │ %41 = (and_int)(%39, %40)::Bool
│ └─── goto #7 if not %41
│ 6 ── %43 = π (%32, Utilities.LinSigmoidBarrierTransform)
│ │ %44 = π (%36, Float64)
│ │ %45 = invoke %38(%43::Utilities.LinSigmoidBarrierTransform, %44::Float64, %37::Float64, _7::Float64)::Tuple{Float64,Float64}
│ └─── goto #10
│ 7 ── %47 = (isa)(%32, Utilities.LinExpBarrierTransform)::Bool
│ │ %48 = (isa)(%36, Float64)::Bool
│ │ %49 = (and_int)(%47, %48)::Bool
│ └─── goto #9 if not %49
│ 8 ── %51 = π (%32, Utilities.LinExpBarrierTransform)
│ │ %52 = π (%36, Float64)
│ │ %53 = invoke %38(%51::Utilities.LinExpBarrierTransform, %52::Float64, %37::Float64, _7::Float64)::Tuple{Float64,Float64}
│ └─── goto #10
│ 9 ── %55 = (AgentManager.calcκ)(%32, %36, %37, t)::Tuple{Float64,Float64}
│ └─── goto #10
│ 10 ┄ %57 = φ (#6 => %45, #8 => %53, #9 => %55)::Tuple{Float64,Float64}
││╻ indexed_iterate │ %58 = (Base.getfield)(%57, 1)::Float64
│╻ indexed_iterate │ %59 = (Base.getfield)(%57, 2)::Float64
│╻ getproperty26 │ %60 = (Base.getfield)(agent, :dynamics)::AbstractSystemDynamics
││ │ %61 = (Base.getfield)(%60, :applyG!)::Any
││ │ %62 = (Base.getfield)(agent, :x)::Array{Float64,1}
││ │ %63 = (Base.getfield)(bc, :dρdx)::Array{Float64,1}
││ │ %64 = (Base.getfield)(bc, :vi)::Array{Float64,1}
│ │ (%61)(%62, %63, %64)
│╻ getindex27 │ %66 = (Base.arrayref)(true, ρ, %19)::Float64
│╻ < │ %67 = (Base.lt_float)(%66, %59)::Bool
│ └─── goto #12 if not %67
│╻ getindex28 11 ─ %69 = (Base.arrayref)(true, ρ, %19)::Float64
│╻ - │ %70 = (Base.sub_float)(%59, %69)::Float64
│╻ getproperty │ %71 = (Base.getfield)(specManager, :TSs)::Array{AbstractTemporalSpecification,1}
│╻ getindex │ %72 = (Base.arrayref)(true, %71, %19)::AbstractTemporalSpecification
│╻ getproperty │ %73 = (Base.getfield)(%72, :γ)::Any
│ │ %74 = (Base.getindex)(%73, tIndex)::Any
│ │ %75 = (%59 - %74)::Any
│ │ %76 = (%70 / %75)::Any
│ 29 │ %77 = Base.Broadcast.materialize!::Core.Compiler.Const(Base.Broadcast.materialize!, false)
│ │ %78 = Base.Broadcast.broadcasted::Core.Compiler.Const(Base.Broadcast.broadcasted, false)
│ │ %79 = (%76 * %58)::Any
│╻ getproperty │ %80 = (Base.getfield)(bc, :Δ)::Array{Float64,1}
│╻ getindex │ %81 = (Base.arrayref)(true, %80, %19)::Float64
│╻ getproperty │ %82 = (Base.getfield)(bc, :vi)::Array{Float64,1}
││ │ %83 = (Base.getfield)(bc, :vi)::Array{Float64,1}
│╻ dot │ %84 = LinearAlgebra.BLAS.dot::typeof(LinearAlgebra.BLAS.dot)
││ │ %85 = invoke %84(%82::Array{Float64,1}, %83::Array{Float64,1})::Float64
│╻ + │ %86 = (Base.add_float)(%81, %85)::Float64
│ │ %87 = (%79 / %86)::Any
│╻ getproperty │ %88 = (Base.getfield)(bc, :vi)::Array{Float64,1}
│ │ %89 = (%87 * %88)::Any
│ │ %90 = (%78)(AgentManager.:+, u, %89)::Any
│ │ (%77)(u, %90)
│ └─── goto #13
│ 12 ─ nothing
│ 33 13 ┄ %94 = φ (#11 => %76, #12 => 0.0)::Any
│ │ %95 = (%18 + %94)::Any
││╻ == │ %96 = (%20 === %8)::Bool
││ └─── goto #15 if not %96
││ 14 ─ goto #16
││╻ + 15 ─ %99 = (Base.add_int)(%20, 1)::Int64
│╻ iterate └─── goto #16
│ 16 ┄ %101 = φ (#15 => %99)::Int64
│ │ %102 = φ (#15 => %99)::Int64
│ │ %103 = φ (#14 => true, #15 => false)::Bool
│ │ %104 = (Base.not_int)(%103)::Bool
│ └─── goto #18 if not %104
│ 17 ─ goto #5
│ 36 18 ─ %107 = φ (#16 => %95, #4 => 0.0)::Any
│ │ %108 = (%107 > 0.0)::Any
│ └─── goto #46 if not %108
│╻ macro expansion37 19 ─ %110 = (AgentManager.typeof)(%107)::DataType
││╻╷╷╷╷╷╷ #repr#326 │ %111 = (Base.sle_int)(1, 1)::Bool
│││┃││││ #sprint └─── goto #21 if not %111
││││┃││││ isempty 20 ─ %113 = (Base.sle_int)(1, 0)::Bool
│││││┃││ iterate └─── goto #22
│ 21 ─ nothing
││││││┃│ iterate 22 ┄ %116 = φ (#20 => %113, #21 => false)::Bool
│││││││┃ iterate └─── goto #24 if not %116
││││││││ 23 ─ invoke Base.getindex(()::Tuple{}, 1::Int64)
││││││││ └─── $(Expr(:unreachable))
││││││││ 24 ─ goto #26
││││││││ 25 ─ $(Expr(:unreachable))
│││││││ 26 ┄ goto #27
│││││╻ iterate 27 ─ goto #28
│││││ 28 ─ goto #29
││││ 29 ─ %125 = invoke Base.:(#sprint#325)(nothing::Nothing, 0::Int64, sprint::Function, show::Function, %110::DataType)::String
││││ └─── goto #30
│││ 30 ─ goto #31
││ 31 ─ goto #32
│ 32 ─ invoke Base.println("typeof(asum) = "::String, %125::String)
││╻╷╷╷╷╷╷╷ repr │ %130 = (Base.sle_int)(1, 1)::Bool
│││┃│││││ #repr#326 └─── goto #34 if not %130
││││┃│││││ #sprint 33 ─ %132 = (Base.sle_int)(1, 0)::Bool
│││││┃│││ isempty └─── goto #35
│ 34 ─ nothing
││││││┃││ iterate 35 ┄ %135 = φ (#33 => %132, #34 => false)::Bool
│││││││┃│ iterate └─── goto #37 if not %135
││││││││┃ iterate 36 ─ invoke Base.getindex(()::Tuple{}, 1::Int64)
│││││││││ └─── $(Expr(:unreachable))
│││││││││ 37 ─ goto #39
│││││││││ 38 ─ $(Expr(:unreachable))
││││││││ 39 ┄ goto #40
││││││╻ iterate 40 ─ goto #41
││││││ 41 ─ goto #42
│││││ 42 ─ %144 = invoke Base.:(#sprint#325)(nothing::Nothing, 0::Int64, sprint::Function, show::Function, Array{Float64,1}::Type)::String
│││││ └─── goto #43
││││ 43 ─ goto #44
│││ 44 ─ goto #45
││ 45 ─ invoke Base.println("typeof(u) = "::String, %144::String)
│ 38 │ %149 = Base.Broadcast.materialize!::Core.Compiler.Const(Base.Broadcast.materialize!, false)
│ │ %150 = Base.Broadcast.broadcasted::Core.Compiler.Const(Base.Broadcast.broadcasted, false)
│ │ %151 = (%150)(AgentManager.:/, u, %107)::Any
│ │ %152 = (%149)(u, %151)::Any
│ └─── return %152
│ 46 ─ return

```

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 28, 2019, 11:42am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/8 "2019-06-28T11:42:38Z")

</div>

Indeed, explicitly labeling the return type of the variables I am accessing through abstract types in my code gets me back to the previous computation speed, though I assume there is a more elegant solution to this. I have not yet figured out how to avoid using arrays of abstract types, which I guess where the main problem stems from.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 28, 2019, 11:55am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/9 "2019-06-28T11:55:15Z")

</div>

> [@Tusike](#):
>
> though I assume there is a more elegant solution to this.

You can either make sure your struct are concretely typed (maybe by adding type parameters) or if that is not possible, introduce a function barrier so the “kernel” of the computation is its own function and can be specialized on the input value.

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [June 28, 2019, 10:56pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/10 "2019-06-28T22:56:22Z")

</div>

For example, instead of

```julia
mutable struct SimpleBaseController <: AbstractBaseController
    BTs::Vector{AbstractBarrierTransform}

```

perhaps

```julia
mutable struct SimpleBaseController{B <: AbstractBarrierTransform} <: AbstractBaseController
    BTs::Vector{B}

```

Now, if the vectors really do have to contain a mix of different `AbstractBarrierTransform`s, how many different types must the vector contain?  
If it’s only two, you could make the vector’s element type be a union of both concrete types. That should still be fast.

Alternatively, do these `AbstractBarrierTransform`s really have to be different concrete types?  
How different are they in terms of data layout and behavior?  
Instead of having `BarrierTransform1` and `BarrierTransform2`, could you make one of the fields an `@emum`, indicating which BarrierTransform it is, and handle it this way?

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 29, 2019, 9:02am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/11 "2019-06-29T09:02:05Z")

</div>

Yep, that’s the plan now, I think a combination of the two might work the best.

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 29, 2019, 9:24am UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/12 "2019-06-29T09:24:03Z")

</div>

The `AbstractBarrierTransform`s essentially each describe a different function (to evaluate a barrier term), and the type contains the parameters of this function as its data, and different constructors to evaluate these parameters based on given hyperparameters. I am using different concrete types to conveniently call the same function from my code, e.g. `evaluateBarrier(myBarrierTransform)`, and allow multiple dispatch to call the function I need.

The vectors really do have to contain a mix of abstract elements. However, there won’t be many elements, and once initialized their number remains fixed! I was thinking it should thus be possible to use Tuples, such as:

```julia
mutable struct SimpleBaseController{B <: Tuple(Vararg{AbstractBarrierTransform}} <: AbstractBaseController
    BTs::B

```

I will test this and hope for the best.

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 29, 2019, 1:51pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/13 "2019-06-29T13:51:20Z")

</div>

I managed to get better performance than before, with a combination of parameterizing my structs with Tuples of abstract types, as well as introducing the function barriers to create specializations.

What I still don’t understand is why I needed to do the latter. A (fully working, standalone) example at the bottom illustrates the core of my problem. I define a function that accepts a tuple of abstract types, in order to compile specialized versions of this function. In the function, I access an element `x` in the abstract types. Now if I pass a given tuple to the function, a specialized version is created, and the compiler should figure out that `x` is always a Float, no? However, `@code_warntype` shows that this is not the case, and accessing the element `x` actually results in a union of all possible datatypes in the struct that composes the tuple… Why doesn’t the compiler know in this scenario that `x` is a float?

```julia
abstract type ABCs end

struct A <: ABCs
    x::Float64
    y::Vector{Float64}
    z::Int
end
struct B <: ABCs
    x::Float64
    y::Vector{Float64}
    z::String
end

function getSumX(letters::Tuple{Vararg{ABCs}})
    sum = 0.0
    for i = 1:length(letters)
        sum += letters[i].x
    end
    return sum
end

a1 = A(2.0, [1.0], 1)
a2 = A(3.0, [1.0], 1)
b = B(4.0, [1.0], "1")

@code_warntype getSumX((a1, a2, b))

```

Result of `@code_warntype`:

```julia
Body::Any
17 1 ── (Base.ifelse)(true, 3, 0) │╻╷╷ Colon
   │ %2 = (Base.slt_int)(3, 1)::Bool ││╻╷╷ isempty
   └─── goto #3 if not %2 ││
   2 ── goto #4 ││
   3 ── goto #4 ││
   4 ┄─ %6 = φ (#2 => true, #3 => false)::Bool │
   │ %7 = φ (#3 => 1)::Int64 │
   │ %8 = φ (#3 => 1)::Int64 │
   │ %9 = (Base.not_int)(%6)::Bool │
   └─── goto #15 if not %9 │
   5 ┄─ %11 = φ (#4 => 0.0, #14 => %28)::Any │
   │ %12 = φ (#4 => %7, #14 => %34)::Int64 │
   │ %13 = φ (#4 => %8, #14 => %35)::Int64 │
18 │ %14 = (Base.getfield)(letters, %12, true)::Union{A, B} │╻ getindex
   │ %15 = (isa)(%14, A)::Bool │
   └─── goto #7 if not %15 │
   6 ── %17 = π (%14, A) │
   │ %18 = (Base.getfield)(%17, :x)::Union{Float64, Int64, Array{Float64,1}} │╻ getproperty
   └─── goto #10 │
   7 ── %20 = (isa)(%14, B)::Bool │
   └─── goto #9 if not %20 │
   8 ── %22 = π (%14, B) │
   │ %23 = (Base.getfield)(%22, :x)::Union{Float64, Array{Float64,1}, String} │╻ getproperty
   └─── goto #10 │
   9 ── (Core.throw)(ErrorException("fatal error in type inference (type bound)")) │
   └─── $(Expr(:unreachable)) │
   10 ┄ %27 = φ (#6 => %18, #8 => %23)::Union{Float64, Int64, Array{Float64,1}, String} │
   │ %28 = (%11 + %27)::Any │
   │ %29 = (%13 === 3)::Bool ││╻ ==
   └─── goto #12 if not %29 ││
   11 ─ goto #13 ││
   12 ─ %32 = (Base.add_int)(%13, 1)::Int64 ││╻ +
   └─── goto #13 │╻ iterate
   13 ┄ %34 = φ (#12 => %32)::Int64 │
   │ %35 = φ (#12 => %32)::Int64 │
   │ %36 = φ (#11 => true, #12 => false)::Bool │
   │ %37 = (Base.not_int)(%36)::Bool │
   └─── goto #15 if not %37 │
   14 ─ goto #5 │
20 15 ─ %40 = φ (#13 => %28, #4 => 0.0)::Any │
   └─── return %40   

```

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 29, 2019, 2:44pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/14 "2019-06-29T14:44:11Z")

</div>

The answer is similar to [Non-allocating loop over a set of structs - #6 by kristoffer.carlsson](https://discourse.julialang.org/t/non-allocating-loop-over-a-set-of-structs/25643/6).

A loop is a chunk of code that is repeated. Here, `letters` will have a different type in each iteration so the code for the loop body that is compiled need to be general enough to handle this.

And like in the other answer, you can use Unrolled.jl to unroll the loop and thereby avoiding the constraint of a loop.

```julia
julia> @unroll function getSumX(letters::Tuple{Vararg{ABCs}})
           sum = 0.0
           @unroll for i = 1:length(letters)
               sum += letters[i].x
           end
           return sum
       end;

julia> @code_warntype getSumX((a1, a2, b))
Body::Float64
1 ─ %1 = π (0.0, Core.Compiler.Const(0.0, false))
│ %2 = π (1, Core.Compiler.Const(1, false))
│ %3 = (Base.getfield)(letters, %2, true)::A
│ %4 = (Base.getfield)(%3, :x)::Float64
│ %5 = (Base.add_float)(%1, %4)::Float64
│ %6 = π (2, Core.Compiler.Const(2, false))
│ %7 = (Base.getfield)(letters, %6, true)::A
│ %8 = (Base.getfield)(%7, :x)::Float64
│ %9 = (Base.add_float)(%5, %8)::Float64
│ %10 = π (3, Core.Compiler.Const(3, false))
│ %11 = (Base.getfield)(letters, %10, true)::B
│ %12 = (Base.getfield)(%11, :x)::Float64
│ %13 = (Base.add_float)(%9, %12)::Float64
└── return %13

```

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 29, 2019, 2:58pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/15 "2019-06-29T14:58:15Z")

</div>

I understand that the code has to be general enough to handle the different types in each iteration, but it seems that it already does this by checking the type of the concrete variable in the current iteration, as seen in the following snippet of the `@code_warnttype` result:

```julia
18 │ %14 = (Base.getfield)(letters, %12, true)::Union{A, B} │╻ getindex
   │ %15 = (isa)(%14, A)::Bool │
   └─── goto #7 if not %15 │
   6 ── %17 = π (%14, A) │
   │ %18 = (Base.getfield)(%17, :x)::Union{Float64, Int64, Array{Float64,1}} │╻ getproperty
   └─── goto #10 │
   7 ── %20 = (isa)(%14, B)::Bool │
   └─── goto #9 if not %20 │
   8 ── %22 = π (%14, B) │
   │ %23 = (Base.getfield)(%22, :x)::Union{Float64, Array{Float64,1}, String} │╻ getproperty
   └─── goto #10 │
   9 ── (Core.throw)(ErrorException("fatal error in type inference (type bound)")) │
   └─── $(Expr(:unreachable))     

```

At first this seems perfectly fine to me. `%14` gets the current iterate of the tuple, which can be a Union{A, B}, as expected. The code then executes differently whether it is A or B, as checked at %15 and %20. To me this seems like a general enough way to handle things… Then, at the different executions - e.g. %17, %18, these lines only run if the iterate is a specific (known!) type, so surely the compiler should know at %18 that `%17.x` is a float, since `%17` is of type `A`. I don’t see where it gets the idea that `x` could also be the same type as the other data types in `A`.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [June 29, 2019, 3:19pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/16 "2019-06-29T15:19:12Z")

</div>

You are right, the compiler is smarter than I thought here and does do the union splitting.

Related, I think there is a regression in this particular case on the version of julia you are using vs julia 1.1.

On 1.1 I get

```julia
julia> @code_warntype getSumX((a1, a2, b))
Body::Float64
1 ── goto #17 if not true
2 ┄─ %2 = φ (#1 => 0.0, #16 => %32)::Float64
│ %3 = φ (#1 => 1, #16 => %38)::Int64
│ %4 = φ (#1 => 1, #16 => %39)::Int64
│ %5 = (Base.getfield)(letters, %3, true)::Union{A, B}
│ %6 = (isa)(%5, A)::Bool
└─── goto #4 if not %6
3 ── %8 = π (%5, A)
│ %9 = (Base.getfield)(%8, :x)::Float64
└─── goto #7
4 ── %11 = (isa)(%5, B)::Bool
└─── goto #6 if not %11
5 ── %13 = π (%5, B)
│ %14 = (Base.getfield)(%13, :x)::Union{Float64, Array{Float64,1}, String}
└─── goto #7
6 ── (Core.throw)(ErrorException("fatal error in type inference (type bound)"))
└─── $(Expr(:unreachable))
7 ┄─ %18 = φ (#3 => %9, #5 => %14)::Union{Float64, Int64, Array{Float64,1}, String}
│ %19 = (isa)(%18, Float64)::Bool
└─── goto #9 if not %19
8 ── %21 = π (%18, Float64)
│ %22 = (Base.add_float)(%2, %21)::Float64
└─── goto #12
9 ── %24 = (isa)(%18, Int64)::Bool
└─── goto #11 if not %24
10 ─ %26 = π (%18, Int64)
│ %27 = (Base.sitofp)(Float64, %26)::Float64
│ %28 = (Base.add_float)(%2, %27)::Float64
└─── goto #12
11 ─ %30 = (%2 + %18)::Float64
└─── goto #12
12 ┄ %32 = φ (#8 => %22, #10 => %28, #11 => %30)::Float64
│ %33 = (%4 === 3)::Bool
└─── goto #14 if not %33
13 ─ goto #15
14 ─ %36 = (Base.add_int)(%4, 1)::Int64
└─── goto #15
15 ┄ %38 = φ (#14 => %36)::Int64
│ %39 = φ (#14 => %36)::Int64
│ %40 = φ (#13 => true, #14 => false)::Bool
│ %41 = (Base.not_int)(%40)::Bool
└─── goto #17 if not %41
16 ─ goto #2
17 ┄ %44 = φ (#15 => %32, #1 => 0.0)::Float64
└─── return %44

```

so the output value is correctly inferred and performance is good:

```julia
julia> @btime getSumX($(a1, a2, b))
  4.578 ns (0 allocations: 0 bytes)
9.0

```

on the master branch I have I get

```julia
julia> @btime getSumX($(a1, a2, b))
  80.251 ns (6 allocations: 96 bytes)
9.0

```

with the `Any` inferred.

I also don’t really understand the

```julia
│ %14 = (Base.getfield)(%13, :x)::Union{Float64, Array{Float64,1}, String}

```

leading to

```julia
7 ┄─ %18 = φ (#3 => %9, #5 => %14)::Union{Float64, Int64, Array{Float64,1}, String}

```

Edit: I posted [https://github.com/JuliaLang/julia/issues/32452](https://github.com/JuliaLang/julia/issues/32452).

---

<div class="post-metadata">

**Author:** ![Tusike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tusike/32/36610_2.png) [@Tusike](https://discourse.julialang.org/u/Tusike)\
**Post date:** [June 29, 2019, 3:29pm UTC](https://discourse.julialang.org/t/significant-decrease-in-performance-after-seemingly-irrelevant-changes/25796/17 "2019-06-29T15:29:57Z")

</div>

Exactly, I was just about to comment on the `%18` part! Still, it’s good to know that I at least seem to have grasped the theory of all this and it just might be the compiler that is not fully optimal.

Edit: indeed I should have mentioned long ago but I’m at version 1.0.1 right now.
