I am testing Julia-1.9-beta2 on Ubuntu 22.04 and would like to improve the performance of plots using Bokeh.jl.
In the moment the first time to plot with this package is about 13s, and it would be nice to improve that.
Initial situation:
julia> @time @eval include("src/plot.jl")
13.155351 seconds (18.53 M allocations: 1.189 GiB, 3.47% gc time, 117.33% compilation time)
Debug approach:
using SnoopCompile
invalidations = @snoopr begin
# Your code goes here!
# example for using plotting in Julia
using Bokeh, BokehBlink
Bokeh.settings!(display=:blink)
function plot(x, y; keywords...)
p = figure(; sizing_mode="fixed", width=1200, height=700) # stretch_both
Bokeh.plot!(p, Bokeh.Line; x=x, y=y, color="blue", line_width=2, keywords...)
display(p)
end
x = 0:0.01:100
y1 = sin.(x)
plot(x, y1)
nothing
end;
trees = SnoopCompile.invalidation_trees(invalidations);
@show length(SnoopCompile.uinvalidated(invalidations)) # show total invalidations
show(trees[end]) # show the most invalidated method
# Count number of children (number of invalidations per invalidated method)
n_invalidations = map(trees) do methinvs
SnoopCompile.countchildren(methinvs)
end
import Plots
Plots.plot(
1:length(trees),
n_invalidations;
markershape=:circle,
xlabel="i-th method invalidation",
label="Number of children per method invalidations"
)
Output:
julia> include("src/test.jl")
length(SnoopCompile.uinvalidated(invalidations)) = 512
inserting write(iod::HTTP.DebugRequest.IODebug, x::String) @ HTTP.DebugRequest ~/.julia/packages/HTTP/aTjcj/src/IODebug.jl:38 invalidated:
backedges: 1: superseding write(io::IO, s::Union{SubString{String}, String}) @ Base strings/io.jl:244 with MethodInstance for write(::IO, ::String) (401 children)
qt.qpa.plugin: Could not find the Qt platform plugin "wayland" in ""
Questions:
How can I fix the error: “qt.qpa.plugin: Could not find the Qt platform plugin “wayland” in “””
How can I fix the most invalidated method:
inserting write(iod::HTTP.DebugRequest.IODebug, x::String) @ HTTP.DebugRequest ~/.julia/packages/HTTP/aTjcj/src/IODebug.jl:38 invalidated:
backedges: 1: superseding write(io::IO, s::Union{SubString{String}, String}) @ Base strings/io.jl:244 with MethodInstance for write(::IO, ::String) (401 children)
Impossible to say without looking deeper at it. Sometimes, you can fix invalidations by fixing the package itself. Sometimes, you need to look at Julia Base or standard libraries - or even other packages, depending on the call chains involved.
But "…looking deeper at it. " is not exactly a scientific method. I would like to look deeper at it, but how, where to start, and what to investigate?
A good start is usually to ascend(trees[end].backedges[end]), looking at the call chains, figuring out whether there is a type instability in callers of the invalidated method that can be fixed. That’s described in the docs I linked above.
(Plotting2) pkg> up
┌ Warning: could not download https://pkg.julialang.org/registries
│ exception = RequestError: HTTP/2 502 while requesting https://pkg.julialang.org/registries
â”” @ Pkg.Registry ~/packages/julias/julia-1.9/share/julia/stdlib/v1.9/Pkg/src/Registry/Registry.jl:69
Updating registry at `~/.julia/registries/General.toml`
No Changes to `~/repos/Plotting2/Project.toml`
No Changes to `~/repos/Plotting2/Manifest.toml`
So some package depends on an old version of HTTP…
(Plotting2) pkg> add HTTP@1.6.2
Resolving package versions...
ERROR: Unsatisfiable requirements detected for package Blink [ad839575]:
Blink [ad839575] log:
├─possible versions are: 0.8.0-0.12.5 or uninstalled
├─restricted by compatibility requirements with BokehBlink [c7d4f63d] to versions: 0.12.5
│ └─BokehBlink [c7d4f63d] log:
│ ├─possible versions are: 0.1.0-0.1.1 or uninstalled
│ └─restricted to versions * by an explicit requirement, leaving only versions: 0.1.0-0.1.1
└─restricted by compatibility requirements with Mux [a975b10e] to versions: uninstalled — no versions left
└─Mux [a975b10e] log:
├─possible versions are: 0.4.0-1.0.1 or uninstalled
└─restricted by compatibility requirements with HTTP [cd3eb016] to versions: 1.0.0-1.0.1 or uninstalled
└─HTTP [cd3eb016] log:
├─possible versions are: 0.6.10-1.6.2 or uninstalled
└─restricted to versions 1.6.2 by an explicit requirement, leaving only versions: 1.6.2
So both Bokeh.jl and Mux.jl limit the version of HTTP.jl to an old version where this issue is not fixed…
I now also tried the suggested approach from @tim.holy :
using SnoopCompileCore
invs = @snoopr using Bokeh, BokehBlink;
tinf = @snoopi_deep begin
Bokeh.settings!(display=:blink)
function plot(x, y; keywords...)
p = figure(; sizing_mode="fixed", width=1200, height=700) # stretch_both
Bokeh.plot!(p, Bokeh.Line; x=x, y=y, color="blue", line_width=2, keywords...)
display(p)
end
x = 0:0.01:100
y1 = sin.(x)
plot(x, y1)
nothing
end
using SnoopCompile
trees = invalidation_trees(invs);
staletrees = precompile_blockers(trees, tinf)
Output:
julia> @time @eval include("src/find_invalidations.jl")
[ Info: Listening on: 127.0.0.1:7151, thread id: 1
14.840208 seconds (22.39 M allocations: 1.435 GiB, 3.59% gc time, 11159.82% compilation time: <1% of which was recompilation)
2-element Vector{SnoopCompile.StaleTree}:
inserting one(::Type{X}) where X<:FixedPoint @ FixedPointNumbers ~/.julia/packages/FixedPointNumbers/HAGk2/src/FixedPointNumbers.jl:109 invalidated:
backedges: 1: MethodInstance for one(::Type{T} where T<:Integer) at depth 0 with 1 children blocked InferenceTimingNode: 0.000491/0.000491 on oneunit(::Type{T} where T<:Integer) with 0 direct children
inserting ==(y::JSON3.PointerString, x::String) @ JSON3 ~/.julia/packages/JSON3/CpNms/src/strings.jl:15 invalidated:
backedges: 1: MethodInstance for ==(::AbstractString, ::String) at depth 0 with 2 children blocked InferenceTimingNode: 0.000749/0.000939 on Logging.default_metafmt(::Base.CoreLogging.LogLevel, ::Any, ::Any, ::Any, ::Any, ::Any) with 1 direct children
Only two invalidations?
If I include the code without the debug code I get:
julia> @time @eval include("src/plot.jl")
[ Info: Listening on: 127.0.0.1:7623, thread id: 1
10.793435 seconds (17.50 M allocations: 1.126 GiB, 3.57% gc time, 116.52% compilation time: <1% of which was recompilation)
More than 10 s compilation time, more than 1 GB allocations… Perhaps in this example the invalidations are not the main issue which costs time, but it is something else?
julia> @time_imports using Bokeh
0.7 ms Statistics
39.3 ms FixedPointNumbers
0.2 ms Reexport
58.0 ms ColorTypes
171.4 ms Colors
0.2 ms DefaultApplication
0.1 ms SnoopPrecompile
52.8 ms Parsers
9.3 ms StructTypes
52.2 ms JSON3
0.2 ms DataValueInterfaces
0.8 ms DataAPI
0.1 ms IteratorInterfaceExtensions
0.1 ms TableTraits
6.2 ms OrderedCollections
11.7 ms Tables
634.0 ms Bokeh 8.13% compilation time
julia> @time_imports using BokehBlink
12.4 ms Observables
0.3 ms Requires
27.4 ms JSON
0.3 ms Pidfile
0.7 ms AssetRegistry
2.6 ms Widgets
7.1 ms FunctionalCollections
20.7 ms WebIO
12.4 ms MacroTools
0.3 ms JSExpr
18.7 ms Lazy
1.5 ms Hiccup
1.3 ms URIs
2.2 ms LoggingExtras
0.2 ms MbedTLS_jll
8.4 ms MbedTLS
2.2 ms BitFlags
13.0 ms Preferences
0.3 ms JLLWrappers
3.2 ms OpenSSL_jll 81.22% compilation time
8.7 ms OpenSSL
3.0 ms TranscodingStreams
0.2 ms Zlib_jll
1.0 ms CodecZlib
0.2 ms SimpleBufferStream
23.6 ms HTTP
9.7 ms Mux
1.0 ms Mustache
36.0 ms Blink 113.59% compilation time
1.7 ms BokehBlink
Has that exact workload been precompiled? If you don’t want to modify the source, you can create a Startup package that precompiles that specific workload, e.g.
and then edit the contents of src/Startup.jl to be something like
module Startup
using Bokeh, BokehBlink
using SnoopPrecompile
function plot(x, y; keywords...)
p = figure(; sizing_mode="fixed", width=1200, height=700) # stretch_both
Bokeh.plot!(p, Bokeh.Line; x=x, y=y, color="blue", line_width=2, keywords...)
display(p)
end
@precompile_setup begin
x = 0:0.01:100
y1 = sin.(x)
@precompile_all_calls begin
Bokeh.settings!(display=:blink)
plot(x, y1)
end
end
end # module Startup
Then for me:
julia> using Startup, Bokeh
julia> x = 0:0.01:100;
julia> y1 = sin.(x);
julia> @time @eval begin
Bokeh.settings!(display=:blink)
Startup.plot(x, y1)
end
[ Info: Listening on: 127.0.0.1:7888, thread id: 1
1.559525 seconds (1.85 M allocations: 127.082 MiB, 3.95% gc time, 87.29% compilation time)
That 1.56s might be caused by the invalidations (see “87.29% compilation time”), but I’ve not checked.