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
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