在 Julia 中通过递归调用减少 JIT 时间
Reducing JIT time with recursive calls in Julia
我有一个递归函数,它操作整数的二叉树,实现为一对嵌套的对或整数。我的函数创建了一棵具有不同结构的新树,并递归调用自身直到满足某些条件。我发现的问题是代码第一次是 运行 时,JIT 编译所有可能的函数签名需要很长时间;之后 运行 就好了。
这是最小的工作示例:
my_tree = ((((6 => 7) => (6 => 7)) => ((7 => 7) => (0 => 7))) => (((8 => 7) => (7 => 7)) => ((8 => 8) => (8 => 0)))) => ((((2 => 4) => 7) => (6 => (0 => 5))) => (((6 => 8) => (2 => 8)) => ((2 => 1) => (4 => 5))))
function tree_reduce(tree::Pair)
left, right = tree
left isa Pair && (left = tree_reduce(left))
right isa Pair && (right = tree_reduce(right))
return left + right
end
@show my_tree
@show tree_reduce(my_tree)
using MethodAnalysis
methods = methodinstances(tree_reduce)
@show length(methods)
虽然这个例子在感知上并不慢,但它仍然生成了 9 个方法实例:
tree_reduce(::Pair{Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}})
tree_reduce(::Pair{Pair{Int64, Int64}, Pair{Int64, Int64}})
tree_reduce(::Pair{Int64, Int64})
tree_reduce(::Pair{Pair{Pair{Pair{Int64, Int64}, Int64}, Pair{Int64, Pair{Int64, Int64}}}, Pair{Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}}})
etc ...
有没有办法避免这种情况/预编译/加速/编写通用函数/运行在解释模式下使用特定(部分)函数?我准备好让代码的整体性能稍微好一点,而不是在第一次顶级调用 tree_reduce
.
时让它 运行 更快
是的,Base.@nospecialize
:
function tree_reduce2(Base.@nospecialize(tree::Pair))
left, right = tree
left isa Pair && (left = tree_reduce(left))
right isa Pair && (right = tree_reduce(right))
return left + right
end
@show my_tree
@show tree_reduce2(my_tree)
using MethodAnalysis
methods = methodinstances(tree_reduce2)
@show length(methods) #1
查看 docs of the macro 了解更多使用方法。
@nospecialize
是一个选项,但我认为在这种情况下,一个不捕获其类型的整个数据结构的单独数据结构是有序的。 Pair
确实适用于强类型的事物对,而不适用于大型嵌套结构。
julia> abstract type BiTree{T} end
julia> struct Branch{T} <: BiTree{T}
left::BiTree{T}
right::BiTree{T}
end
julia> struct Leaf{T} <: BiTree{T}
value::T
end
julia> Base.foldl(f, b::Branch) = f(foldl(f, b.left), foldl(f, b.right))
julia> Base.foldl(f, l::Leaf) = f(l.value)
julia> (→)(l::Branch, r::Branch) = Branch(l, r) # just for illustration
→ (generic function with 1 method)
julia> (→)(l, r::Branch) = Branch(Leaf(l), r)
→ (generic function with 2 methods)
julia> (→)(l::Branch, r) = Branch(l, Leaf(r))
→ (generic function with 3 methods)
julia> (→)(l, r) = Branch(Leaf(l), Leaf(r))
→ (generic function with 4 methods)
julia> my_tree = ((((6 → 7) → (6 → 7)) → ((7 → 7) → (0 → 7))) → (((8 → 7) → (7 → 7)) → ((8 → 8) → (8 → 0)))) → ((((2 → 4) → 7) → (6 → (0 → 5))) → (((6 → 8) → (2 → 8)) → ((2 → 1) → (4 → 5))));
julia> typeof(my_tree)
Branch{Int64}
julia> foldl(+, my_tree)
160
这还有一个好处,就是您可以在不破坏任何东西的情况下重载其他方法,例如打印或索引。
我有一个递归函数,它操作整数的二叉树,实现为一对嵌套的对或整数。我的函数创建了一棵具有不同结构的新树,并递归调用自身直到满足某些条件。我发现的问题是代码第一次是 运行 时,JIT 编译所有可能的函数签名需要很长时间;之后 运行 就好了。
这是最小的工作示例:
my_tree = ((((6 => 7) => (6 => 7)) => ((7 => 7) => (0 => 7))) => (((8 => 7) => (7 => 7)) => ((8 => 8) => (8 => 0)))) => ((((2 => 4) => 7) => (6 => (0 => 5))) => (((6 => 8) => (2 => 8)) => ((2 => 1) => (4 => 5))))
function tree_reduce(tree::Pair)
left, right = tree
left isa Pair && (left = tree_reduce(left))
right isa Pair && (right = tree_reduce(right))
return left + right
end
@show my_tree
@show tree_reduce(my_tree)
using MethodAnalysis
methods = methodinstances(tree_reduce)
@show length(methods)
虽然这个例子在感知上并不慢,但它仍然生成了 9 个方法实例:
tree_reduce(::Pair{Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}})
tree_reduce(::Pair{Pair{Int64, Int64}, Pair{Int64, Int64}})
tree_reduce(::Pair{Int64, Int64})
tree_reduce(::Pair{Pair{Pair{Pair{Int64, Int64}, Int64}, Pair{Int64, Pair{Int64, Int64}}}, Pair{Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}, Pair{Pair{Int64, Int64}, Pair{Int64, Int64}}}})
etc ...
有没有办法避免这种情况/预编译/加速/编写通用函数/运行在解释模式下使用特定(部分)函数?我准备好让代码的整体性能稍微好一点,而不是在第一次顶级调用 tree_reduce
.
是的,Base.@nospecialize
:
function tree_reduce2(Base.@nospecialize(tree::Pair))
left, right = tree
left isa Pair && (left = tree_reduce(left))
right isa Pair && (right = tree_reduce(right))
return left + right
end
@show my_tree
@show tree_reduce2(my_tree)
using MethodAnalysis
methods = methodinstances(tree_reduce2)
@show length(methods) #1
查看 docs of the macro 了解更多使用方法。
@nospecialize
是一个选项,但我认为在这种情况下,一个不捕获其类型的整个数据结构的单独数据结构是有序的。 Pair
确实适用于强类型的事物对,而不适用于大型嵌套结构。
julia> abstract type BiTree{T} end
julia> struct Branch{T} <: BiTree{T}
left::BiTree{T}
right::BiTree{T}
end
julia> struct Leaf{T} <: BiTree{T}
value::T
end
julia> Base.foldl(f, b::Branch) = f(foldl(f, b.left), foldl(f, b.right))
julia> Base.foldl(f, l::Leaf) = f(l.value)
julia> (→)(l::Branch, r::Branch) = Branch(l, r) # just for illustration
→ (generic function with 1 method)
julia> (→)(l, r::Branch) = Branch(Leaf(l), r)
→ (generic function with 2 methods)
julia> (→)(l::Branch, r) = Branch(l, Leaf(r))
→ (generic function with 3 methods)
julia> (→)(l, r) = Branch(Leaf(l), Leaf(r))
→ (generic function with 4 methods)
julia> my_tree = ((((6 → 7) → (6 → 7)) → ((7 → 7) → (0 → 7))) → (((8 → 7) → (7 → 7)) → ((8 → 8) → (8 → 0)))) → ((((2 → 4) → 7) → (6 → (0 → 5))) → (((6 → 8) → (2 → 8)) → ((2 → 1) → (4 → 5))));
julia> typeof(my_tree)
Branch{Int64}
julia> foldl(+, my_tree)
160
这还有一个好处,就是您可以在不破坏任何东西的情况下重载其他方法,例如打印或索引。