From 7b4adc96d0e50aff15d8d29fa838e2553718bb73 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Thu, 18 Jun 2026 09:58:58 +0100 Subject: [PATCH 1/2] Option to plot 'exclusive' times --- Project.toml | 2 +- src/TimerOutputComparisons.jl | 54 ++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 7ee147a..2d251a8 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "TimerOutputComparisons" uuid = "59c811e7-adb4-4b01-a05e-50a2dd0b99a2" -version = "1.0.0" +version = "1.0.1" authors = ["John Omotani and contributors"] [deps] diff --git a/src/TimerOutputComparisons.jl b/src/TimerOutputComparisons.jl index c8e8989..9b16b93 100644 --- a/src/TimerOutputComparisons.jl +++ b/src/TimerOutputComparisons.jl @@ -52,7 +52,7 @@ const possible_use_data = (:ncalls, :time, :allocs) """ compare_timers(timers::Union{AbstractString,Tuple{<:AbstractString,<:AbstractString},Tuple{TimerOutput,<:AbstractString}}...; - flatten::Bool=false, averages::Bool=false, + flatten::Bool=false, averages::Bool=false, exclusive::Bool=false, save_as::Union{AbstractString,Nothing}=nothing, use_data=possible_use_data, legend::Bool=true, root=nothing) @@ -72,6 +72,9 @@ If `flatten=true`, the TimerOutput objects are flattened with `TimerOutputs.flat If `averages=true`, then the time per call and allocs per call will be plotted instead of the total time and total allocs for each timer. +If `exclusive=true`, then subtract the times from any nested timers from each timer. This +should remove any double-counting of times. + If a file-name is passed to `save_as` the plots are saved instead of being displayed interactively. For example, `save_as="foo.png"` would result in the plots being saved as "foo_ncalls.png", "foo_time.png", and "foo_allocs.png". @@ -102,10 +105,14 @@ function compare_timers(timers::Union{AbstractString,<:Tuple{AbstractString,Abst return compare_timers(Tuple(get_timer(t) for t ∈ timers)...; kwargs...) end function compare_timers(timers::Tuple{TimerOutput,String}...; - flatten::Bool=false, averages::Bool=false, + flatten::Bool=false, averages::Bool=false, exclusive::Bool=false, save_as::Union{AbstractString,Nothing}=nothing, use_data=possible_use_data, legend::Bool=true, root=nothing) + if flatten && exclusive + error("Cannot use `flatten=true` and `exclusive=true` at the same time.") + end + if isa(use_data, Symbol) use_data = (use_data,) end @@ -176,10 +183,11 @@ function compare_timers(timers::Tuple{TimerOutput,String}...; if root !== nothing plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, root[end:end], xticks, - averages, true) + averages, exclusive, true) end for name ∈ timer_names - plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, name, xticks, averages) + plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, name, xticks, averages, + exclusive) end if legend @@ -232,7 +240,7 @@ function get_single_timer(to::TimerOutput, name::Vector{String}) end function plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, name::Vector{String}, - xticks, averages, is_root::Bool=false) + xticks, averages, exclusive, is_root::Bool=false) if is_root this_timer_list = to_list else @@ -254,9 +262,41 @@ function plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, name::Vector inspector_label=(self,i,p) -> "$(self.label[])\n$(xtick_values[round(Int64, p[1])]): ncalls=$(ncalls_values[round(Int64, p[1])])") end + function get_time(t) + if t === nothing + return NaN + end + + tval = TimerOutputs.time(t) + + if exclusive + for subtimer ∈ values(t.inner_timers) + tval -= TimerOutputs.time(subtimer) + end + end + + return tval * 1.0e-6 + end + + function get_alloc(t) + if t === nothing + return NaN + end + + aval = TimerOutputs.allocated(t) + + if exclusive + for subtimer ∈ values(t.inner_timers) + aval -= TimerOutputs.allocated(subtimer) + end + end + + return aval / 1024 + end + # Convert times from ns to ms. if ax_time !== nothing - time_values = [t === nothing ? NaN : TimerOutputs.time(t) * 1.0e-6 for t ∈ this_timer_list] + time_values = [get_time(t) for t ∈ this_timer_list] if averages time_values ./= ncalls_values end @@ -266,7 +306,7 @@ function plot_single_timer!(ax_ncalls, ax_time, ax_allocs, to_list, name::Vector end if ax_allocs !== nothing - allocs_values = [t === nothing ? NaN : TimerOutputs.allocated(t) / 1024 for t ∈ this_timer_list] + allocs_values = [get_alloc(t) for t ∈ this_timer_list] if averages allocs_values ./= ncalls_values end From 14fb42a556f5bf46f6b6e57bf0a7c1290d8bcab9 Mon Sep 17 00:00:00 2001 From: John Omotani Date: Thu, 18 Jun 2026 10:03:45 +0100 Subject: [PATCH 2/2] Allow passing keyword arguments through to DataInspector Can be useful, for example, to change the font size so that very long tooltips can be seen. --- src/TimerOutputComparisons.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/TimerOutputComparisons.jl b/src/TimerOutputComparisons.jl index 9b16b93..936cabc 100644 --- a/src/TimerOutputComparisons.jl +++ b/src/TimerOutputComparisons.jl @@ -54,7 +54,8 @@ const possible_use_data = (:ncalls, :time, :allocs) compare_timers(timers::Union{AbstractString,Tuple{<:AbstractString,<:AbstractString},Tuple{TimerOutput,<:AbstractString}}...; flatten::Bool=false, averages::Bool=false, exclusive::Bool=false, save_as::Union{AbstractString,Nothing}=nothing, - use_data=possible_use_data, legend::Bool=true, root=nothing) + use_data=possible_use_data, legend::Bool=true, root=nothing, + datainspector_kwargs::Union{Dict,NamedTuple}=Dict()) Make a plot comparing `timers`. For `t` in `timers`: * if `t` is an AbstractString, load a TimerOutput from the file named `t` using @@ -87,6 +88,9 @@ To remove the legend, pass `legend=false`. If you only want to use_data some (possibly nested) sub-timer, pass its name as a String, Tuple or Vector to `root`. For example if the top-level TimerOutput is `to`, passing `root=("foo", "bar")` will use_data only the timers in `to["foo"]["bar"]`. + +`datainspector_kwargs` are passed as the keyword arguments to the `DataInspector()` call +that creates the tooltips. """ compare_timers @@ -107,7 +111,8 @@ end function compare_timers(timers::Tuple{TimerOutput,String}...; flatten::Bool=false, averages::Bool=false, exclusive::Bool=false, save_as::Union{AbstractString,Nothing}=nothing, - use_data=possible_use_data, legend::Bool=true, root=nothing) + use_data=possible_use_data, legend::Bool=true, root=nothing, + datainspector_kwargs::Union{Dict,NamedTuple}=Dict()) if flatten && exclusive error("Cannot use `flatten=true` and `exclusive=true` at the same time.") @@ -208,7 +213,7 @@ function compare_timers(timers::Tuple{TimerOutput,String}...; backend = Makie.current_backend() for fig in (fig_ncalls, fig_time, fig_allocs) if fig !== nothing - DataInspector(fig) + DataInspector(fig; datainspector_kwargs...) display(backend.Screen(), fig) end end