Home > OS >  Why does the allocated memory increase with the number of iterations of calling this lambda in sum()
Why does the allocated memory increase with the number of iterations of calling this lambda in sum()

Time:01-24

I'm just trying to experiment with Julia, so I wrote this code:

function f()
    i::Int = 0
    return () -> i  = 1
end

@time (h = f(); sum(h() for _ in 1:1000000))
@time (h = f(); sum(h() for _ in 1:10000000))

When I ran it I obtain

  0.178856 seconds (3.07 M allocations: 49.691 MiB, 4.24% gc time, 33.87% compilation time)
500000500000
  1.128918 seconds (30.07 M allocations: 461.536 MiB, 4.79% gc time, 7.03% compilation time)
50000005000000

We see that it requires a lot of memory, that grows as the size of the range increases. But I don't understand why, I'd expect it to require a constant amount of memory, basically the memory to store the lambda with its captured variable (and the variables required to compute the sum). So the question is: why does the required memory grow with the size of the range?

(I know that I can obtain the same result simply with sum(1:1000000) or with other approaches, but this is not what I am interested in, here I'm just trying to understand this specific example)

CodePudding user response:

There are two issues in your function: boxing and binding in global scope. Here is an example how both can be resolved:

julia> function f()
           i = Ref(0)
           return () -> i[]  = 1
       end
f (generic function with 1 method)

julia> @time let h = f()
           sum(h() for _ in 1:1000000)
       end
  0.027134 seconds (113.72 k allocations: 6.053 MiB, 98.97% compilation time)
500000500000

julia> @time let h = f()
           sum(h() for _ in 1:10000000)
       end
  0.024634 seconds (109.76 k allocations: 5.821 MiB, 96.81% compilation time)
50000005000000

julia> @time let h = f()
           sum(h() for _ in 1:100000000)
       end
  0.031428 seconds (109.76 k allocations: 5.826 MiB, 81.02% compilation time)
5000000050000000

julia> @time let h = f()
           sum(h() for _ in 1:1000000000)
       end
  0.088952 seconds (109.77 k allocations: 5.824 MiB, 8.99% gc time, 36.63% compilation time)
500000000500000000

CodePudding user response:

because the closure over i is boxed, and accessing a boxed variable requires allocation, and thus it increase linearly as the number of time you access it.

The memory allocation is showing the total amount of memory allocated, not the peak memory.

and you can see the type instability:

julia> @code_warntype h()
MethodInstance for (::var"#3#4")()
  from (::var"#3#4")() @ Main REPL[1]:3
Arguments
  #self#::var"#3#4"
Locals
  i::Union{}
Body::Int64
1 ─ %1  = Core.getfield(#self#, :i)::Core.Box
│   %2  = Core.isdefined(%1, :contents)::Bool
└──       goto #3 if not %2
2 ─       goto #4
3 ─       Core.NewvarNode(:(i))
└──       i
4 ┄ %7  = Core.getfield(%1, :contents)::Any
│   %8  = Core.typeassert(%7, Main.Int)::Int64
│   %9  = (%8   1)::Int64
│    = Core.getfield(#self#, :i)::Core.Box
│    = Base.convert(Main.Int, %9)::Int64
│    = Core.typeassert(, Main.Int)::Int64
│         Core.setfield!(, :contents, )
└──       return %9
  • Related