Skip to content

Commit ed1d4fb

Browse files
committed
switch to non-call-based closure capture optimization
Cause it to capture all values of any expression (even values of globals), not just values passed to a single call.
1 parent 6b29102 commit ed1d4fb

File tree

1 file changed

+47
-48
lines changed

1 file changed

+47
-48
lines changed

base/timing.jl

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -472,44 +472,34 @@ function gc_bytes()
472472
b[]
473473
end
474474

475-
function allocated(f, args::Vararg{Any,N}; kwargs...) where {N}
476-
b0 = Ref{Int64}(0)
477-
b1 = Ref{Int64}(0)
478-
Base.gc_bytes(b0)
479-
f(args...; kwargs...)
480-
Base.gc_bytes(b1)
481-
return b1[] - b0[]
482-
end
483-
only(methods(allocated)).called = 0xff
484-
485-
function allocations(f, args::Vararg{Any,N}; kwargs...) where {N}
486-
stats = Base.gc_num()
487-
f(args...; kwargs...)
488-
diff = Base.GC_Diff(Base.gc_num(), stats)
489-
return Base.gc_alloc_count(diff)
490-
end
491-
only(methods(allocations)).called = 0xff
492-
493-
function is_simply_call(@nospecialize ex, captures::Vector{Symbol})
494-
Meta.isexpr(ex, :call) || return false
495-
function is_simple_arg(@nospecialize a)
496-
a isa Symbol && return true
497-
a isa GlobalRef && return true
498-
is_self_quoting(a) && return true
499-
is_quoted(a) && return true
500-
if a isa Expr
501-
a.head === :(kw) && return is_simple_arg(a.args[2])
502-
a.head === :(...) && return is_simple_arg(a.args[1])
503-
a.head === :parameters && return all(is_simple_arg, a.args)
504-
end
475+
# get a list of all possible captures and globals from ex into vars
476+
# return false if that list would change the expression meaning too much
477+
function try_get_vars!(@nospecialize(ex), vars::Vector{Symbol})
478+
ex isa Symbol && (ex in vars || push!(vars, ex))
479+
ex isa Expr || return true
480+
is_quoted(ex) && return true
481+
if ex.head == :kw
482+
return try_get_vars!(ex.args[2], vars)
483+
end
484+
# An allow-list of all expression types that don't change meaning (much) if
485+
# we hoist their possible variable accesses into arguments, given that we
486+
# don't allow :(=) in this list. We do allow conditionals, even though that
487+
# means we could cause an UndefVar error to appear. We also hoist what may
488+
# be global accesses, even though that means we change the order of evaluation.
489+
if ex.head (:call, :kw, :parameters, :(...), :., :var"'", :tuple, :ref,
490+
:vect, :hcat, :vcat, :ncat, :row, :nrow, :block, :if, :&&, :||, :?,
491+
:let, :try, :catch, :finally, :global, :local)
505492
return false
506493
end
507494
for a in ex.args
508-
is_simple_arg(a) || return false
495+
try_get_vars!(a, vars) || return false
509496
end
510497
return true
511498
end
512499

500+
const b0 = gensym("b0")
501+
const b1 = gensym("b1")
502+
513503
"""
514504
@allocated
515505
@@ -526,22 +516,23 @@ julia> @allocated rand(10^6)
526516
"""
527517
macro allocated(ex)
528518
vars = Symbol[]
529-
try_get_captures!(ex, vars) || empty!(vars)
519+
try_get_vars!(ex, vars) || empty!(vars)
520+
#ex = esc(ex)
530521
body = quote
531-
b0 = Ref{Int64}(0)
532-
b1 = Ref{Int64}(0)
533-
gc_bytes(b0)
534-
$(esc(ex))
535-
gc_bytes(b1)
536-
return b1[] - b0[]
522+
local $b0 = $Ref{$Int64}(0)
523+
local $b1 = $Ref{$Int64}(0)
524+
$gc_bytes($b0)
525+
$ex
526+
$gc_bytes($b1)
527+
return $-($getindex($b1), $getindex($b0))
537528
end
538-
pushfirst!(ex.args, GlobalRef(Base, :allocated))
539-
ws = map(i -> Symbol("_$i"), 1:length(vars))
540-
vars = map(esc, vars)
529+
# add "wheres" to disable de-specialization heuristics
530+
ws = map(i -> gensym(), 1:length(vars)) # Symbol("_$i")
531+
#vars = map(esc, vars)
541532
args = map((v, w) -> :($v::$w), vars, ws)
542533
sig = :( ($(args...),) where {$(ws...)} )
543534
ex = remove_linenums!(:(($sig -> $body)($(vars...))))
544-
return ex
535+
return esc(ex) # manual hygiene to work around macroexpand bugs
545536
end
546537

547538
"""
@@ -562,13 +553,21 @@ julia> @allocations rand(10^6)
562553
This macro was added in Julia 1.9.
563554
"""
564555
macro allocations(ex)
565-
if !is_simply_call(ex)
566-
ex = :((() -> $ex)())
567-
else
568-
length(ex.args) >= 2 && isexpr(ex.args[2], :parameters) && ((ex.args[1], ex.args[2]) = (ex.args[2], ex.args[1]))
556+
vars = Symbol[]
557+
try_get_vars!(ex, vars) || empty!(vars)
558+
#ex = esc(ex)
559+
body = quote
560+
local $b0 = $gc_num()
561+
$ex
562+
return $gc_alloc_count($GC_Diff($gc_num(), $b0))
569563
end
570-
pushfirst!(ex.args, GlobalRef(Base, :allocations))
571-
return esc(ex)
564+
# add "wheres" to disable de-specialization heuristics
565+
ws = map(i -> gensym(), 1:length(vars)) # Symbol("_$i")
566+
#vars = map(esc, vars)
567+
args = map((v, w) -> :($v::$w), vars, ws)
568+
sig = :( ($(args...),) where {$(ws...)} )
569+
ex = remove_linenums!(:(($sig -> $body)($(vars...))))
570+
return esc(ex) # manual hygiene to work around macroexpand bugs
572571
end
573572

574573

0 commit comments

Comments
 (0)