Skip to content

Instantly share code, notes, and snippets.

@JeffreySarnoff
Last active February 25, 2020 04:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeffreySarnoff/97d213cd03588c745e32e88ee2563c3c to your computer and use it in GitHub Desktop.
Save JeffreySarnoff/97d213cd03588c745e32e88ee2563c3c to your computer and use it in GitHub Desktop.
BenchmarkTools: a macro to wrap `$x` as `$(Ref(x))[]`
#=
This material is the work of Mason Protter
https://github.com/JuliaCI/BenchmarkTools.jl/pull/140
id(x) = x
a, b = 1, 2
sym = :sym
@refd :($a)
:((Base.RefValue{Int64}(1))[])
@btime id($a) # 0.001 ns
@refd @btime id($a) # 1.100 ns
@refd :($sym)
:((Base.RefValue{Symbol}(:sym)).x)
@btime id($sym) # 9.000 ns
@refd @btime id($sym) # 1.400 ns
@btime $a + $b # 0.024 ns
@refd @btime $a + $b # 1.277 ns
=#
#=
Sometimes, interpolating variables into very simple expressions can give the compiler
more information than you intended, causing it to "cheat" the benchmark by hoisting
the calculation out of the benchmark code.
```
julia> a = 1; b = 2
2
julia> @btime $a + $b
0.024 ns (0 allocations: 0 bytes)
3
```
As a rule of thumb, if a benchmark reports that it took less than a nanosecond
to perform this hoisting probably occured. You can avoid this by referencing
and dereferencing the interpolated variables.
```
julia> @btime Ref($a)[] + Ref($b)[]
1.524 ns (0 allocations: 0 bytes)
3
```
As this can be easily misremembered and tedious to write, so we provide a macro
@refd to automatically reference and dereference interpolated variables.
```
julia> @refd @btime $a + $b
1.277 ns (0 allocations: 0 bytes)
3
```
Note that @refd should be appended outside the @btime statement.
=#
using BenchmarkTools
using MacroTools: MacroTools, prewalk, postwalk, @capture
walk(x, inner, outer) = outer(x)
walk(x::Expr, inner, outer) = outer(Expr(x.head, map(inner, x.args)...))
MacroTools.postwalk(f, x) = walk(x, x -> postwalk(f, x), f)
function _refd(expr::Expr)
if expr.head == :$
:($(Expr(:$, :(Ref($(expr.args...)))))[])
else
expr
end
end
_refd(x) = x
"""
@refd bmacro expression
where `bmacro` is one of `@btime`, `@belapsed`, `@benchmark`
In `expression`, wraps all '$'-prefixed variables `$var` in a `Ref()`.
- (`$var`) is `var` prefixed with '$'
This prevents the compiler from omitting code that is used
Works with any macro that accepts interpolation
Example
julia> @btime $a + $b
0.024 ns (0 allocations: 0 bytes)
3
julia> @refd @btime $a + $b
1.277 ns (0 allocations: 0 bytes)
3
"""
macro refd(expr)
out = postwalk(_refd, expr) |> esc
end
#=
for experimentation
# using Ref(x).x
function _refx(expr::Expr)
if expr.head == :$
:($:($(Expr(:$, :(Ref($(expr.args...)))))).x)
else
expr
end
end
_refx(x) = x
macro refx(expr)
out = postwalk(_refx, expr) |> esc
end
=#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment