-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dummy timers as a zero-overhead alternative to @timeit_debug
#109
base: master
Are you sure you want to change the base?
Conversation
It's an interesting proposal, but from what I understand it lacks the flexibility of being able to enable/disable the timers in various other packages where going in and changing the source code is not feasible. Regarding the bad performance with |
Well, that depends on how timers are used within packages using TimerOutputs. My logic is that a package should allow the user to pass their own Following this logic, it would be enough to pass around a dummy |
I see, that is pretty clean and would work well with this PR, indeed. Just to be clear, I'm fine putting something like this in but I just wanted to note that I think we can still do better with the existing |
Great, thanks. I'll put some more work on this PR and let you know when it's ready. In any case, this proposal doesn't interfere with |
This is ready for review. |
src/TimerOutput.jl
Outdated
@@ -180,6 +185,9 @@ end | |||
function _timer_expr(m::Module, is_debug::Bool, to::Union{Symbol, Expr, TimerOutput}, label, ex::Expr) | |||
timeit_block = quote | |||
local to = $(esc(to)) | |||
if isdummy(to) | |||
return $(esc(ex)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, It is slightly unfortunate to have to insert the full expression here again. It would probable be removed by the compiler early because it knows isdummy
on compile time but perhaps it could be posible to exploit dispatch a bit better with something like:
function enter_section(::TimerOutput)
accumulated_data = $(push!)(to, $(esc(label)))
return accumulated_data, gc_bytes(), time_ns()
end
enter_section(::NoTimerOutput) = nothing
function exit_section(::TimerOutput, data) =
if enabled
do_accumulate!(
...
exit_section(::NoTimerOutput, data::Nothing) = nothing
and then have the macro expand to
local data = enter_section!(to)
local val
$(Expr(:tryfinally,
....
)
exit_section!(to, data)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about doing something like that, but there is one small issue: the try/finally block is always compiled and executed. This adds some overhead, which I agree is really small (~10 ns), but breaks the zero-overhead promise. As you noticed, this is not an issue with the current implementation, as the try/finally block is optimised away by the compiler. But I agree that it's not very elegant to copy the whole expression twice...
(Alternative name could be |
I agree, that's a good suggestion. Maybe |
Any update on this PR? FWIW, |
I just rebased to the current master. A single test fails, in which the A very hacky workaround, which seems to work, is to replace this line by: # if TimerOutputs.isdummy($local_to) # current version
if Main.hasfield(typeof($local_to), :enabled) === false
return $ex
end |
@jipolanco I noticed an odd issue where using a NullTimer changed my code behavior. I'll see if I can produce a MWE. Is the "hacky workaround" implemented in your branch already? |
Here's an MWE for the issue using this PR. using TimerOutputs,UnPack,Test
function foo!(dx,x,timer)
@timeit timer "set x" begin
x .= 2.0
end
@timeit timer "set dx" begin
dx .= x
end
return nothing
end
timer = NullTimer() # fails for NullTimer
# timer = TimerOutput() # passes for regular TimerOutput
x = zeros(2)
dx = similar(x)
foo!(dx,x,timer)
@test all(dx .≈ 2.0) |
@jlchan Thanks for noticing that issue! I just pushed a possible fix. |
Works great for my examples - thanks! |
Bumping - any outstanding issues remaining with this PR? |
First of all, thanks again for this really useful package.
I'd like to propose an alternative zero-overhead mechanism for disabling timers, which avoids some of the issues with the
@timeit_debug
macro (see for example #61, maybe #105?). More practically, it also provides a very convenient way of enabling and disabling timers (in my opinion).The idea is simple: define a dummy type, that I have called
NoTimerOutput
(but this can be changed...), that works as a direct replacement for aTimerOutput
. In other words, to disable timings in their code, a user would just need to switch from a realTimerOutput
to a dummy timer.I've checked that the proposed implementation disables timings and has zero overhead. This also includes the case of annotated functions (as in
@timeit to f(to) = 3
). See below for some examples, which were run on Julia 1.6-rc3.In this first example,
NoTimerOutput
has zero overhead, just like@timeit_debug
:Second example using function annotations, based on #61. Note that, as discussed in #61,
@timeit_debug
has always some overhead. On the other hand, passing aNoTimerOutput
to@timeit
has zero overhead:This PR is work in progress, as it's still missing tests as well as some documentation on its usage. To make it fully compatible with
TimerOutput
, it may also need some dummy definitions forgetindex
,flatten
,complement!
, and other documented functions.