Skip to content

Commit 5718fe7

Browse files
authoredMar 8, 2023
Add descend_clicked (#211)
Also modifies and expands the README about topics related to performance-improvement.
1 parent 7694c1f commit 5718fe7

File tree

5 files changed

+157
-23
lines changed

5 files changed

+157
-23
lines changed
 

‎Project.toml

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
1717
MethodAnalysis = "85b6ec6f-f7df-4429-9514-a64bcd9ee824"
1818
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
1919
Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
20+
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
2021
SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c"
2122
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
2223

@@ -31,6 +32,7 @@ GtkObservables = "1"
3132
IntervalSets = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7"
3233
MethodAnalysis = "0.4"
3334
Preferences = "1.2"
35+
Requires = "1"
3436
SnoopPrecompile = "1"
3537
julia = "1.6"
3638

‎README.md

+91-13
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ using ProfileView
6565
> This is because VSCode defines its own `@profview`, which conflicts with ProfileView's.
6666
> Fix it by using `ProfileView.@profview`.
6767
68-
If you're following along, you should see something like this:
68+
If you're following along, you may see something like this:
6969

7070
![ProfileView](readme_images/pv1.png)
7171

72-
(Note that collected profiles can vary from run-to-run, so don't be alarmed
72+
(Note that collected profiles can vary by Julia version and from run-to-run, so don't be alarmed
7373
if you get something different.)
7474
This plot is a visual representation of the *call graph* of the code that you just profiled.
7575
The "root" of the tree is at the bottom; if you move your mouse along the long horizontal
@@ -115,19 +115,22 @@ prompt, and so the bottom `eval` call is red.
115115
Red bars are problematic only when they account for a sizable
116116
fraction of the top of a call stack, as only in such cases are they likely to be
117117
the source of a significant performance bottleneck.
118-
We can see that `mapslices` relies on run-time dispatch;
118+
In the image above, can see that `mapslices` relied on run-time dispatch;
119119
from the absence of pastel-colored bars above much of the red, we
120-
might guess that this makes a substantial
120+
might guess that this made a substantial
121121
contribution to its total run time.
122-
123-
To determine the nature of the inference problem(s) in a red bar, left-click on it
124-
and then enter
125-
126-
```julia
127-
julia> warntype_last()
128-
```
129-
130-
at the REPL. You'll see the result of Julia's `code_warntype` for that particular call.
122+
(Your version of Julia may show different results.)
123+
See [Solving type-inference problems](solving-type-inference-problems) below
124+
for tips on how to efficiently diagnose the nature of the problem.
125+
126+
Yellow is also a special color: it indicates a site of garbage collection, which can be
127+
triggered at a site of memory allocation. You may find that such bars lead you to lines
128+
whose performance can be improved by reducing the amount of temporary memory allocated
129+
by your program. One common example is to consider using `@views(A[:, i] .* v)` instead
130+
of `A[:, i] .* v`; the latter creates a new column-vector from `A`, whereas the former
131+
just creates a reference to it.
132+
Julia's [memory profiler](https://docs.julialang.org/en/v1/stdlib/Profile/#Memory-profiling)
133+
may provide much more information about the usage of memory in your program.
131134

132135
## GUI features
133136

@@ -184,6 +187,81 @@ Some default settings can be changed and retained across settings through a
184187
**NOTE**: ProfileView does not support the old JLD-based `*.jlprof` files anymore.
185188
Use the format provided by FlameGraphs v0.2 and higher.
186189

190+
191+
## Solving type-inference problems
192+
193+
[Cthulhu.jl](https://github.com/JuliaDebug/Cthulhu.jl) is a powerful tool for
194+
diagnosing problems of type inference. Let's do a simple demo:
195+
196+
```julia
197+
function profile_test_sort(n, len=100000)
198+
for i = 1:n
199+
list = []
200+
for _ in 1:len
201+
push!(list, rand())
202+
end
203+
sort!(list)
204+
end
205+
end
206+
207+
julia> profile_test_sort(1) # to force compilation
208+
209+
julia> @profview profile_test_sort(10)
210+
```
211+
212+
Notice that there are lots of individual red bars (`sort!` is recursive) along the top
213+
row of the image. To determine the nature of the inference problem(s) in a red bar, left-click on it
214+
and then enter
215+
216+
```julia
217+
julia> using Cthulhu
218+
219+
julia> descend_clicked()
220+
```
221+
222+
You may see something like this:
223+
224+
![ProfileView](readme_images/descend.png)
225+
226+
You can see the source code of the running method, with "problematic" type-inference
227+
results highlighted in red. (By default, non-problematic type inference results are
228+
suppressed, but you can toggle their display with `'h'`.)
229+
230+
For this example, you can see that objects extracted from `v` have type `Any`: that's because in
231+
`profile_test_sort`, we created `list` as `list = []`, which makes it a `Vector{Any}`;
232+
in this case, a better option might be `list = Float64[]`. Notice that the *cause* of the performance
233+
problem is quite far-removed from the place where it manifests, because it's only when
234+
the low-level operations required by `sort!` get underway that the consequence of our choice
235+
of container type become an issue. Often it's necessary to "chase" these performance issues
236+
backwards to a caller; for that, `ascend_clicked()` can be useful:
237+
238+
```julia
239+
julia> ascend_clicked()
240+
Choose a call for analysis (q to quit):
241+
> partition!(::Vector{Any}, ::Int64, ::Int64, ::Int64, ::Base.Order.ForwardOrdering, ::Vector{Any}, ::Bool, ::Vector{Any}, ::Int64)
242+
#_sort!#25(::Vector{Any}, ::Int64, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Insertio
243+
kwcall(::NamedTuple{(:t, :offset, :swap, :rev), Tuple{Vector{Any}, Int64, Bool, Bool}}, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSo
244+
#_sort!#25(::Vector{Any}, ::Int64, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Inse
245+
#_sort!#25(::Nothing, ::Nothing, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Insert
246+
_sort!(::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.InsertionSortAlg}, ::Base.Order.ForwardOrdering, ::NamedTuple{(:scratch, :
247+
_sort!(::Vector{Any}, ::Base.Sort.StableCheckSorted{Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.InsertionSortAlg}}, ::Base.Order.ForwardOrde
248+
_sort!(::Vector{Any}, ::Base.Sort.IsUIntMappable{Base.Sort.Small{40, Base.Sort.InsertionSortAlg, Base.Sort.CheckSorted{Base.Sort.ComputeExtrema{Base.
249+
_sort!(::Vector{Any}, ::Base.Sort.IEEEFloatOptimization{Base.Sort.IsUIntMappable{Base.Sort.Small{40, Base.Sort.InsertionSortAlg, Base.Sort.CheckSor
250+
v _sort!(::Vector{Any}, ::Base.Sort.Small{10, Base.Sort.InsertionSortAlg, Base.Sort.IEEEFloatOptimization{Base.Sort.IsUIntMappable{Base.Sort.Small{
251+
```
252+
253+
This is an interactive menu showing each "callee" above the "caller": use the up and down arrows to pick a call to `descend` into. If you scroll to the bottom
254+
you'll see the `profile_test_sort` call that triggered the whole cascade.
255+
256+
You can also see type-inference results without using Cthulhu: just enter
257+
258+
```
259+
julia> warntype_clicked()
260+
```
261+
262+
at the REPL. You'll see the result of Julia's `code_warntype` for the call you clicked on.
263+
264+
187265
### Advanced usage: deeper analysis of specific dispatches
188266
189267
`ProfileView.clicked[]` stores a stackframe entry for the most recently clicked

‎readme_images/descend.png

168 KB
Loading

‎src/ProfileView.jl

+59-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ using Gtk.ShortNames, GtkObservables, Colors, FileIO, IntervalSets
1515
import Cairo
1616
using Graphics
1717
using Preferences
18+
using Requires
1819

1920
using FlameGraphs: Node, NodeData
2021
using Gtk.GConstants.GdkModifierType: SHIFT, CONTROL, MOD1
2122

22-
export @profview, warntype_last
23+
export @profview, warntype_clicked, descend_clicked, ascend_clicked
24+
@deprecate warntype_last warntype_clicked
2325

2426
const clicked = Ref{Any}(nothing) # for getting access to the clicked bar
2527

@@ -55,14 +57,16 @@ This setting is retained for the active environment via a `LocalPreferences.toml
5557
set_theme!(theme::Symbol) = set_preference!(_theme, "theme", theme, (:light, :dark))
5658

5759
"""
58-
warntype_last()
59-
warntype_last(io::IO; kwargs...)
60+
warntype_clicked()
61+
warntype_clicked(io::IO; kwargs...)
6062
6163
Show `code_warntype` for the most recently-clicked bar.
6264
6365
Optionally direct output to stream `io`. Keyword arguments are passed to `code_warntype`.
66+
67+
See also: [`descend_clicked`](@ref).
6468
"""
65-
function warntype_last(io::IO=stdout; kwargs...)
69+
function warntype_clicked(io::IO=stdout; kwargs...)
6670
st = clicked[]
6771
if st === nothing || st.linfo === nothing
6872
@warn "click on a non-inlined bar to see `code_warntype` info"
@@ -71,6 +75,30 @@ function warntype_last(io::IO=stdout; kwargs...)
7175
return code_warntype(io, call_type(st.linfo.specTypes)...; kwargs...)
7276
end
7377

78+
"""
79+
descend_clicked(; optimize=false, iswarn=true, hide_type_stable=true, kwargs...)
80+
81+
Run [Cthulhu's](https://github.com/JuliaDebug/Cthulhu.jl) `descend` for the most recently-clicked bar.
82+
To make this function available, you must be `using Cthulhu` in your session.
83+
84+
Keyword arguments control the initial view mode.
85+
86+
See also: [`ascend_clicked`](@ref), [`descend_warntype`](@ref)
87+
"""
88+
function descend_clicked end
89+
90+
"""
91+
ascend_clicked(; hide_type_stable=true, kwargs...)
92+
93+
Run [Cthulhu's](https://github.com/JuliaDebug/Cthulhu.jl) `ascend` for the most recently-clicked bar.
94+
To make this function available, you must be `using Cthulhu` in your session.
95+
96+
Keyword arguments control the initial view mode.
97+
98+
See also: [`descend_clicked`](@ref)
99+
"""
100+
function ascend_clicked end
101+
74102
mutable struct ZoomCanvas
75103
bb::BoundingBox # in user-coordinates
76104
c::Canvas
@@ -489,7 +517,7 @@ end
489517
490518
The toolbar at the top includes icons to load and save profile data. Clicking the save icon will prompt you for a filename; you should use extension *.jlprof for any file you save. Launching `ProfileView.view(nothing)` opens a blank window, which you can populate with saved data by clicking on the "open" icon.
491519
492-
After clicking on a bar, you can type `warntype_last()` and see the result of `code_warntype` for the call represented by that bar.
520+
After clicking on a bar, you can type `warntype_clicked()` and see the result of `code_warntype` for the call represented by that bar. Alterntively, use `descend_clicked` (requires loading Cthulhu.jl).
493521
494522
`ProfileView.view(windowname="method1")` allows you to name your window, which can help avoid confusion when opening several ProfileView windows simultaneously.
495523
@@ -506,6 +534,32 @@ end
506534
discardfirstcol(A) = A[:,2:end]
507535
discardfirstcol(A::IndirectArray) = IndirectArray(A.index[:,2:end], A.values)
508536

537+
function __init__()
538+
@require Cthulhu="f68482b8-f384-11e8-15f7-abe071a5a75f" begin
539+
function descend_clicked(; optimize=false, iswarn=true, hide_type_stable=true, kwargs...)
540+
st = clicked[]
541+
if st === nothing || st.linfo === nothing
542+
@warn "the bar you clicked on might have been inlined and unavailable for inspection. Click on a non-inlined bar to `descend`."
543+
return nothing
544+
end
545+
return Cthulhu.descend(st.linfo; optimize, iswarn, hide_type_stable, kwargs...)
546+
end
547+
function ascend_clicked(; hide_type_stable=true, kwargs...)
548+
st = clicked[]
549+
if st === nothing || st.linfo === nothing
550+
@warn "the bar you clicked on might have been inlined and unavailable for inspection. Click on a non-inlined bar to `descend`."
551+
return nothing
552+
end
553+
return Cthulhu.ascend(st.linfo; hide_type_stable, kwargs...)
554+
end
555+
end
556+
Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
557+
if (exc.f === descend_clicked || exc.f === ascend_clicked) && isempty(argtypes)
558+
printstyled(io, "\n`using Cthulhu` is required for `$(exc.f)`"; color=:yellow)
559+
end
560+
end
561+
end
562+
509563
using SnoopPrecompile
510564

511565
let

‎test/runtests.jl

+5-5
Original file line numberDiff line numberDiff line change
@@ -161,19 +161,19 @@ end
161161
ProfileView.closeall()
162162
end
163163

164-
@testset "warntype_last" begin
165-
# Test `warntype_last`
164+
@testset "warntype_clicked" begin
165+
# Test `warntype_clicked`
166166
ProfileView.clicked[] = nothing
167-
@test_logs (:warn, "click on a non-inlined bar to see `code_warntype` info") warntype_last() === nothing
167+
@test_logs (:warn, "click on a non-inlined bar to see `code_warntype` info") warntype_clicked() === nothing
168168
_, bt = add2(Any[1,2])
169169
st = stacktrace(bt)
170170
ProfileView.clicked[] = st[1]
171171
io = IOBuffer()
172-
warntype_last(io)
172+
warntype_clicked(io)
173173
str = String(take!(io))
174174
@test occursin("Base.getindex(x, 1)::ANY", str)
175175
idx = findfirst(sf -> sf.inlined, st)
176176
ProfileView.clicked[] = st[idx]
177-
@test_logs (:warn, "click on a non-inlined bar to see `code_warntype` info") warntype_last(io) === nothing
177+
@test_logs (:warn, "click on a non-inlined bar to see `code_warntype` info") warntype_clicked(io) === nothing
178178
end
179179
end

0 commit comments

Comments
 (0)
Please sign in to comment.