This question is related to this post and I am trying to understand how FunctionWrappers avoids the world age problem (using cfunction
) and if there is a better way to realize the same functionality in a straightforward manner. I am using Julia v0.6 but feel free to point out if there might be any incompatibilities in the future.
Following is a simplified version of the problem I am trying to solve. I need to generate functions Int64
→ Int64
, given a Vector{Int64}
as an input that add the sum of the vectors to the input argument.
I define a compile method to build these functions and store them in an object that I can use in other parts of my code. The code works without any issues if I specify the type of the generated function using FunctionWrappers
as invoker::FunctionWrappers.FunctionWrapper{Int64, Tuple{Int64}}
(see GoodCompiledObject
). The exact same code without this line causes a world age issue.
struct BadCompiledObject
invoker
end
@inline (x::BadCompiledObject)(y) = x.invoker(y)
function compilebad(x::Vector{Int64})
modulecode = quote
function foo(x::Int64)
x + sum($x)
end
@inline invokefoo() = foo
end
moduleexpr = Expr(:module, true, gensym(:CompiledObject), modulecode)
eventmodule = eval(moduleexpr)
BadCompiledObject(Base.invokelatest(eventmodule.invokefoo))
end
function profile_bad()
x = [1, 10]
bco = compilebad(x)
@allocated bco(2)
end
profile_bad() # ERROR
# MethodError: no method matching foo(::Int64)
# The applicable method may be too new: running in world age 21945, while current world is 21949.e[0m
# Closest candidates are:
# foo(::Int64) at profile.jl:286 (method too new to be called from this world context.)
# #16#f at util.jl:328 [inlined]
# macro expansion at util.jl:331 [inlined]
# profile_bad() at profile_nn.jl:307
# include_string(::String, ::String) at loading.jl:522
# include_string(::String, ::String, ::Int64) at eval.jl:30
# include_string(::Module, ::String, ::String, ::Int64, ::Vararg{Int64,N} where N) at eval.jl:34
# (::Atom.##102#107{String,Int64,String})() at eval.jl:82
# withpath(::Atom.##102#107{String,Int64,String}, ::String) at utils.jl:30
# withpath(::Function, ::String) at eval.jl:38
# hideprompt(::Atom.##101#106{String,Int64,String}) at repl.jl:67
# macro expansion at eval.jl:80 [inlined]
# (::Atom.##100#105{Dict{String,Any}})() at task.jl:80
###############################################################################
# Version that works
###############################################################################
import FunctionWrappers
struct GoodCompiledObject
invoker::FunctionWrappers.FunctionWrapper{Int64, Tuple{Int64}}
end
@inline (x::GoodCompiledObject)(y) = x.invoker(y)
function compilegood(x::Vector{Int64})
modulecode = quote
function foo(x::Int64)
x + sum($x)
end
@inline invokefoo() = foo
end
moduleexpr = Expr(:module, true, gensym(:CompiledObject), modulecode)
eventmodule = eval(moduleexpr)
GoodCompiledObject(Base.invokelatest(eventmodule.invokefoo))
end
function profile_good()
x = [1, 10]
gco = compilegood(x)
@allocated gco(2)
end
profile_good() # returns 0
Following are my questions:
- How come the world age issue doesn’t affect
FunctionWrappers
? Is it working around the dynamic dispatch pertaining to world age somehow? - Are there any performance overheads of using
FunctionWrappers
? My application is very performance critical and therefore is sensitive to even small overheads. - Is this the recommended way of achieving my end goal? My end goal is to generate functions that perform matrix operations on static arrays efficiently.
- Will this approach work in Julia 1.0 as well?