Avoid allocation for local array

I’m curious if there is a way to have a small, local array without allocations.

Here is a short, contrived example:

julia> function foo(x)
           arr = [1.0, 2.0, 3.0]
           if x < 1
               arr[1] *= x
           elseif x < 2
               arr[2] *= x
           elseif x < 3
               arr[3] *= x
           end
           return sum(arr)
       end
foo (generic function with 1 method)

julia> @time foo(1.9)
  0.000006 seconds (1 allocation: 48 bytes)
7.8

I’m assuming the allocations are due to arr.

StaticArrays doesn’t seems to be a solution since they are immutable and MVectors are heap-allocated.

Any suggestions?

Not entire sure what you are looking for.

Does below help you out?

using StaticArrays 
using BenchmarkTools
arr = [1.0, 2.0, 3.0]

function foo!(arr,x) 
           if x < 1
               arr[1] *= x
           elseif x < 2
               arr[2] *= x
           elseif x < 3
               arr[3] *= x
           end
           return sum(arr)
end
foo!(arr,1.)

6.0

@btime foo!($arr,$1.)

3.958 ns (0 allocations: 0 bytes)

Perhaps you could give Bumper.jl a go? I’ve not really used it myself, but it seems like it might do the trick:

using Bumper
using BenchmarkTools
function foo(x)
    @no_escape begin
        arr = @alloc(typeof(x), 3)
        arr .= 1:3
        if x < 1
            arr[1] *= x
        elseif x < 2
            arr[2] *= x
        elseif x < 3
            arr[3] *= x
        end
        sum(arr)
    end
end
## --
foo(2.0) # 9.0
@btime foo(2.0) # 16.994 ns (0 allocations: 0 bytes)

Not super fast for this toy example though.

2 Likes

Thanks for the suggestion, but I’m trying to avoid having the user pass in a scratch buffer for computation.

Thanks, this appears to meet my need. I’ll check it out!

Did you try it? If it does not escape it may be stack allocated by the compiler. (use @btime or you may get an apparent allocation from the output to the REPL).

Thanks for the suggestion! I hadn’t tried it and just assumed it would be heap allocated. Looks like you’re right and the compiler turned it into a stack allocation.

julia> function foo2(x)
           arr = MVector(1.0, 2.0, 3.0)
           if x < 1
               arr[1] *= x
           elseif x < 2
               arr[2] *= x
           elseif x < 3
               arr[3] *= x
           end
           return sum(arr)
       end
foo2 (generic function with 1 method)

julia> @btime foo2(1.9)
  3.333 ns (0 allocations: 0 bytes)
7.8