GitHub - timholy/ProfileView.jl: Visualization of Julia profiling data (original) (raw)

Build Status codecov PkgEval

NOTE: Jupyter/IJulia and SVG support has migrated to the ProfileSVG package.

Introduction

This package contains tools for visualizing and interacting with profiling data collected with Julia's built-in samplingprofiler. It can be helpful for getting a big-picture overview of the major bottlenecks in your code, and optionally highlights lines that trigger garbage collection as potential candidates for optimization.

This type of plot is known as a flame graph. The main logic is handled by the FlameGraphs package; this package is just a visualization front-end.

Compared to other flamegraph viewers, ProfileView adds interactivity features, such as:

These features are described in detail below.

Installation

Within Julia, use the package manager:

using Pkg Pkg.add("ProfileView")

Tutorial: usage and visual interpretation

To demonstrate ProfileView, first we have to collect some profiling data. Here's a simple test function for demonstration:

function profile_test(n) for i = 1:n A = randn(100,100,20) m = maximum(A) Am = mapslices(sum, A; dims=2) B = A[:,:,5] Bsort = mapslices(sort, B; dims=1) b = rand(100) C = B.*b end end

using ProfileView @profview profile_test(1) # run once to trigger compilation (ignore this one) @profview profile_test(10)

@profview f(args...) is just shorthand for Profile.clear(); @profile f(args...); ProfileView.view(). (These commands require that you first say using Profile, the Julia profiling standard library.)

If you use ProfileView from VSCode you'll get an error UndefVarError: @profview not defined. This is because VSCode defines its own @profview, which conflicts with ProfileView's. Fix it by using ProfileView.@profview.

If you're following along, you may see something like this:

ProfileView

(Note that collected profiles can vary by Julia version and from run-to-run, so don't be alarmed if you get something different.) This plot is a visual representation of the call graph of the code that you just profiled. The "root" of the tree is at the bottom; if you move your mouse along the long horizontal bar at the bottom, you'll see a tooltip that's something like

This refers to one of Julia's own source files, base/boot.jl.eval is the name of the function being executed, and 330 is the line number of the file. This is the function that evaluated your profile_test(10) command that you typed at the REPL. (Indeed, to reduce the amount of internal "overhead" in the flamegraph, some of these internals are truncated; see the norepl option of FlameGraphs.flamegraph.) If you move your mouse upwards, you'll then see bars corresponding to the function(s) you ran with @profview (in this case, profile_test). Thus, the vertical axis represents nesting depth: bars lie on top of the bars that called them.

The horizontal axis represents the amount of time (more precisely, the number of backtraces) spent at each line. The row at which the single long bar breaks up into multiple different-colored bars corresponds to the execution of different lines from profile_test. The fact that they are all positioned on top of the lower peach-colored bar means that all of these lines are called by the same "parent" function. Within a block of code, they are sorted in order of increasing line number, to make it easier for you to compare to the source code.

From this visual representation, we can very quickly learn several things about this function:

Color Coding

GUI features

Customizable defaults:

Some default settings can be changed and retained across settings through aLocalPreferences.toml file that is added to the active environment.

Gtk Interface

NOTE: ProfileView does not support the old JLD-based *.jlprof files anymore. Use the format provided by FlameGraphs v0.2 and higher.

Solving type-inference problems

Cthulhu.jl is a powerful tool for diagnosing problems of type inference. Let's do a simple demo:

function profile_test_sort(n, len=100000) for i = 1:n list = [] for _ in 1:len push!(list, rand()) end sort!(list) end end

julia> profile_test_sort(1) # to force compilation

julia> @profview profile_test_sort(10)

Notice that there are lots of individual red bars (sort! is recursive) along the top row of the image. To determine the nature of the inference problem(s) in a red bar, left-click on it and then enter

julia> using Cthulhu

julia> descend_clicked()

You may see something like this:

ProfileView

You can see the source code of the running method, with "problematic" type-inference results highlighted in red. (By default, non-problematic type inference results are suppressed, but you can toggle their display with 'h'.)

For this example, you can see that objects extracted from v have type Any: that's because inprofile_test_sort, we created list as list = [], which makes it a Vector{Any}; in this case, a better option might be list = Float64[]. Notice that the cause of the performance problem is quite far-removed from the place where it manifests, because it's only when the low-level operations required by sort! get underway that the consequence of our choice of container type become an issue. Often it's necessary to "chase" these performance issues backwards to a caller; for that, ascend_clicked() can be useful:

julia> ascend_clicked() Choose a call for analysis (q to quit):

partition!(::Vector{Any}, ::Int64, ::Int64, ::Int64, ::Base.Order.ForwardOrdering, ::Vector{Any}, ::Bool, ::Vector{Any}, ::Int64) #_sort!#25(::Vector{Any}, ::Int64, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Insertio kwcall(::NamedTuple{(:t, :offset, :swap, :rev), Tuple{Vector{Any}, Int64, Bool, Bool}}, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSo #_sort!#25(::Vector{Any}, ::Int64, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Inse #_sort!#25(::Nothing, ::Nothing, ::Bool, ::Bool, ::typeof(Base.Sort._sort!), ::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.Insert _sort!(::Vector{Any}, ::Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.InsertionSortAlg}, ::Base.Order.ForwardOrdering, ::NamedTuple{(:scratch, : _sort!(::Vector{Any}, ::Base.Sort.StableCheckSorted{Base.Sort.ScratchQuickSort{Missing, Missing, Base.Sort.InsertionSortAlg}}, ::Base.Order.ForwardOrde _sort!(::Vector{Any}, ::Base.Sort.IsUIntMappable{Base.Sort.Small{40, Base.Sort.InsertionSortAlg, Base.Sort.CheckSorted{Base.Sort.ComputeExtrema{Base. _sort!(::Vector{Any}, ::Base.Sort.IEEEFloatOptimization{Base.Sort.IsUIntMappable{Base.Sort.Small{40, Base.Sort.InsertionSortAlg, Base.Sort.CheckSor v _sort!(::Vector{Any}, ::Base.Sort.Small{10, Base.Sort.InsertionSortAlg, Base.Sort.IEEEFloatOptimization{Base.Sort.IsUIntMappable{Base.Sort.Small{

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 you'll see the profile_test_sort call that triggered the whole cascade.

You can also see type-inference results without using Cthulhu: just enter

julia> warntype_clicked()

at the REPL. You'll see the result of Julia's code_warntype for the call you clicked on.

These commands all use ProfileView.clicked[], which stores a stackframe entry for the most recently clicked bar.

Command-line options

The view command has the following syntax:

function view([fcolor,] data = Profile.fetch(); lidict = nothing, C = false, fontsize = 14, kwargs...)

Here is the meaning of the different arguments:

These are the main options, but there are others; see FlameGraphs for more details.

Source locations & Revise (new in ProfileView 0.5.3)

Profiling and Revise are natural partners, as together they allow you to iteratively improve the performance of your code. If you use Revise and are tracking the source files (either as a package or with includet), the source locations (file and line number) reported by ProfileView will match the current code at the time the window is created.