Type Issues and Memory Mangement


#1

For the following code:


function run0(x, V, n)
    for i=1:n
        V(x);
    end
end

function vA(x,A)
    v = 0.5 * A * x^2;
    return v
end

v1 = x-> vA(x,2);

x0 = rand();

@time run0(x0, v1, 10)
@time run0(x0, v1, 10)
@time run0(x0, v1, 100)
@time run0(x0, v1, 1000)
@time run0(x0, v1, 10000)

A = 2.0;
v2 = x-> vA(x,A);
@time run0(x0, v2, 10)
@time run0(x0, v2, 10)
@time run0(x0, v2, 100)
@time run0(x0, v2, 1000)
@time run0(x0, v2, 10000)

I find that the latter function, v2, is allocating additional memory at each iteration:

  0.005096 seconds (1.54 k allocations: 83.864 KiB)
  0.000003 seconds (4 allocations: 160 bytes)
  0.000002 seconds (4 allocations: 160 bytes)
  0.000002 seconds (4 allocations: 160 bytes)
  0.000001 seconds (4 allocations: 160 bytes)
  0.007968 seconds (1.65 k allocations: 88.158 KiB)
  0.000004 seconds (34 allocations: 640 bytes)
  0.000008 seconds (304 allocations: 4.844 KiB)
  0.000055 seconds (3.00 k allocations: 47.031 KiB)
  0.000514 seconds (30.00 k allocations: 468.906 KiB)

Looking at the @code_warntype, there are clearly type instabilities in the latter function:

julia> @code_warntype v2(x0)
Variables:
  #self# <optimized out>
  x::Float64
  v <optimized out>

Body:
  begin 
      SSAValue(0) = Main.A
      $(Expr(:inbounds, false))
      # meta: location /Users/gideonsimpson/code/weighted_ensemble/forpost3.jl vA 9
      # meta: location operators.jl * 424
      SSAValue(1) = ((0.5 * SSAValue(0))::Any * (Base.mul_float)(x::Float64, x::Float64)::Float64)::Any
      # meta: pop location
      # meta: pop location
      $(Expr(:inbounds, :pop))
      return SSAValue(1)
  end::Any

But I’m unclear on why these are occurring and how best to write code to avoid them.


#2

A lives in Main: global scope -> instability?


#3

It’s certainly a global variable, but how does one handle this then? It often makes sense to write functions that have additional parameters, (A in this case) which will be set (globally) for the lifetime of the program. At least that’s the way I am used to writing code. Or is this merely something that one has to accept when working with Julia?


#4

What if you put it inside a module?


#5

Let block or const?


#6

A module is still a dynamic scope, so that doesn’t help inference. Instead, just make A const, or declare the variable before usage in functions. Or let block like @cortner suggested.


#7

Declaring

const A = 2.0;

works as suggested. Now how would I address the following variation:

const A = 2.0;
v2 = x-> vA(x,A);

function V2(x)
  return sum(v2,x)
end

X0 = rand(100);

@time run0(X0, V2, 10)
@time run0(X0, V2, 10)
@time run0(X0, V2, 100)
@time run0(X0, V2, 1000)
@time run0(X0, V2, 10000)

which returns:

  0.041488 seconds (22.00 k allocations: 1.192 MiB)
  0.000007 seconds (14 allocations: 320 bytes)
  0.000038 seconds (104 allocations: 1.719 KiB)
  0.000300 seconds (1.00 k allocations: 15.781 KiB)
  0.001827 seconds (10.00 k allocations: 156.406 KiB)

#8

const v2 = x-> vA(x,A); but don’t ask me why?!?


#9

Exactly for the same reason as A: v2 is a global variable and needs to be a constant to avoid boxing.


#10

so there is a significant difference between function v2(x) and v2 = x -> ?


#11

I thought anonymous functions are automatically const because

julia> f = x -> x^2
(::#1) (generic function with 1 method)
julia> f(x) = x
ERROR: cannot define function f; it already has a value

#12
julia> a = 1
1

julia> a(x) = 2
ERROR: cannot define function a; it already has a value

julia> const f = (x) -> (x^2)
(::#1) (generic function with 1 method)

julia> f(x,y) = x*y
(::#1) (generic function with 2 methods)

julia> f(2)
4

julia> f(2,3)
6

julia> g(x) = x^2
g (generic function with 1 method)

julia> g = 2
ERROR: invalid redefinition of constant g

So function f(x) .... or simply f(x) = ... make f constant by default.


#13

Thank you, that helps.


#14
julia> f = x->x^2+x
(::#7) (generic function with 1 method)

julia> f = 2.34
2.34

#15

If you need a global variable where you need to be able to change its value, the typical way is to use a const Ref e.g.

const X = Ref(1)
getx() = X[]
setx(x) = X[] = x

#16

Thanks all. Is there any simpler alternative to declaring const A= or using the Ref construct?