Implementing recipes on a per package basis?

I recently started thinking about an alternative strategy to improving the end user experience in the plotting ecosystem. Is it correct that the recipe system nowadays is decoupled from Plots.jl? For instance, could an author of a plotting package (e.g. GR.jl) implement the recipe for his/her own package using RecipesPipeline.jl? I believe that the answer is yes based on the recent developments around MakieRecipes.jl, but would like to double check with you all. Is it true that currently the implementation of backends is delegated to the Plots.jl maintainers?

I think we all understand the value of the recipe system initiated by @tbreloff a long time ago, and I think we all agree that we reached a state of affairs in the ecosystem where a reduced set of maintainers are overwhelmed maintaining all the backends in a single repository. Could this maintenance burden be split among the package authors? Could RecipesPipeline.jl be some sort of “PlotsBase.jl” with an agreed API for recipes that plot packages implement? What do you think of this proposal?

As an end user, this is a list of benefits that I can foresee with this approach:

  1. It would be much easier to contribute patches to specific backends.
  2. Authors of plotting packages would be able to review and merge patches much more quickly without worrying about breaking other backends.
  3. I guess time-to-first-plot would reduce? Given that users would only load a specific backend, we could avoid loading the entire system:
using Plots # loads everything
gr() # select GR

in favor of loading a specific backend code:

using GR # recipes for GR ready to be used

Appreciate your thoughts on this issue.

cc: @jheinen @daschw @mkborregaard @stevengj @sglyon @kristoffer.carlsson @sdanisch

I’m not sure how much my post is relevant for recipes specifically (it’s more a out the backends) but anyway.

A PlotsCore.jl together with backend packages that you explicitly install (like PlotsPyPlot.jl) that depend on PlotsCore.jl would at least allow getting rid of Requires which is usually pretty bad for compile times.

You could still have the functions for setting the backend in PlotsCore.jl, it would just need to check that you have loaded the backend package (using Base.loaded_modules. Yes, it means you have to add these backend packages but it might mean more code can get precompiled and you don’t have to load the code for all backends as Solnas you load Plots. Would at least be interesting to do some timing measurements. Maybe this has already been tried/discussed.

1 Like

GR has been hardcoded to load for about 2 years now, with other choices using a Requires mechanism so this shouldn’t effect the default usage. But that only made a dent in the compile times. The issues in Plots.jl are much different than this.

The current issues with the recipes system has more to do with inference issues and invalidations. There are some libraries that can intercept a lot of what other libraries are doing (like dispatches on AbstractArray{<:Complex} that make a lot of it not compile well. This coupled with the fact that the GR backend code is one gigantic function slab (this is the hot spot IIRC) means pretty much nothing there meaningfully precompiles, and a lot of what does precompile gets thrown away.

So the issue now is moreso that the Plots.jl front end has issues with compilation even without lazy backends.

1 Like

Not true. For example, the small amount of Requires.jl usage we use in PGFPlotsX.jl doubles our load time:

Where is the analysis that this is caused by invalidations?

Are you saying that the function for the GR backend code gets invalidated and has to be recompiled? When does it get invalidated?

This might very well be the case.

1 Like

That’s not Plots.jl. Plots.jl doesn’t use Requires.jl for the core GR plotting, so I’m not sure why that measurement from PGFPlotsX.jl is relevant. The using time in Plots.jl isn’t a major factor anyways in comparison to the timing on plot and display.

That was the whole load of stuff from Leon over the last year (before Snoopcompile was finding invalidations). Not sure how much of it is online. He iteratively went through to find what libraries were causing issues and concluded that is as best as we can get right now, with most of the remaining functions that cannot precompile due to Measures.jl, DataFrames.jl (something to do with CSV, I think Jeff or Jameson ended up patching something for that) and StaticArrays.jl. But building a system image with enough of the packages in there did get FTTP down to 2 seconds (without the full Plots in there).

It was a bit hard to document because we didn’t have very much tooling back then (October?), but a few months later the Snoopcompile invalidation hunting started and found roughly the same libraries were involved.

Because the GR backend is not lazy and first time to plot with GR still mostly exists (though it’s a lot smaller than before), “might very well” is not a strong enough statement.

I’m not completely sure, but I think what you (@juliohm ) describe is the purpose of the refactor that led to RecipesPipeline.jl. Plot package owners can implement the functions defined there, and a translation table for Plots’ attributes and then use both Plots recipes and Plots syntax to plot to their own package. Makie has chosen to so far do that in the glue package MakieRecipes, and there’s been talk of doing something similar for VegaLite.

It’s different from a Plots backend in that it produces a Makie (og Vega) plot object and allows mixing in Makie (or Vega) syntax. On the downside it doesn’t do all the axes/color/theming/layouting/animation etc for you that Plots does.

1 Like

Thank you @mkborregaard for clarifying the issue. So I understand that the effort around writing the glue code for recipes could be split among plot package authors nowadays, and be maintained separately.

Just to make sure that I understand the concepts: animation is the feature provided by the @anim and related macros; theming the feature that allows changing the overall aesthetics of the plot like the background color; layouting is the feature that allows composing subplots into grid-like formats. What about the axes and color features you mentioned, could you please clarify?

If my understanding is correct, maybe the animation part could be its own package that users could load when they need animations. Regarding layouting, we now have an amazing implementation in MakieLayouts.jl. Maybe it could also be made agnostic of Makie.jl somehow for reuse in other plot packages. More generally, what I am trying to say is that perhaps creating small packages for each of these features could help the ecosystem.

And a final question to see if I understood the complexity correctly. What are the changes necessary to make the statement using GR equivalent to the statement using Plots; gr() in a Julia session?

Loading Plots takes 13 seconds for me. Seems non-negligble.

It is relevant because in the issue I linked none of the Requires enabled packages are loaded but Requires still causes extra compile time (you need to compile all the Requires expressions).

As already explained, Requires causes extra load time anyway. Also, one of the main “features” of Plots is presumably the fact that you can use different backends and these use cases are also important?

1 Like

That increased since we last timed it. The thing that caused a major bump to the using time was the addition of the precompile file, but that bump was a lot smaller: . That result was similar on my computer, and I now see 30 seconds for using, so something went wrong. There must’ve been a regression of package load times in a recent Julia version, possibly package load times in the presence of precompile files. This is probably worth a Julia Base issue to track down.

I see, there’s 0.4 seconds just from using Requires, even if @requires isn’t used anywhere in the package? That’s good to know, but in the grand scheme of things here it’s a small problem. The other 12.6 seconds are the major issue, + the 10 seconds or so for @time display(plot(rand(10), rand(10)))

1 Like