Strange memory allocations

I have the following function:

function residual!(res, yd, y::MVector{S, SimFloat}, s::KPS3, time) where S
    if false
        T = S-2 # T: three times the number of particles excluding the origin
        segments = div(T,6) - KITE_PARTICLES
        # unpack the vectors y and yd
        # extract the data for the winch simulation
        length,  v_reel_out  = y[end-1],  y[end]
        lengthd, v_reel_outd = yd[end-1], yd[end]
        # extract the data of the particles
        y_  = @view y[1:end-2]
        yd_ = @view yd[1:end-2]
        # unpack the vectors y and yd
        part  = reshape(SVector{T}(y_),  Size(3, div(T,6), 2))
        partd = reshape(SVector{T}(yd_), Size(3, div(T,6), 2))
    #     pos1 = part[:,:,1]
    #     pos1, vel1 = part[:,:,1], part[:,:,2]
    #     pos = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(pos1[:,i-1]) end for i in 1:div(T,6)+1)
    #     vel = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(vel1[:,i-1]) end for i in 1:div(T,6)+1)
    #     posd1, veld1 = partd[:,:,1], partd[:,:,2]
    #     posd = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(posd1[:,i-1]) end for i in 1:div(T,6)+1)
    #     veld = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(veld1[:,i-1]) end for i in 1:div(T,6)+1)
    else
        part = reshape(SVector{S}(y),  Size(3, div(S,6), 2))
        partd = reshape(SVector{S}(yd),  Size(3, div(S,6), 2))
        pos1, vel1 = part[:,:,1], part[:,:,2]
        pos = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(pos1[:,i-1]) end for i in 1:div(S,6)+1)
        vel = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(vel1[:,i-1]) end for i in 1:div(S,6)+1)
        posd1, veld1 = partd[:,:,1], partd[:,:,2]
        posd = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(posd1[:,i-1]) end for i in 1:div(S,6)+1)
        veld = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(veld1[:,i-1]) end for i in 1:div(S,6)+1)
    end


    # update parameters
    pos_kite = pos[div(S,6)+1]
    s.vel_kite .= vel[div(S,6)+1]
    delta_t = time - s.t_0
    delta_v = s.v_reel_out - s.last_v_reel_out
    s.segment_length = (s.l_tether + s.last_v_reel_out * delta_t + 0.5 * delta_v * delta_t^2) / div(S,6)
    s.c_spring = s.set.c_spring / s.segment_length
    s.damping  = s.set.damping / s.segment_length
    s.beta = calc_elevation(s)

    # call core calculation routines
    vec_c = SVector{3, SimFloat}(pos[s.set.segments] - pos_kite)     # convert to SVector to avoid allocations
    v_app = SVector{3, SimFloat}(s.v_wind - s.vel_kite)
    calc_set_cl_cd!(s, vec_c, v_app)
    calc_aero_forces(s, pos_kite, s.vel_kite, s.rho, s.steering) # force at the kite
    loop(s, pos, vel, posd, veld, s.res1, s.res2)
  
    # copy and flatten result
    for i in 2:div(S,6)+1
        for j in 1:3
           @inbounds res[3*(i-2)+j]              = s.res1[i][j]
           @inbounds res[3*(div(S,6))+3*(i-2)+j] = s.res2[i][j]
        end
    end
    if norm(res) < 1e5
        # println(norm(res))
        for i in eachindex(pos)
            @inbounds s.pos[i] .= pos[i]
        end
    end
    # @assert ! isnan(norm(res))
    s.iter += 1
    nothing
end

It does not allocate any memory. If I uncomment the line # pos1 = part[:,:,1] it suddenly allocates a lot of memory, even though this part of the code is never executed. How can that happen?

The full test can be found here: KiteModels.jl/bench3.jl at main Β· ufechner7/KiteModels.jl Β· GitHub

Does the uncommenting make it type unstable? What does @code_warntype say?

Is getindex of SVector non-allocating?

Simplified test case:

using StaticArrays, LinearAlgebra

const KVec3    = MVector{3, Float64}

Base.@kwdef mutable struct KPS3{S, T, P}
    v_apparent::T =       zeros(S, 3)
end

const kps3= KPS3{Float64, KVec3, 6+4+1}()
const KITE_PARTICLES = 4

const WINCH = false

function residual!(res, yd, y::MVector{S, Float64}, s::KPS3, time) where S
    if WINCH
        T = S-2 # T: three times the number of particles excluding the origin
        segments = div(T,6) - KITE_PARTICLES
        length,  v_reel_out  = y[end-1],  y[end]
        lengthd, v_reel_outd = yd[end-1], yd[end]
        # extract the data of the particles
        y_  = @view y[1:end-2]
        yd_ = @view yd[1:end-2]
        part  = reshape(SVector{T}(y_),  Size(3, div(T,6), 2))
        partd = reshape(SVector{T}(yd_), Size(3, div(T,6), 2))
        # if you comment the following line there are no allocations
        pos1 = part[:,:,1]
    else
        part = reshape(SVector{S}(y),  Size(3, div(S,6), 2))
        partd = reshape(SVector{S}(yd),  Size(3, div(S,6), 2))
        pos1, vel1 = part[:,:,1], part[:,:,2]
        pos = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(pos1[:,i-1]) end for i in 1:div(S,6)+1)
        vel = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(vel1[:,i-1]) end for i in 1:div(S,6)+1)
        posd1, veld1 = partd[:,:,1], partd[:,:,2]
        posd = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(posd1[:,i-1]) end for i in 1:div(S,6)+1)
        veld = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(veld1[:,i-1]) end for i in 1:div(S,6)+1)
    end
    nothing
end

function test_residual()
    if WINCH
        y0 = MVector{62, Float64}([13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 150.0, 0.0])
        yd0= MVector{62, Float64}([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0])
    else
        y0 = MVector{60, Float64}([13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
        yd0= MVector{60, Float64}([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81])
    end
    time = 0.1
    res1 = zeros(SVector{6, KVec3})
    res2 = deepcopy(res1)
    res = reduce(vcat, vcat(res1, res2))
    # call first time to compile
    residual!(res, yd0, y0, kps3, time)
    # measure allocations
    @allocated residual!(res, yd0, y0, kps3, time)
end

test_residual()

This shows 1728 allocation which disappear if I comment the line pos1 = part[:,:,1] .

The allocations also disappear if I use WINCH=true.

So all works fine if I execute the code that I want to execute with the correct sized array as input.

It allocates if there is dead code inside an if false; ...; end block that would operate on a wrongly sized array if executed.

Not sure if this is a bug…

julia> @code_warntype test_residual()
MethodInstance for test_residual()
  from test_residual() in Main at /home/ufechner/repos/KiteModels/test/test_residual2.jl:40
Arguments
  #self#::Core.Const(test_residual)
Locals
  b1::Base.RefValue{Int64}
  b0::Base.RefValue{Int64}
  res::MVector{36, Float64}
  res2::SVector{6, MVector{3, Float64}}
  res1::SVector{6, MVector{3, Float64}}
  time::Float64
  yd0::MVector{60, Float64}
  y0::MVector{60, Float64}
Body::Int64
1 ─       Core.NewvarNode(:(b1))
β”‚         Core.NewvarNode(:(b0))
β”‚         Core.NewvarNode(:(res))
β”‚         Core.NewvarNode(:(res2))
β”‚         Core.NewvarNode(:(res1))
β”‚         Core.NewvarNode(:(time))
β”‚         Core.NewvarNode(:(yd0))
β”‚         Core.NewvarNode(:(y0))
└──       goto #3 if not Main.WINCH
2 ─       Core.Const(:(Core.apply_type(Main.MVector, 62, Main.Float64)))
β”‚         Core.Const(:(Base.vect(13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 150.0, 0.0)))
β”‚         Core.Const(:(y0 = (%10)(%11)))
β”‚         Core.Const(:(Core.apply_type(Main.MVector, 62, Main.Float64)))
β”‚         Core.Const(:(Base.vect(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0)))
β”‚         Core.Const(:(yd0 = (%13)(%14)))
└──       Core.Const(:(goto %23))
3 β”„ %17 = Core.apply_type(Main.MVector, 60, Main.Float64)::Core.Const(MVector{60, Float64})
β”‚   %18 = Base.vect(13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)::Vector{Float64}
β”‚         (y0 = (%17)(%18))
β”‚   %20 = Core.apply_type(Main.MVector, 60, Main.Float64)::Core.Const(MVector{60, Float64})
β”‚   %21 = Base.vect(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81)::Vector{Float64}
β”‚         (yd0 = (%20)(%21))
β”‚         (time = 0.1)
β”‚   %24 = Core.apply_type(Main.SVector, 6, Main.KVec3)::Core.Const(SVector{6, MVector{3, Float64}})
β”‚         (res1 = Main.zeros(%24))
β”‚         (res2 = Main.deepcopy(res1))
β”‚   %27 = Main.vcat(res1, res2)::SVector{12, MVector{3, Float64}}
β”‚         (res = Main.reduce(Main.vcat, %27))
β”‚         Main.residual!(res, yd0, y0, Main.kps3, time::Core.Const(0.1))
β”‚         $(Expr(:meta, :force_compile))
β”‚   %31 = Core.apply_type(Base.Ref, Base.Int64)::Core.Const(Ref{Int64})
β”‚         (b0 = (%31)(0))
β”‚   %33 = Core.apply_type(Base.Ref, Base.Int64)::Core.Const(Ref{Int64})
β”‚         (b1 = (%33)(0))
β”‚         Base.gc_bytes(b0)
β”‚         Main.residual!(res, yd0, y0, Main.kps3, time::Core.Const(0.1))
β”‚         Base.gc_bytes(b1)
β”‚   %38 = Base.getindex(b1)::Int64
β”‚   %39 = Base.getindex(b0)::Int64
β”‚   %40 = (%38 - %39)::Int64
└──       return %40

I would not know what to look for, though…

Its a bit annoying in the sense that I cannot execute different branches of my code depending on a constant without causing allocations…

Created a bug report: Dead code is causing memory allocations Β· Issue #48798 Β· JuliaLang/julia Β· GitHub

OK, this is a duplicate of performance of captured variables in closures Β· Issue #15276 Β· JuliaLang/julia Β· GitHub .

Their is an easy workaround: Just rename some of the variables that appear in both branches of the code:

using StaticArrays, LinearAlgebra

const KVec3    = MVector{3, Float64}

Base.@kwdef mutable struct KPS3{S, T, P}
    v_apparent::T =       zeros(S, 3)
end

const kps3= KPS3{Float64, KVec3, 6+4+1}()
const KITE_PARTICLES = 4

const WINCH = false

function residual!(res, yd, y::MVector{S, Float64}, s::KPS3, time) where S
    if WINCH
        T = S-2 # T: three times the number of particles excluding the origin
        segments = div(T,6) - KITE_PARTICLES
        length,  v_reel_out  = y[end-1],  y[end]
        lengthd, v_reel_outd = yd[end-1], yd[end]
        # extract the data of the particles
        y_  = @view y[1:end-2]
        yd_ = @view yd[1:end-2]
        part  = reshape(SVector{T}(y_),  Size(3, div(T,6), 2))
        partd = reshape(SVector{T}(yd_), Size(3, div(T,6), 2))
        # if you comment the following line there are no allocations
        pos1_, vel1_ = part[:,:,1], part[:,:,2]
        pos = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(pos1_[:,i-1]) end for i in 1:div(T,6)+1)
        vel = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(vel1_[:,i-1]) end for i in 1:div(T,6)+1)
        posd1_, veld1_ = partd[:,:,1], partd[:,:,2]
        posd = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(posd1_[:,i-1]) end for i in 1:div(T,6)+1)
        veld = SVector{div(T,6)+1}(if i==1 SVector(0.0,0,0) else SVector(veld1_[:,i-1]) end for i in 1:div(T,6)+1)
    else
        part = reshape(SVector{S}(y),  Size(3, div(S,6), 2))
        partd = reshape(SVector{S}(yd),  Size(3, div(S,6), 2))
        pos1, vel1 = part[:,:,1], part[:,:,2]
        pos = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(pos1[:,i-1]) end for i in 1:div(S,6)+1)
        vel = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(vel1[:,i-1]) end for i in 1:div(S,6)+1)
        posd1, veld1 = partd[:,:,1], partd[:,:,2]
        posd = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(posd1[:,i-1]) end for i in 1:div(S,6)+1)
        veld = SVector{div(S,6)+1}(if i==1 SVector(0.0,0,0) else SVector(veld1[:,i-1]) end for i in 1:div(S,6)+1)
    end
    nothing
end

function test_residual()
    if WINCH
        y0 = MVector{62, Float64}([13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 150.0, 0.0])
        yd0= MVector{62, Float64}([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0])
    else
        y0 = MVector{60, Float64}([13.970413450119487, 0.0, 21.238692070636343, 27.65581376097752, 0.0, 42.66213714321849, 40.976226230518435, 0.0, 64.314401166278, 53.87184032029182, 0.0, 86.22231803750196, 66.28915240374937, 0.0, 108.4048292516046, 78.17713830204762, 0.0, 130.87545423106485, 79.56930502428155, 0.0, 135.70836376062155, 80.90383289255747, 0.0, 137.7696816741141, 80.60126812407692, 2.4016533873456325, 135.3023287520457, 80.60126812407692, -2.4016533873456325, 135.3023287520457, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
        yd0= MVector{60, Float64}([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81, 0.0, 0.0, -9.81])
    end
    time = 0.1
    res1 = zeros(SVector{6, KVec3})
    res2 = deepcopy(res1)
    res = reduce(vcat, vcat(res1, res2))
    # call first time to compile
    residual!(res, yd0, y0, kps3, time)
    # measure allocations
    @allocated residual!(res, yd0, y0, kps3, time)
end

test_residual()

I had to rename pos1, vel1, posd1, veld1 to pos1_, vel1_, posd1_, veld1_ to avoid any memory allocations.

Still not clear to me: Why these 4 variables and not any others…

1 Like

Because these four variables are captured by closures which are created by the generator expressions.

To illustrate:

julia> function gengen()
           a = if true; 1 else 1 end
           if true; b=1 else b=1 end
           return (a+x for x in 1:5), (b+x for x in 1:5)
       end
gengen (generic function with 1 method)

julia> g1, g2 = gengen()
       g1.f.a, g2.f.b
(1, Core.Box(1))

julia> g1..., g2...
(2, 3, 4, 5, 6, 2, 3, 4, 5, 6)

julia> g2.f.b.contents = 11
       g1..., g2...
(2, 3, 4, 5, 6, 12, 13, 14, 15, 16)

The rule is currently very (too) simple: if a local variable identifier is syntactically assigned more than once, or if it’s assigned in a conditional, and if it is captured by a closure, then it is boxed. It leads to a lot of unnecessary boxing. And boxes are currently very (too) simple: they expressly lose type information. It leads to a lot of unnecessarily slow code, and is a bug that must be fixed before Julia can be considered a serious language imo.

1 Like