diff --git a/README.md b/README.md index 723170f..f419a08 100644 --- a/README.md +++ b/README.md @@ -397,6 +397,10 @@ By default, debug timings are disabled, and this conditional should be optimized If a user calls `TimerOutputs.enable_debug_timings()`, the `.timeit_debug_enabled()` method will be redefined, causing all dependent methods to be recompiled within that module. This may take a while, and hence is intended only for debugging usage, however all calls to `@timeit_debug` (within that Module) will thereafter be enabled. +As an alternative to `@timeit_debug`, this package also provides a `NullTimer` type that allows disabling all timings with zero overhead. +This type represents a dummy timer which acts as a drop-in replacement for regular `TimerOutput`s. +From a user's perspective, it suffices to replace `to = TimerOutput()` by `to = NullTimer()` at the beginning of their code to fully disable timers. + ## Author Kristoffer Carlsson - @KristofferC diff --git a/src/NullTimer.jl b/src/NullTimer.jl new file mode 100644 index 0000000..c0921fb --- /dev/null +++ b/src/NullTimer.jl @@ -0,0 +1,30 @@ +struct NullTimer <: AbstractTimerOutput + name::String + NullTimer(label::String = "root") = new(label) +end + +isdummy(::NullTimer) = true + +Base.copy(to::NullTimer) = NullTimer(to.name) + +# Add definitions for exported methods +reset_timer!(to::NullTimer) = to +timeit(f::Function, ::NullTimer, ::String) = f() +enable_timer!(::NullTimer) = true +disable_timer!(::NullTimer) = false +flatten(to::NullTimer) = to + +ncalls(::NullTimer) = 0 +time(::NullTimer) = 0 +allocated(::NullTimer) = 0 +tottime(::NullTimer) = 0 +totallocated(::NullTimer) = 0 + +complement!(to::NullTimer) = to + +Base.haskey(::NullTimer, ::String) = false +Base.getindex(::NullTimer, key::String) = throw(KeyError(key)) + +function Base.show(io::IO, to::NullTimer) + print(io, "Timer \"", to.name, "\" is a dummy timer") +end diff --git a/src/TimerOutput.jl b/src/TimerOutput.jl index ac1187e..2d5f791 100644 --- a/src/TimerOutput.jl +++ b/src/TimerOutput.jl @@ -19,7 +19,12 @@ end ############### # TimerOutput # ############### -mutable struct TimerOutput + +abstract type AbstractTimerOutput end + +isdummy(::AbstractTimerOutput) = false + +mutable struct TimerOutput <: AbstractTimerOutput start_data::TimeData accumulated_data::TimeData inner_timers::Dict{String,TimerOutput} @@ -201,20 +206,24 @@ function _timer_expr(m::Module, is_debug::Bool, to::Union{Symbol, Expr, TimerOut @gensym local_to enabled accumulated_data b₀ t₀ val timeit_block = quote $local_to = $to - $enabled = $local_to.enabled - if $enabled - $accumulated_data = $(push!)($local_to, $label) + if Main.hasfield(typeof($local_to), :enabled) === false + $val = $ex + else + $enabled = $local_to.enabled + if $enabled + $accumulated_data = $(push!)($local_to, $label) + end + $b₀ = $(gc_bytes)() + $t₀ = $(time_ns)() + $(Expr(:tryfinally, + :($val = $ex), + quote + if $enabled + $(do_accumulate!)($accumulated_data, $t₀, $b₀) + $(pop!)($local_to) + end + end)) end - $b₀ = $(gc_bytes)() - $t₀ = $(time_ns)() - $(Expr(:tryfinally, - :($val = $ex), - quote - if $enabled - $(do_accumulate!)($accumulated_data, $t₀, $b₀) - $(pop!)($local_to) - end - end)) $val end @@ -357,6 +366,9 @@ notimeit_expr(ex::Expr) = notimeit_expr(:($(TimerOutputs.DEFAULT_TIMER)), ex) function notimeit_expr(to, ex::Expr) return quote local to = $(esc(to)) + if isdummy(to) + return $(esc(ex)) + end local enabled = to.enabled $(disable_timer!)(to) local val diff --git a/src/TimerOutputs.jl b/src/TimerOutputs.jl index 4979733..30294ac 100644 --- a/src/TimerOutputs.jl +++ b/src/TimerOutputs.jl @@ -5,6 +5,7 @@ using ExprTools import Base: show, time_ns export TimerOutput, @timeit, @timeit_debug, reset_timer!, print_timer, timeit, enable_timer!, disable_timer!, @notimeit +export AbtractTimerOutput, NullTimer # https://github.com/JuliaLang/julia/pull/33717 if VERSION < v"1.4.0-DEV.475" @@ -21,6 +22,7 @@ using Printf include("TimerOutput.jl") +include("NullTimer.jl") include("show.jl") include("utilities.jl") diff --git a/src/show.jl b/src/show.jl index f917269..687e940 100644 --- a/src/show.jl +++ b/src/show.jl @@ -1,9 +1,9 @@ print_timer(; kwargs...) = print_timer(stdout; kwargs...) -print_timer(to::TimerOutput; kwargs...) = print_timer(stdout, to; kwargs...) +print_timer(to::AbstractTimerOutput; kwargs...) = print_timer(stdout, to; kwargs...) print_timer(io::IO; kwargs...) = print_timer(io, DEFAULT_TIMER; kwargs...) -print_timer(io::IO, to::TimerOutput; kwargs...) = (show(io, to; kwargs...); println(io)) +print_timer(io::IO, to::AbstractTimerOutput; kwargs...) = (show(io, to; kwargs...); println(io)) -Base.show(to::TimerOutput; kwargs...) = show(stdout, to; kwargs...) +Base.show(to::AbstractTimerOutput; kwargs...) = show(stdout, to; kwargs...) function Base.show(io::IO, to::TimerOutput; allocations::Bool = true, sortby::Symbol = :time, linechars::Symbol = :unicode, compact::Bool = false, title::String = "") sortby in (:time, :ncalls, :allocations, :name) || throw(ArgumentError("sortby should be :time, :allocations, :ncalls or :name, got $sortby")) linechars in (:unicode, :ascii) || throw(ArgumentError("linechars should be :unicode or :ascii, got $linechars")) diff --git a/test/runtests.jl b/test/runtests.jl index f969690..cdb75fd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -511,3 +511,44 @@ TimerOutputs.enable_debug_timings(@__MODULE__) @test ncalls(subto["baz"]) == 2 end end + +@testset "dummy timers" begin + to = TimerOutput() + nt = NullTimer() + + @test repr(nt) == "Timer \"root\" is a dummy timer" + + let io = IOBuffer() + print_timer(io, nt) + @test String(take!(io)) == "Timer \"root\" is a dummy timer\n" + end + + @test copy(nt) == nt + @test reset_timer!(nt) === nt + @test enable_timer!(nt) == true + @test disable_timer!(nt) == false + @test flatten(nt) === nt + + foo(x) = x + x + @timeit tt foo(x, tt) = foo(x) + @test foo(4) == foo(4, to) + @test foo(4) == foo(4, nt) + + @test @notimeit(nt, foo(3)) == foo(3) + + @timeit to "sin" sin(3) + @timeit nt "sin" sin(3) + + @test haskey(to, "sin") + @test !haskey(nt, "sin") + + @test to["sin"] isa TimerOutput + @test_throws KeyError("sin") nt["sin"] + + @test ncalls(nt) == 0 + @test TimerOutputs.time(nt) == 0 + @test TimerOutputs.allocated(nt) == 0 + @test TimerOutputs.tottime(nt) == 0 + @test TimerOutputs.totallocated(nt) == 0 + @test TimerOutputs.complement!(nt) === nt +end