Search code examples
arraysclosuresjuliaallocationlexical-scope

Array allocation in julia closure


I wonder is it a good idea to allocate temporary arrays in let-block, which wraps some function? Some toy example: instead of

function foo(x)
    y = zeros(100)
    for i in 1 : 100
        y[i] = 2*x[i] - 1
    end
    do_something(y)
end

I will write something like:

let
  const y = zeros(100) # otherwise foo will be type-unstable due to 'global' y
  function foo(x)
    for i in 1 : 100
        y[i] = 2*x[i] - 1
    end
    do_something(y)
  end
end

It can be easily checked via @benchmark macro that in the second case memory for y-array will be allocated only once, which significantly improves performance (in my not-toy case). I'm wondering is it a "julian-way" to do such things?


Solution

  • I will give you an answer for Julia 1.0. For earlier versions of Julia the situation would be a bit different.

    Point 1. Your code with let will not run under Julia 1.0 as let creates local scope and in local scope you are not allowed to use const.

    Point 2. It is fully OK to do something like this in global scope:

    const y = zeros(100) # otherwise foo will be type-unstable due to 'global' y
    function foo(x)
        for i in 1 : 100
            y[i] = 2*x[i] - 1
        end
        do_something(y)
    end
    

    and you will have good performance as Julia 1.0 knows that y has constant type and will optimize it. The consequence is that you will have y in global scope and foo in methods table (i.e. you can call foo using its name).

    Point 3. You can also use let block like this:

    const foo = let y = zeros(100)
        function inner_foo(x)
            for i in 1 : 100
                y[i] = 2*x[i] - 1
            end
            do_something(y)
        end
    end
    

    This time y is defined only in local scope and does not leak out to global scope. Also inner_foo is not defined in global scope therefore you have to assign the return value of let block to a variable foo that then can be used to make calls (I make it a const to improve performance if it gets used in some functions later on)

    Point 4. Note, however, that this almost identical code will not be as good, as Julia 1.0 has problems with type inference of variable y (hopefully this will be fixed in the future)

    const foo = let
        y = zeros(100)
        function inner_foo(x)
            for i in 1 : 100
                y[i] = 2*x[i] - 1
            end
            do_something(y)
        end
    end
    

    In summary: the decision if you use let block depends mostly on what you have to be visible in global scope (as what is defined in let is not visible in global scope) and if you use let block it is best to define variables you want to use as a part of let block definition.