Skip to content

Commit ec102eb

Browse files
committed
Cache binding pointer in GlobalRef
On certain workloads, profiling shows a surprisingly high fraction of inference time spent looking up bindings in modules. Bindings use a hash table, so they're expected to be quite fast, but certainly not zero. A big contributor to the problem is that we do basically treat it as zero, looking up bindings for GlobalRefs multiple times for each statement (e.g. in `isconst`, `isdefined`, to get the types, etc). This PR attempts to improve the situation by adding an extra field to GlobalRef that caches this lookup. This field is not serialized and if not set, we fallback to the previous behavior. I would expect the memory overhead to be relatively small, since we do intern GlobalRefs in memory to only have one per binding (rather than one per use). # Benchmarks The benchmarks look quite promising. Consider this artifical example (though it's actually not all that far fetched, given some of the generated code we see): ``` using Core.Intrinsics: add_int const ONE = 1 @eval function f(x, y) z = 0 $([:(z = add_int(x, add_int(z, ONE))) for _ = 1:10000]...) return add_int(z, y) end g(y) = f(ONE, y) ``` On master: ``` julia> @time @code_typed g(1) 1.427227 seconds (1.31 M allocations: 58.809 MiB, 5.73% gc time, 99.96% compilation time) CodeInfo( 1 ─ %1 = invoke Main.f(Main.ONE::Int64, y::Int64)::Int64 └── return %1 ) => Int64 ``` On this PR: ``` julia> @time @code_typed g(1) 0.503151 seconds (1.19 M allocations: 53.641 MiB, 5.10% gc time, 33.59% compilation time) CodeInfo( 1 ─ %1 = invoke Main.f(Main.ONE::Int64, y::Int64)::Int64 └── return %1 ) => Int64 ``` I don't expect the same speedup on other workloads, but there should be a few % speedup on most workloads still. # Future extensions The other motivation for this is that with a view towards #40399, we will want to more clearly define when bindings get resolved. The idea here would then be that binding resolution replaces generic `GlobalRefs` by GlobalRefs with a set binding cache, and any unresolved bindings would be treated conservatively by inference and optimization.
1 parent 1916d35 commit ec102eb

File tree

14 files changed

+90
-25
lines changed

14 files changed

+90
-25
lines changed

base/boot.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ eval(Core, quote
412412
end
413413
LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line::Int32, inlined_at::Int32) =
414414
$(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at))
415-
GlobalRef(m::Module, s::Symbol) = $(Expr(:new, :GlobalRef, :m, :s))
415+
GlobalRef(m::Module, s::Symbol, binding::Ptr{Nothing}) = $(Expr(:new, :GlobalRef, :m, :s, :binding))
416416
SlotNumber(n::Int) = $(Expr(:new, :SlotNumber, :n))
417417
TypedSlot(n::Int, @nospecialize(t)) = $(Expr(:new, :TypedSlot, :n, :t))
418418
PhiNode(edges::Array{Int32, 1}, values::Array{Any, 1}) = $(Expr(:new, :PhiNode, :edges, :values))
@@ -812,6 +812,8 @@ Unsigned(x::Union{Float16, Float32, Float64, Bool}) = UInt(x)
812812
Integer(x::Integer) = x
813813
Integer(x::Union{Float16, Float32, Float64}) = Int(x)
814814

815+
GlobalRef(m::Module, s::Symbol) = GlobalRef(m, s, bitcast(Ptr{Nothing}, 0))
816+
815817
# Binding for the julia parser, called as
816818
#
817819
# Core._parse(text, filename, lineno, offset, options)

base/compiler/abstractinterpretation.jl

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,7 +1986,7 @@ function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(
19861986
return sv.argtypes[e.n]
19871987
end
19881988
elseif isa(e, GlobalRef)
1989-
return abstract_eval_global(interp, e.mod, e.name, sv)
1989+
return abstract_eval_globalref(interp, e, sv)
19901990
end
19911991

19921992
return Const(e)
@@ -2260,17 +2260,24 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
22602260
return rt
22612261
end
22622262

2263-
function abstract_eval_global(M::Module, s::Symbol)
2264-
if isdefined(M, s) && isconst(M, s)
2265-
return Const(getglobal(M, s))
2263+
function isdefined_globalref(g::GlobalRef)
2264+
g.binding != C_NULL && return ccall(:jl_binding_boundp, Cint, (Ptr{Cvoid},), g.binding) != 0
2265+
return isdefined(g.mod, g.name)
2266+
end
2267+
2268+
function abstract_eval_globalref(g::GlobalRef)
2269+
if isdefined_globalref(g) && isconst(g)
2270+
g.binding != C_NULL && return Const(ccall(:jl_binding_value, Any, (Ptr{Cvoid},), g.binding))
2271+
return Const(getglobal(g.mod, g.name))
22662272
end
2267-
ty = ccall(:jl_binding_type, Any, (Any, Any), M, s)
2273+
ty = ccall(:jl_binding_type, Any, (Any, Any), g.mod, g.name)
22682274
ty === nothing && return Any
22692275
return ty
22702276
end
2277+
abstract_eval_global(M::Module, s::Symbol) = abstract_eval_globalref(GlobalRef(M, s))
22712278

2272-
function abstract_eval_global(interp::AbstractInterpreter, M::Module, s::Symbol, frame::Union{InferenceState, IRCode})
2273-
rt = abstract_eval_global(M, s)
2279+
function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, frame::Union{InferenceState, IRCode})
2280+
rt = abstract_eval_globalref(g)
22742281
consistent = inaccessiblememonly = ALWAYS_FALSE
22752282
nothrow = false
22762283
if isa(rt, Const)
@@ -2281,7 +2288,7 @@ function abstract_eval_global(interp::AbstractInterpreter, M::Module, s::Symbol,
22812288
else
22822289
nothrow = true
22832290
end
2284-
elseif isdefined(M,s)
2291+
elseif isdefined_globalref(g)
22852292
nothrow = true
22862293
end
22872294
merge_effects!(interp, frame, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly))

base/compiler/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ function argextype(
336336
elseif isa(x, QuoteNode)
337337
return Const(x.value)
338338
elseif isa(x, GlobalRef)
339-
return abstract_eval_global(x.mod, x.name)
339+
return abstract_eval_globalref(x)
340340
elseif isa(x, PhiNode)
341341
return Any
342342
elseif isa(x, PiNode)

base/compiler/ssair/slot2ssa.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::
216216
end
217217
return (ci.ssavaluetypes::Vector{Any})[idx]
218218
end
219-
isa(x, GlobalRef) && return abstract_eval_global(x.mod, x.name)
219+
isa(x, GlobalRef) && return abstract_eval_globalref(x)
220220
isa(x, SSAValue) && return (ci.ssavaluetypes::Vector{Any})[x.id]
221221
isa(x, Argument) && return slottypes[x.n]
222222
isa(x, NewSSAValue) && return DelayedTyp(x)

base/compiler/utilities.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ function is_throw_call(e::Expr)
378378
if e.head === :call
379379
f = e.args[1]
380380
if isa(f, GlobalRef)
381-
ff = abstract_eval_global(f.mod, f.name)
381+
ff = abstract_eval_globalref(f)
382382
if isa(ff, Const) && ff.val === Core.throw
383383
return true
384384
end

base/essentials.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ length(a::Array) = arraylen(a)
1313
eval(:(getindex(A::Array, i1::Int) = arrayref($(Expr(:boundscheck)), A, i1)))
1414
eval(:(getindex(A::Array, i1::Int, i2::Int, I::Int...) = (@inline; arrayref($(Expr(:boundscheck)), A, i1, i2, I...))))
1515

16+
==(a::GlobalRef, b::GlobalRef) = a.mod === b.mod && a.name === b.name
17+
1618
"""
1719
AbstractSet{T}
1820

base/reflection.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,11 @@ Determine whether a global is declared `const` in a given module `m`.
265265
isconst(m::Module, s::Symbol) =
266266
ccall(:jl_is_const, Cint, (Any, Any), m, s) != 0
267267

268+
function isconst(g::GlobalRef)
269+
g.binding != C_NULL && return ccall(:jl_binding_is_const, Cint, (Ptr{Cvoid},), g.binding) != 0
270+
return isconst(g.mod, g.name)
271+
end
272+
268273
"""
269274
isconst(t::DataType, s::Union{Int,Symbol}) -> Bool
270275

base/show.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,9 +1838,10 @@ function allow_macroname(ex)
18381838
end
18391839
end
18401840

1841-
function is_core_macro(arg, macro_name::AbstractString)
1842-
arg === GlobalRef(Core, Symbol(macro_name))
1841+
function is_core_macro(arg::GlobalRef, macro_name::AbstractString)
1842+
arg == GlobalRef(Core, Symbol(macro_name))
18431843
end
1844+
is_core_macro(@nospecialize(arg), macro_name::AbstractString) = false
18441845

18451846
# symbol for IOContext flag signaling whether "begin" is treated
18461847
# as an ordinary symbol, which is true in indexing expressions.

src/clangsa/GCChecker.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@ bool GCChecker::isGCTrackedType(QualType QT) {
745745
Name.endswith_lower("jl_method_match_t") ||
746746
Name.endswith_lower("jl_vararg_t") ||
747747
Name.endswith_lower("jl_opaque_closure_t") ||
748+
Name.endswith_lower("jl_globalref_t") ||
748749
// Probably not technically true for these, but let's allow it
749750
Name.endswith_lower("typemap_intersection_env") ||
750751
Name.endswith_lower("interpreter_state") ||

src/dump.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,6 +2084,10 @@ static void jl_deserialize_struct(jl_serializer_state *s, jl_value_t *v) JL_GC_D
20842084
entry->min_world = 1;
20852085
entry->max_world = 0;
20862086
}
2087+
} else if (dt == jl_globalref_type) {
2088+
jl_globalref_t *r = (jl_globalref_t*)v;
2089+
jl_binding_t *b = jl_get_binding(r->mod, r->name);
2090+
r->bnd_cache = b->value ? b : NULL;
20872091
}
20882092
}
20892093

0 commit comments

Comments
 (0)