What is the most idiomatic way to allocate a (reusable) buffer for a function?

Suppose I have a function f that allocates, for instance the following

function sum_of_fractions(a)
    v = map(x -> 1/(x+a), 1:10)
    return sum(v)
end

Assume for the sake of this discussion that I cannot get rid of the temporary vector v by converting the operation to a loop. To get rid of the allocations, one could pass a vector v to use as a buffer:

function sum_of_fractions(a, buffer)
    buffer .= map(x -> 1/(x+a), 1:10)
    return sum(buffer)

In this way, I can reuse the same buffer under multiple calls, without allocating more memory:

v = zeros(10)
for k = 1:100
    @show sum_of_squares(k, v)
end

This looks like it should be quite a common pattern in Julia. Is there a more idiomatic way to write it? What is the best way to do it?

I think that providing a buffer is OK and quite Julian.
In this simple case you could also avoid to materialize the array:

sum_of_fractions(a) =sum(x->1/(x+a),1:10)

or

sum_of_fractions_bis(a) = sum(x->inv(x+a),1:10)
4 Likes

One common way is to provide the buffer as an optional keyword parameter, such as:

function foo(x; buffer = similar(x))
    ...
end

so that the function can be used with and without providing the buffer.

5 Likes

But in this case, how do deal with the ! convention ?

2 Likes

pirates-caribbean-code

I dont think the ! is truly neccessary if the function only mutates an optional(!) key word argument.

Another common pattern is

function dostuff!(buffer,args...)
  [...]
end
dostuff(args...) = dostuff!(allocateBuffer(args...),args...)
6 Likes

NB: even though a buffer was passed to it, this still allocates a vector to store the result of map. You’d need something like this to avoid allocating:

function y = sum_of_fractions!(buffer, a)
    map!(x -> 1/(x+a), buffer, 1:10)
    return sum(buffer)
end
4 Likes

For example:

function sum_of_fractions!(buffer::AbstractVector, a::Real)
    # https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured
    fun = let a = a
        x -> 1/(x + a)
    end

    map!(fun, buffer, 1:length(buffer))
    sum(buffer)
end

function sum_of_fractions(::Type{T}, len::Integer, a::Real) where {T}
  sum_of_fractions!(zeros(T, len), a)
end

Regarding the order of the parameters of the mutating function, see the manual’s style guide: section.

Instead of putting the closure into a local variable, it might sometimes be nicer to create it with a global function:

function my_callable_object(b)
    let a = b
        x -> 1/(x + a)
    end
end

Then create your function by calling my_callable_object, like my_callable_object(a).

3 Likes

Actually in this case I would not use it, and have the name of the variable (buffer) be very clear in that it won’t use the content of buffer in the computation. The other alternative, mentioned above, is to have two methods:

function foo(x)
    buffer = similar(x)
    return foo!(buffer, x)
end

function foo!(buffer, x)
    ...
end
3 Likes

Also of note to know about in case it helps: Bumper.jl. I believe it’s very stable and useful now on Linux at least. It will also Work on Windows, less tested, and if it seems to work there or on any platform then it’s as good as on Linux. It’s just that Windows doesn’t overcommit, so it could possibly run out of space in extreme situations.

Is this idiomatic? I believe you would (want to) define foo! if you want to change arguments. And would have a corresponding foo, that doesn’t and reuse that code. Though your way works too, to simplify into one function. Using ! is actually just a convention, though useful to maintain.

I’m thinking, since it’s a common pattern, to have these two similarily named functions, can and should it be handled with a macro, that defines the other for you?

function y = sum_of_fractions(a)

Note, this is not idiomatic. You could skip y = I think this may be from or similar syntax to MATLAB?! [Or you can skip function and use the short form. Though I believe that 3rd form (and more!) works too and you’re making a function named y, as synonym?]

1 Like

It’s a syntax error, in fact.

1 Like

Oops yes, I wrote this example code from scratch in the forum post and I included a Matlabism. Sorry, I’ll fix it.

2 Likes