I've got a situation where it would be handy to have a variable, to, which can either be a TimerOutput or nothing. I'm interested in providing a macro that takes the same arguments as @timeit from TimerOutputs (e.g. @timeit to "time spent" s = foo()). Because to is potentially set to nothing, I can't simply disable a TimerObject.
If to is set, my preference would be to pass the arguments along to @timeit, but could live with calling timer_expr(__module__, false, args...).
If to is not set, I'd want to just return the remaining arguments (something like args[3:end], maybe) as an expression.
I've been fussing with this for a day or so, and can handle each of the cases in isolation. For the case where I'm not involving TimerOutput, this seems to work:
macro no_timer(args...)
args[3:end][1]
end
And for the case where I am using TimerOutput, I can do this:
macro with_timer(args...)
timer_expr(__module__, false, args...)
end
Not surprising, as that's just what @timeit does.
I haven't figured out how to handle both cases in one macro. I've gotten the closest by wrapping everything in a ternary operator - i.e. return :(isnothing($(args[1])) ? <expression stuff> : <TimerOutput stuff>), but there is some level of abstraction mismatch that I haven't unsnarled.
Addendum: I've come to the conclusion that my original framing was an "X-Y" problem. I didn't actually need a new macro to solve my problem - hence my accepting the answer I did. That said, I am struck by the fact that both of the answers proffered stayed well away from defining a macro.
You have already this functionality in TimerOuputs.
Just use disable_timer! and enable_timer! methods.
julia> const to = TimerOutput();
julia> disable_timer!(to);
julia> @timeit to "sleep" sleep(0.02)
julia> to
────────────────────────────────────────────────────────────────────
Time Allocations
─────────────────────── ────────────────────────
Tot / % measured: 23.6s / 0.0% 464KiB / 0.0%
Section ncalls time %tot avg alloc %tot avg
────────────────────────────────────────────────────────────────────
────────────────────────────────────────────────────────────────────
julia> enable_timer!(to);
julia> @timeit to "sleep" sleep(0.02)
julia> to
────────────────────────────────────────────────────────────────────
Time Allocations
─────────────────────── ────────────────────────
Tot / % measured: 37.3s / 0.1% 777KiB / 0.0%
Section ncalls time %tot avg alloc %tot avg
────────────────────────────────────────────────────────────────────
sleep 1 22.6ms 100.0% 22.6ms 320B 100.0% 320B
────────────────────────────────────────────────────────────────────
You can define your own more specific version of the timer_expr function that the macro calls, where the returned expression contains a check for to argument being nothing, then doing the appropriate thing.
import TimerOutputs: timer_expr
function timer_expr(m::Module, is_debug::Bool, to::Symbol, label::String, ex::Expr)
unescaped(ex) = ex.head == :escape ? ex.args[1] : ex
# this is from the original timer_expr functions,
# to be used when `to` isn't nothing
timer_ex = TimerOutputs.is_func_def(ex) ?
unescaped(TimerOutputs.timer_expr_func(m, is_debug, to, ex, label)) :
TimerOutputs._timer_expr(m, is_debug, to, label, ex)
cond_ex = esc(:(if isnothing($to)
$ex
else
placeholder # dummy symbol, to be replaced
end))
# args[3] = "else" section,
# the placeholder is args[2] within that
unescaped(cond_ex).args[3].args[2] = timer_ex
cond_ex
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With