# Understanding the output of @code\_warntype

**URL:** https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191
**Category:** New to Julia
**Tags:** code\_warntype, type-stability
**Created:** [November 27, 2024, 11:37pm UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191 "2024-11-27T23:37:22Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)
#### Post date: [November 27, 2024, 11:37pm UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/1 "2024-11-27T23:37:22Z")

</div>

Hi there,

I simulated some data which I stored as an `Array{Observation}` where `Observation` is a composite type that stores the elements of each observation. I then I wrote a function to compute the log-likelihood of the data as:

```julia
struct Observation{T}
    y::T
    x1::T 
    x2::T
end

# this function will generate the logit data for Q4 
function simulate_logit_data(; n_obs = 300000)
    # define the DGP 
    X1 = 1.0:0.01:10.0
    X2 = 18:0.01:65.0
    α = -1.0
    θ_1 = 0.5
    θ_2 = 0.02
    # pre-allocate the container for the data 
    data = Array{Observation}(undef, n_obs)
    for i in eachindex(data)
        x1 = rand(X1)
        x2 = rand(X2)
        u = rand()
        y = (α + θ_1*x2 + θ_2*x2 + log(u/(1-u)) > 0) ? 1.0 : 0.0
        data[i] = Observation(y, x1, x2)
    end
    return data
end

function Xθ(θ, obs; f = fieldnames(Observation))
    result::Float64 = θ[1]
    for i in 2:length(f)
        result += θ[i]*getfield(obs, f[i])
    end
    return result
end

# this function will compute the log-likelihood given a parameter space point 
function LL(θ, data)
    ll::Float64 = 0.0
    for i in eachindex(data)
        ll += -log(1+exp(Xθ(θ, data[i]))) + getfield(data[i], :y)*Xθ(θ, data[i])
    end 
    return ll
end

function main()
    data = simulate_logit_data()
    @btime LL($(rand(3)), $data)
end 

main()

```

But the performance is not as expected:

1. I am getting a lot of allocations. I don’t seem to understand why this is happening.

```julia
julia> @btime LL($rand(3), $data)
  37.647 ms (2400002 allocations: 36.62 MiB)
-21070.048454177613

```

1. I tried to check for type stability using `@code_warntype` but I can not understand where the issues come. Here I attach a screenshot with partial output (due to space constraint) but which contains the two issues:

```julia
MethodInstance for LL(::Vector{Float64}, ::Vector{Observation})
  from LL(θ, data) @ Main REPL[12]:1
Arguments
  #self#::Core.Const(Main.LL)
  θ::Vector{Float64}
  data::Vector{Observation}
Locals
  @_4::Union{Nothing, Tuple{Int64, Int64}}
  ll::Float64
  i::Int64
  @_7::Float64
  @_8::Any
Body::Float64
1 ── Core.NewvarNode(:(@_4))
│ Core.NewvarNode(:(ll))
│ (@_7 = 0.0)
│ %4 = @_7::Core.Const(0.0)
│ %5 = (%4 isa Main.Float64)::Core.Const(true)
└─── goto #3 if not %5
2 ── goto #4
3 ── Core.Const(:(@_7))
│ Core.Const(:(Base.convert(Main.Float64, %8)))
│ Core.Const(:(Main.Float64))
└─── Core.Const(:(@_7 = Core.typeassert(%9, %10)))
4 ┄─ %12 = @_7::Core.Const(0.0)
│ (ll = %12)
│ %14 = Main.eachindex(data)::Base.OneTo{Int64}
│ (@_4 = Base.iterate(%14))
│ %16 = @_4::Union{Nothing, Tuple{Int64, Int64}}
│ %17 = (%16 === nothing)::Bool
│ %18 = Base.not_int(%17)::Bool
└─── goto #10 if not %18
5 ┄─ %20 = @_4::Tuple{Int64, Int64}
│ (i = Core.getfield(%20, 1))
│ %22 = Core.getfield(%20, 2)::Int64
│ %23 = Main.:+::Core.Const(+)
│ %24 = ll::Float64
│ %25 = Main.:+::Core.Const(+)
│ %26 = Main.:-::Core.Const(-)
│ %27 = Main.log::Core.Const(log)
│ %28 = Main.:+::Core.Const(+)
│ %29 = Main.exp::Core.Const(exp)
│ %30 = Main.Xθ::Core.Const(Main.Xθ)
│ %31 = i::Int64
│ %32 = Base.getindex(data, %31)::Observation
│ %33 = (%30)(θ, %32)::Float64
│ %34 = (%29)(%33)::Float64
│ %35 = (%28)(1, %34)::Float64
│ %36 = (%27)(%35)::Float64
│ %37 = (%26)(%36)::Float64
│ %38 = Main.:*::Core.Const(*)
│ %39 = Main.getfield::Core.Const(getfield)
│ %40 = i::Int64
│ %41 = Base.getindex(data, %40)::Observation
│ %42 = (%39)(%41, :y)::Any
│ %43 = Main.Xθ::Core.Const(Main.Xθ)
│ %44 = i::Int64
│ %45 = Base.getindex(data, %44)::Observation
│ %46 = (%43)(θ, %45)::Float64
│ %47 = (%38)(%42, %46)::Any
│ %48 = (%25)(%37, %47)::Any
│ %49 = (%23)(%24, %48)::Any
│ (@_8 = %49)
│ %51 = @_8::Any
│ %52 = (%51 isa Main.Float64)::Bool
└─── goto #7 if not %52
6 ── goto #8
7 ── %55 = @_8::Any
│ %56 = Base.convert(Main.Float64, %55)::Any
│ %57 = Main.Float64::Core.Const(Float64)
└─── (@_8 = Core.typeassert(%56, %57))
8 ┄─ %59 = @_8::Float64
│ (ll = %59)
│ (@_4 = Base.iterate(%14, %22))
│ %62 = @_4::Union{Nothing, Tuple{Int64, Int64}}
│ %63 = (%62 === nothing)::Bool
│ %64 = Base.not_int(%63)::Bool
└─── goto #10 if not %64
9 ── goto #5
10 ┄ %67 = ll::Float64
└─── return %67

```

thanks a lot in advance.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [November 27, 2024, 11:47pm UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/2 "2024-11-27T23:47:58Z")

</div>

Hard to help if you don’t share a working example of the input `data` and the full printout of `@code_warntype` (you omitted the part relevant to the possibly problematic `@_8::Any`). Don’t share a screenshot of the REPL, copy and paste the text. If space is an issue even with the formatted scroll bar, you can put text in a Hide Details block for us to expand and collapse manually (click gear icon when writing a post/comment).

Right off the bat though, change to `@btime LL($(rand(3)), $data)` or use a setup to do `rand(3)` so you’re not profiling that call and its allocations. `LL(::Vector{Float64}, ::Vector{Observation})` also seems like a problem for type stability, `Observation` is an abstract type. EDIT: now you shared the full `@code_warntype`, type instability does indeed start at:

```julia
│ %39 = Main.getfield::Core.Const(getfield)
│ %40 = i::Int64
│ %41 = Base.getindex(data, %40)::Observation
│ %42 = (%39)(%41, :y)::Any

```

It can’t infer a concrete type for the `y` field of the abstract `Observation`. Do you need the vector to hold any instance of `Observation`? Or is there a particular value for `T` in `Vector{Observation{T}}` you can set?

---

<div class="post-metadata">

### Author: ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)
#### Post date: [November 28, 2024, 12:03am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/3 "2024-11-28T00:03:07Z")

</div>

Oh I see. The compiler only knows `data::Vector{Observation}` and since its general it can’t infer. I do not need the `T` flexibility do I changed it to all fields being `Float64` and now the `Any` red part of the `@code_warntype` output disappeared and:

```julia
@btime LL($(rand(3)), data)
  7.757 ms (1 allocation: 16 bytes)
-58.12176896543346

```

Wow! Okay, I guess that gets rid of the alloctions thanks a lot.

Any clue about the `@_4::Union{Nothing, Tuple{Int64, Int64}}` yellow part?

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [November 28, 2024, 12:06am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/4 "2024-11-28T00:06:35Z")

</div>

That’s just an underlying iteration variable’s type inference. When the iterable runs out of elements, it has a value of `nothing`. In general you don’t have to worry about inferences of 2-3 element `Union`s, especially if they don’t propagate into larger ones or `Any` via multiple arguments in function calls. The compiler just duplicates the code into 2-3 type-stable branches if needed.

---

<div class="post-metadata">

### Author: ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)
#### Post date: [November 28, 2024, 12:54am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/5 "2024-11-28T00:54:25Z")

</div>

Quick follow-up question if thats okay:

My gradient is type-stable but it causing a large number of allocations:

```julia
# compute the gradient 
function ∇LL(
            θ, 
            data;
            f = fieldnames(Observation)
            )
    grad = zeros(length(θ))
    X = zeros(3)
    for i in eachindex(data)
        X .= map(x -> getfield(data[i], x), f)
        grad .+= (getfield(data[i], :y) - exp(Xθ(θ, data[i]))/(1+exp(Xθ(θ, data[i])))) * X
    end
    return grad
end 

```

```julia
@btime ∇LL($(rand(3)), $data)
  20.653 ms (600004 allocations: 22.89 MiB)

```

My understanding is that I am re-using the initial containers every time so why that many allocations?

Thanks!

---

<div class="post-metadata">

### Author: ![carstenbauer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/carstenbauer/32/4981_2.png) [@carstenbauer](https://discourse.julialang.org/u/carstenbauer)
#### Post date: [November 28, 2024, 3:48am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/6 "2024-11-28T03:48:32Z")

</div>

> [@miguelborrero](#):
>
> `map(x -> getfield(data[i], x), f)`

This allocates. Either use broadcasting instead of `map`, such that it is fused with the broadcasted assignment, or don’t use broadcasting at all and just write a loop.

Same for the `grad` assignment in the loop: The RHS isn’t broadcasted and contains a vector scalar multiplication IIUC. This will allocate.

---

<div class="post-metadata">

### Author: ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)
#### Post date: [November 28, 2024, 3:50am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/7 "2024-11-28T03:50:34Z")

</div>

Thanks a lot!

---

<div class="post-metadata">

### Author: ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)
#### Post date: [November 28, 2024, 8:13am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/8 "2024-11-28T08:13:08Z")

</div>

I’d suggest some refactor to make code more generic and avoid explicit container allocation:

```julia
using Random

function simulate_logit_data(
    ;
    X1::AbstractVector{T} = 1.0:0.01:10.0,
    X2::AbstractVector{T} = 18:0.01:65.0,
    α::T = -1.0,
    θ_1::T = 0.5,
    θ_2::T = 0.02,
    n_obs = 300000,
    rng = copy(Random.default_rng()),
) where {T<:Real}
    data = map(1:n_obs) do _
        x1 = rand(rng, X1)
        x2 = rand(rng, X2)
        u = rand(rng)
        y = (α + θ_1*x2 + θ_2*x2 + log(u/(1-u)) > 0) ? 1.0 : 0.0
        return Observation(y, x1, x2)
    end
    return data
end

function Xθ(θ, obs::Observation)
    return float(θ[1] + θ[2] * obs.x1 + θ[3] * obs.x2)
end

function LL(θ, data)
    ll = sum(data) do x
        -log(1+exp(Xθ(θ, x))) + x.y * Xθ(θ, x)
    end
    return ll
end

```

---

<div class="post-metadata">

### Author: ![Vasily\_Pisarev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vasily_pisarev/32/7929_2.png) [@Vasily\_Pisarev](https://discourse.julialang.org/u/Vasily_Pisarev)
#### Post date: [November 28, 2024, 8:35am UTC](https://discourse.julialang.org/t/understanding-the-output-of-code-warntype/123191/9 "2024-11-28T08:35:12Z")

</div>

And for the gradient,

```julia
function ∇LL(
    θ, 
    data::AbstractVector{<:Observation},
)
    grad = fill!(similar(θ), 0) # ensure a mutable container
    for obs in data
        X = (obs.y, obs.x1, obs.x2)
        grad .+= (obs.y - exp(Xθ(θ, obs))/(1+exp(Xθ(θ, obs)))) .* X
    end
    return grad
end

```
