Skip to content
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

Add globally registered timers #135

Merged
merged 5 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,55 @@ julia> print_timer(to)

In order to complement the default timer simply call `TimerOutputs.complement!()`.

## Shared Timers

It is sometimes desirable for a timer to be shared across all users of the
package. For this purpose, `get_timer` maintains a collection of named timers
defined in the package.

`get_timer(timer_name::String)` retrieves the timer `timer_name` from the
collection, creating a new timer if none already exists.

For example:
```julia
module UseTimer
using TimerOutputs: @timeit, get_timer

function foo()
to = get_timer("Shared")
@timeit get_timer("Shared") "foo" sleep(0.1)
end
end

@timeit get_timer("Shared") "section1" begin
UseTimer.foo()
sleep(0.01)
end
```

which prints:
```julia
julia> print_timer(get_timer("Shared"))
───────────────────────────────────────────────────────────────────
Time Allocations
────────────────────── ───────────────────────
Tot / % measured: 17.1s / 0.82% 44.0MiB / 2.12%

Section ncalls time %tot avg alloc %tot avg
───────────────────────────────────────────────────────────────────
section1 1 140ms 100% 140ms 956KiB 100% 956KiB
foo 1 102ms 72.7% 102ms 144B 0.01% 144B
───────────────────────────────────────────────────────────────────
```

Note that the result of `get_timer` should not be called from top-level in a
package that is getting precompiled since the retrieved timer will no longer be
shared with other users getting a timer with the same name. Also, this function
is not recommended to be used extensively by libraries as the namespace is
shared and collisions are possible if two libraries happen to use the same timer
name.


## Overhead

There is a small overhead in timing a section (0.25 μs) which means that this package is not suitable for measuring sections that finish very quickly.
Expand Down
16 changes: 16 additions & 0 deletions src/TimerOutput.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ Base.copy(to::TimerOutput) = TimerOutput(copy(to.start_data), copy(to.accumulate
copy(to.timer_stack), to.name, to.flattened, to.enabled, to.totmeasured, "", nothing)

const DEFAULT_TIMER = TimerOutput()
const _timers = Dict{String, TimerOutput}("Default" => DEFAULT_TIMER)
const _timers_lock = ReentrantLock() # needed for adding new timers on different threads
"""
get_timer(name::String)

Returns the `TimerOutput` associated with `name`.
If no timers are associated with `name`, a new `TimerOutput` will be created.
"""
function get_timer(name::String)
lock(_timers_lock) do
if !haskey(_timers, name)
_timers[name] = TimerOutput(name)
end
return _timers[name]
end
end

# push! and pop!
function Base.push!(to::TimerOutput, label::String)
Expand Down
2 changes: 1 addition & 1 deletion src/TimerOutputs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using ExprTools

import Base: show, time_ns
export TimerOutput, @timeit, @timeit_debug, reset_timer!, print_timer, timeit,
enable_timer!, disable_timer!, @notimeit
enable_timer!, disable_timer!, @notimeit, get_timer

# https://github.com/JuliaLang/julia/pull/33717
if VERSION < v"1.4.0-DEV.475"
Expand Down
21 changes: 21 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,27 @@ end
@test "foo" in keys(DEFAULT_TIMER.inner_timers)
TimerOutputs.reset_timer!()

# Test sharing timers between modules
@test !haskey(TimerOutputs._timers, "TestModule2")
@test !haskey(TimerOutputs._timers, "my_timer")

to = get_timer("my_timer")
@timeit to "foo" sleep(0.1)
@test ncalls(get_timer("my_timer")["foo"]) == 1

module TestModule2
using TimerOutputs: @timeit, get_timer
foo(x) = x
@timeit get_timer("TestModule2") "foo" foo(1)
@timeit get_timer("my_timer") "foo" foo(1)
end

# Timer from module is accessible to root
@test haskey(TimerOutputs._timers, "TestModule2")
@test ncalls(get_timer("TestModule2")["foo"]) == 1
# Timer from root is accessible to module
@test ncalls(get_timer("my_timer")["foo"]) == 2

# Broken
#=
# Type inference with @timeit_debug
Expand Down