Skip to content

Commit 3fa9a26

Browse files
committed
Ensure interpolated variables are garbage-collectable
1 parent 104f4c1 commit 3fa9a26

File tree

2 files changed

+39
-27
lines changed

2 files changed

+39
-27
lines changed

src/execution.jl

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ gcscrub() = (GC.gc(); GC.gc(); GC.gc(); GC.gc())
99

1010
mutable struct Benchmark
1111
samplefunc
12+
quote_vals
1213
params::Parameters
1314
end
1415

@@ -95,13 +96,13 @@ function _run(b::Benchmark, p::Parameters; verbose = false, pad = "", kwargs...)
9596
start_time = Base.time()
9697
trial = Trial(params)
9798
params.gcsample && gcscrub()
98-
s = b.samplefunc(params)
99+
s = b.samplefunc(b.quote_vals, params)
99100
push!(trial, s[1:end-1]...)
100101
return_val = s[end]
101102
iters = 2
102103
while (Base.time() - start_time) < params.seconds && iters params.samples
103104
params.gcsample && gcscrub()
104-
push!(trial, b.samplefunc(params)[1:end-1]...)
105+
push!(trial, b.samplefunc(b.quote_vals, params)[1:end-1]...)
105106
iters += 1
106107
end
107108
return trial, return_val
@@ -157,7 +158,7 @@ function _lineartrial(b::Benchmark, p::Parameters = b.params; maxevals = RESOLUT
157158
for evals in eachindex(estimates)
158159
params.gcsample && gcscrub()
159160
params.evals = evals
160-
estimates[evals] = first(b.samplefunc(params))
161+
estimates[evals] = first(b.samplefunc(b.quote_vals, params))
161162
completed += 1
162163
((time() - start_time) > params.seconds) && break
163164
end
@@ -305,18 +306,22 @@ function collectvars(ex::Expr, vars::Vector{Symbol} = Symbol[])
305306
return vars
306307
end
307308

308-
function quasiquote!(ex::Expr, vars::Vector{Expr})
309+
"""
310+
quasiquote!(expr::Expr, vars::Vector{Symbol}, vals::Vector{Expr})
311+
312+
Replace every interpolated value in `expr` with a placeholder variable and
313+
store the resulting variable / value pairings in `vars` and `vals`.
314+
"""
315+
quasiquote!(ex, _...) = ex
316+
function quasiquote!(ex::Expr, vars::Vector{Symbol}, vals::Vector{Expr})
309317
if ex.head === :($)
310-
lhs = ex.args[1]
311-
rhs = isa(lhs, Symbol) ? gensym(lhs) : gensym()
312-
push!(vars, Expr(:(=), rhs, ex))
313-
return rhs
318+
var = isa(ex.args[1], Symbol) ? gensym(ex.args[1]) : gensym()
319+
push!(vars, var)
320+
push!(vals, ex)
321+
return var
314322
elseif ex.head !== :quote
315323
for i in 1:length(ex.args)
316-
arg = ex.args[i]
317-
if isa(arg, Expr)
318-
ex.args[i] = quasiquote!(arg, vars)
319-
end
324+
ex.args[i] = quasiquote!(ex.args[i], vars, vals)
320325
end
321326
end
322327
return ex
@@ -410,15 +415,11 @@ function benchmarkable_parts(args)
410415
end
411416
deleteat!(params, delinds)
412417

413-
if isa(core, Expr)
414-
quote_vars = Expr[]
415-
core = quasiquote!(core, quote_vars)
416-
if !isempty(quote_vars)
417-
setup = Expr(:block, setup, quote_vars...)
418-
end
419-
end
418+
quote_vars = Symbol[]
419+
quote_vals = Expr[]
420+
core = quasiquote!(core, quote_vars, quote_vals)
420421

421-
return core, setup, teardown, params
422+
return core, setup, teardown, quote_vars, quote_vals, params
422423
end
423424

424425
"""
@@ -428,7 +429,7 @@ Create a `Benchmark` instance for the given expression. `@benchmarkable`
428429
has similar syntax with `@benchmark`. See also [`@benchmark`](@ref).
429430
"""
430431
macro benchmarkable(args...)
431-
core, setup, teardown, params = benchmarkable_parts(args)
432+
core, setup, teardown, quote_vars, quote_vals, params = benchmarkable_parts(args)
432433
map!(esc, params, params)
433434

434435
# extract any variable bindings shared between the core and setup expressions
@@ -441,6 +442,8 @@ macro benchmarkable(args...)
441442
generate_benchmark_definition($__module__,
442443
$(Expr(:quote, out_vars)),
443444
$(Expr(:quote, setup_vars)),
445+
$(Expr(:quote, quote_vars)),
446+
$(esc(Expr(:vect,Expr.(:quote, quote_vals)...))),
444447
$(esc(Expr(:quote, core))),
445448
$(esc(Expr(:quote, setup))),
446449
$(esc(Expr(:quote, teardown))),
@@ -455,14 +458,14 @@ end
455458
# The double-underscore-prefixed variable names are not particularly hygienic - it's
456459
# possible for them to conflict with names used in the setup or teardown expressions.
457460
# A more robust solution would be preferable.
458-
function generate_benchmark_definition(eval_module, out_vars, setup_vars, core, setup, teardown, params)
461+
function generate_benchmark_definition(eval_module, out_vars, setup_vars, quote_vars, quote_vals, core, setup, teardown, params)
459462
@nospecialize
460463
corefunc = gensym("core")
461464
samplefunc = gensym("sample")
462-
type_vars = [gensym() for i in 1:length(setup_vars)]
463-
signature = Expr(:call, corefunc, setup_vars...)
465+
type_vars = [gensym() for i in 1:length(quote_vars)+length(setup_vars)]
466+
signature = Expr(:call, corefunc, quote_vars..., setup_vars...)
464467
signature_def = Expr(:where, Expr(:call, corefunc,
465-
[Expr(:(::), setup_var, type_var) for (setup_var, type_var) in zip(setup_vars, type_vars)]...)
468+
[Expr(:(::), var, type) for (var, type) in zip([quote_vars;setup_vars], type_vars)]...)
466469
, type_vars...)
467470
if length(out_vars) == 0
468471
invocation = signature
@@ -478,7 +481,7 @@ function generate_benchmark_definition(eval_module, out_vars, setup_vars, core,
478481
end
479482
return Core.eval(eval_module, quote
480483
@noinline $(signature_def) = begin $(core_body) end
481-
@noinline function $(samplefunc)(__params::$BenchmarkTools.Parameters)
484+
@noinline function $(samplefunc)($(Expr(:tuple, quote_vars...)), __params::$BenchmarkTools.Parameters)
482485
$(setup)
483486
__evals = __params.evals
484487
__gc_start = Base.gc_num()
@@ -498,7 +501,7 @@ function generate_benchmark_definition(eval_module, out_vars, setup_vars, core,
498501
__evals))
499502
return __time, __gctime, __memory, __allocs, __return_val
500503
end
501-
$BenchmarkTools.Benchmark($(samplefunc), $(params))
504+
$BenchmarkTools.Benchmark($(samplefunc), $(quote_vals), $(params))
502505
end)
503506
end
504507

test/ExecutionTests.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,13 @@ let time = 2
253253
@benchmark identity(time)
254254
end
255255

256+
# Ensure that interpolated values are garbage-collectable
257+
x = []
258+
x_finalized = false
259+
finalizer(x->(global x_finalized=true), x)
260+
b = @benchmarkable $x
261+
b = x = nothing
262+
GC.gc()
263+
@test x_finalized
264+
256265
end # module

0 commit comments

Comments
 (0)