How to avoid Box?

FLoops.jl may complain about HasBoxedVariableError. For a quick prototyping, calling FLoops.assistant(false) to disable the error/warning may be useful. However, it is also easy to avoid this problem if you understand a few patterns as explained below:

"Leaked variables" ⇒ use local

HasBoxedVariableError can occur when "leaked" variables are causing data races. Consider the following example:

function leaked_variable_example(xs)
    a = xs[begin]
    b = xs[end]
    c = (a + b) / 2
    @floop for x in xs
        a = max(x, c)
        @reduce s += a
    end
    return s
end

Calling this function causes HasBoxedVariableError:

julia> FLoops.assistant(:error);

julia> leaked_variable_example(1:2)
ERROR: HasBoxedVariableError: Closure ... has 1 boxed variable: a

This occurs because the variable a is assigned before @floop by a = xs[begin] and inside @floop by a = max(x, c). However, since the assignment inside @floop can occur in parallel, this is a data race. We can avoid this issue easily by making a local inside the loop:

function leaked_variable_example_fix1(xs)
    a = xs[begin]
    b = xs[end]
    c = (a + b) / 2
    @floop for x in xs
        local a = max(x, c)   # note `local`
        @reduce s += a
    end
    return s
end

Alternatively, we can use a different name:

function leaked_variable_example_fix2(xs)
    a = xs[begin]
    b = xs[end]
    c = (a + b) / 2
    @floop for x in xs
        d = max(x, c)   # not `a`
        @reduce s += d
    end
    return s
end

or limit the scope of a used for calculating c:

function leaked_variable_example_fix3(xs)
    c = let a = xs[begin], b = xs[end]  # limit the scope of `a`
        (a + b) / 2
    end
    @floop for x in xs
        a = max(x, c)
        @reduce s += a
    end
    return s
end

"Uncertain values" ⇒ use let

Note

This is a known limitation as of Julia 1.8-DEV. This documentation may not be accurate in the future version of Julia. For more information, see: performance of captured variables in closures · Issue #15276 · JuliaLang/julia

HasBoxedVariableError can also occur when Julia is uncertain about the value of some variables in the scope surrounding @floop (i.e., there are multiple binding locations). For example:

function uncertain_value_example(xs; flag = false)
    if flag
        a = 0
    else
        a = 1
    end
    @floop for x in xs
        x += a
        @reduce s += x
    end
    return s
end

This can be fixed by using let to ensure that the variable a does not change while executing @floop. (Julia can be sure that the variable is not updated when it is assigned only once.)

function uncertain_value_example_fix(xs; flag = false)
    if flag
        a = 0
    else
        a = 1
    end
    let a = a  # "quench" the value of `a`
        @floop for x in xs
            x += a
            @reduce s += x
        end
        return s
    end
end

"Not really a data race" ⇒ use Ref

It is conceptually sound to assign to the variables in an outer scope of @floop if it is ensured that the race does not occur. For example, in the following example, found = i can be executed at most once since the elements of xs are all distinct:

function non_data_race_example(xs::UnitRange; ex = nothing)
    local found = nothing
    @floop ex for (i, x) in pairs(xs)
        if x == 1
            found = i
        end
    end
    return found
end

However, FLoops.jl also complains about HasBoxedVariableError when executing this function. We can fix this by using Ref:

function non_data_race_example_fix(xs::UnitRange; ex = nothing)
    found = Ref{Union{Int,Nothing}}(nothing)
    @floop ex for (i, x) in pairs(xs)
        if x == 1
            found[] = i
        end
    end
    return found[]
end