-
-
Notifications
You must be signed in to change notification settings - Fork 360
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
Cache axis args in a dictionary #3775
Conversation
These axis arguments can be generated lots of times for large plots. We can save time by caching them in a dictionary. For my large map test plot: ``` Before: 1.075 s (3393429 allocations: 183.49 MiB) After: 959.024 ms (3393294 allocations: 181.64 MiB) - 10% improvement in speed, small decrease in allocations TTFP Before: 7.543192 seconds (26.79 M allocations: 1.544 GiB, 3.89% gc time, 0.06% compilation time) TTFP After: 6.886222 seconds (23.58 M allocations: 1.355 GiB, 3.63% gc time, 0.07% compilation time) - 8% improvement in speed, 12% fewer allocations, 12% lower allocation amount ``` The cost of this is, I think, just 3-4 kb. Is there a function to read out the size in memory of a dictionary? `sizeof` for the dictionary itself just returns 64.
Codecov Report
@@ Coverage Diff @@
## master #3775 +/- ##
==========================================
- Coverage 63.34% 62.85% -0.50%
==========================================
Files 28 28
Lines 7377 7104 -273
==========================================
- Hits 4673 4465 -208
+ Misses 2704 2639 -65
Continue to review full report at Codecov.
|
Generalized a bit, new time:
Not much time savings over the initial change, but an extra 15% allocations gone and 8% allocation amount. Marginal increase in TTFP, but still improved over the current master. |
My mental model on Could you share a mwe for the TTFP, so that I can also check the numbers on my machine ? |
I hear you haha. Each time a symbol is created, it has to make a new string. This should do it. U.S. Census county shapefiles: https://www2.census.gov/geo/tiger/GENZ2018/shp/cb_2018_us_county_5m.zip using Shapefile
using Plots
main() = begin
shapepath = joinpath("usgeodata", "cb_2018_us_county_5m.shp")
table = Shapefile.Table(shapepath)
geoms = Shapefile.shapes(table)
stateids = parse.(Int, table.STATEFP)
states = (stateids .< 57)
stateids = stateids[states]
geoms = geoms[states]
ALASKA = 2
HAWAII = 15
lower48geoms = geoms[stateids .∉ Ref([ALASKA, HAWAII])]
plot(lower48geoms)
return
end
@time main() |
Thanks, but |
Oops, that'll teach me to run on a fresh session. Updated it. |
My numbers: without PR $ julia
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
6.162806 seconds (11.35 M allocations: 661.625 MiB, 0.19% compilation time)
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
1.506822 seconds (4.78 M allocations: 279.002 MiB) # repeated with PR (and no const) $ julia
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
5.992996 seconds (9.49 M allocations: 546.062 MiB, 0.19% compilation time)
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
1.248008 seconds (2.94 M allocations: 164.003 MiB) # repeated LGTM. |
Re-thinking about this, another possible implementation (not reinventing the wheel, what we do here is memoization) could be: --- a/Plots.jl/src/utils.jl 2021-08-26 20:05:17.172180754 +0200
+++ b/Plots.jl/src/utils.jl 2021-08-28 21:26:22.900145140 +0200
@@ -1,4 +1,5 @@
# ---------------------------------------------------------------
+using Memoize
treats_y_as_x(seriestype) =
seriestype in (:vline, :vspan, :histogram, :barhist, :stephist, :scatterhist)
@@ -1214,3 +1215,6 @@
end
return X, Y, Z
end
+
+@memoize get_axis_attr(letter::Symbol, keyword::String) = get_axis_attr(letter, Symbol(keyword))
+@memoize get_axis_attr(letter::Symbol, keyword::Symbol) = Symbol(letter, keyword) Running #3775 (comment) with this patch and GC disabled: $ julia
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
5.890243 seconds (10.00 M allocations: 562.515 MiB, 0.19% compilation time)
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
1.126509 seconds (3.42 M allocations: 178.810 MiB) # repeated Compared to #3775 (comment), With original PR (and const) $ julia
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
5.790343 seconds (9.04 M allocations: 532.402 MiB, 0.20% compilation time)
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
1.088492 seconds (2.49 M allocations: 150.345 MiB) # repeated Fastest, and winner on allocs. |
Oh, that change to a const may have made TTFP worse, but did further improve repeated plotting.
|
Memoize.jl version with my own example:
|
On my machine timings improve with const. Maybe a gc effect ? |
Are you testing this on Julia master, or 1.6.2? |
1.6.2, but not the downloaded binaries, local build. julia> versioninfo()
Julia Version 1.6.2
Commit 1b93d53fc4b* (2021-07-14 15:36 UTC)
Platform Info:
OS: Linux (x86_64-unknown-linux-gnu)
CPU: Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-11.0.1 (ORCJIT, sandybridge) |
Alright, I've reproduced your numbers. Are there enough other things that need to be cached to justify the extra overhead? Profiling this example, I don't see many other opportunities. Would it be better to stay with this and just leave a comment to that effect? Wouldn't be a hard change to make if it became necessary to memoize more generally. |
I'm OK with merging this as is. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it we can just generate the whole cache ahead of time to simplify access.
Co-authored-by: Simon Christ <SimonChrist@gmx.de>
I found a good place to populate the dictionary up front. There's a loop at the top level of
This is with PyPlot:
|
Thanks for reworking, I think this looks way neater. For reference, numbers are now: julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
5.778806 seconds (9.06 M allocations: 533.092 MiB, 0.20% compilation time)
julia> GC.enable(false); @time main(); GC.enable(true); GC.gc()
1.092024 seconds (2.49 M allocations: 150.345 MiB) |
These axis arguments can be generated lots of times for large plots. We can save time by caching them in a dictionary. For my large map test plot:
The cost of this is, I think, just 3-4 kb. Is there a function to read out the size in memory of a dictionary?
sizeof
for the dictionary itself just returns 64.Resolves #3763