Skip to content

Instantly share code, notes, and snippets.

@timholy
Last active February 3, 2024 10:46
Show Gist options
  • Save timholy/f29b5a9f9f33d941f561316683ef377f to your computer and use it in GitHub Desktop.
Save timholy/f29b5a9f9f33d941f561316683ef377f to your computer and use it in GitHub Desktop.
Debugging Documenter's search
var documenterSearchIndex = {"docs":
[{"location":"invalidations.html#Why-does-Julia-invalidate-code?","page":"Invalidations","title":"Why does Julia invalidate code?","text":"","category":"section"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Julia may be unique among computer languages in supporting all four of the following features:","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"interactive development\n\"method overloading\" by packages that don't own the function\naggressive compilation\nconsistent compilation: same result no matter how you got there","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"The combination of these features requires that you sometimes \"throw away\" code that you have previously compiled.","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"To illustrate: suppose you have a function numchildren with one method,","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"numchildren(::Any) = 1","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"and then write","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"total_children(list) = sum(numchildren.(list))","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Now let list be a Vector{Any}. You can compile a fast total_children(::Vector{Any}) (aggressive compilation) by leveraging the fact that you know there's only one possible method of numchildren, and you know that it returns 1 for every input. Thus, total_children(list) gives you just length(list), which would indeed be a very highly-optimized implementation!","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"But now suppose you add a second method (interactive development + method overloading)","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"numchildren(::BinaryNode) = 2","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"where BinaryNode is a new type you've defined (so it's not type-piracy). If you want to get the right answer (consistent compilation) from an arbitrary list::Vector{Any}, there are only two options:","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Option A: plan for this eventuality from the beginning, by making every numchildren(::Any) be called by runtime dispatch. But when there is only one method of numchildren, forcing runtime dispatch makes the code vastly slower. Thus, this option at least partly violates aggressive compilation.","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Option B: throw away the code for total_children that you created when there was only one method of numchildren, and recompile it in this new world where there are two.","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Julia does a mix of these: it does B up to 3 methods, and then A thereafter. (Recent versions of Julia have experimental support for customizing this behavior with Base.Experimental.@max_methods.)","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"This example was framed as an experiment at the REPL, but it is also relevant if you load two packages: PkgX might define numchildren and total_children, and PkgY might load PkgX and define a second method of PkgX.numchildren. Any precompilation that occurs in PkgX doesn't know what's going to happen in PkgY. Therefore, unless you want to defer all compilation, including for Julia itself, until the entire session is loaded and then closed to further extension (similar to how compilers for C, Rust, etc. work), you have to make the same choice between options A and B.","category":"page"},{"location":"invalidations.html","page":"Invalidations","title":"Invalidations","text":"Given that invalidation is necessary if Julia code is to be both fast and deliver the answers you expect, invalidation is a good thing! But sometimes Julia \"defensively\" throws out code that might be correct but can't be proved to be correct by Julia's type-inference machinery; such cases of \"spurious invalidation\" serve to (uselessly) increase latency and worsen the Julia experience. Except in cases of piracy, invalidation is a risk only for poorly-inferred code. With our example of numchildren and total_children above, the invalidations were necessary because list was a Vector{Any}, meaning that the elements might be of Any type and therefore Julia can't predict in advance which method(s) of numchildren would be applicable. Were one to create list as, say, list = Union{BinaryNode,TrinaryNode}[] (where TrinaryNode is some other kind of object with children), Julia would know much more about the types of the objects to which it applies numchildren: defining yet another new method like numchildren(::ArbitraryNode) would not trigger invalidations of code that was compiled for a list::Vector{Union{BinaryNode,TrinaryNode}}.","category":"page"},{"location":"explanations.html#How-PrecompileTools-works","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"","category":"section"},{"location":"explanations.html","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"Julia itself has a function precompile, to which you can pass specific signatures to force precompilation. For example, precompile(foo, (ArgType1, ArgType2)) will precompile foo(::ArgType1, ::ArgType2) and all of its inferrable callees. Alternatively, you can just execute some code at \"top level\" within the module, and during precompilation any method or signature \"owned\" by your package will also be precompiled. Thus, base Julia itself has substantial facilities for precompiling code.","category":"page"},{"location":"explanations.html#The-workload-macros","page":"How PrecompileTools works","title":"The workload macros","text":"","category":"section"},{"location":"explanations.html","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"@compile_workload adds one key feature: the non-inferrable callees (i.e., those called via runtime dispatch) that get made inside the @compile_workload block will also be cached, regardless of module ownership. In essence, it's like you're adding an explicit precompile(noninferrable_callee, (OtherArgType1, ...)) for every runtime-dispatched call made inside @compile_workload.","category":"page"},{"location":"explanations.html","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"These workload macros add other features as well:","category":"page"},{"location":"explanations.html","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"Statements that occur inside a @compile_workload block are executed only if the package is being actively precompiled; it does not run when the package is loaded, nor if you're running Julia with --compiled-modules=no.\nCompared to just running some workload at top-level, @compile_workload ensures that your code will be compiled (it disables the interpreter inside the block)\nPrecompileTools also defines @setup_workload, which you can use to create data for use inside a @compile_workload block. Like @compile_workload, this code only runs when you are precompiling the package, but it does not necessarily result in the @setup_workload code being stored in the package precompile file.","category":"page"},{"location":"explanations.html#@recompile_invalidations","page":"How PrecompileTools works","title":"@recompile_invalidations","text":"","category":"section"},{"location":"explanations.html","page":"How PrecompileTools works","title":"How PrecompileTools works","text":"@recompile_invalidations activates logging of invalidations before executing code in the block. It then parses the log to extract the \"leaves\" of the trees of invalidations, which generally represent the top-level calls (typically made by runtime dispatch). It then triggers their recompilation. Note that the recompiled code may return different results than the original (this possibility is why the code had to be invalidated in the first place).","category":"page"},{"location":"reference.html","page":"Reference","title":"Reference","text":"CurrentModule = PrecompileTools","category":"page"},{"location":"reference.html#Reference-(API)","page":"Reference","title":"Reference (API)","text":"","category":"section"},{"location":"reference.html","page":"Reference","title":"Reference","text":"API documentation for PrecompileTools.","category":"page"},{"location":"reference.html","page":"Reference","title":"Reference","text":"","category":"page"},{"location":"reference.html","page":"Reference","title":"Reference","text":"Modules = [PrecompileTools]","category":"page"},{"location":"reference.html#PrecompileTools.check_edges-Tuple{Any}","page":"Reference","title":"PrecompileTools.check_edges","text":"check_edges(node)\n\nRecursively ensure that all callees of node are precompiled. This is (rarely) necessary because sometimes there is no backedge from callee to caller (xref https://github.com/JuliaLang/julia/issues/49617), and staticdata.c relies on the backedge to trace back to a MethodInstance that is tagged mi.precompiled.\n\n\n\n\n\n","category":"method"},{"location":"reference.html#PrecompileTools.@compile_workload-Tuple{Expr}","page":"Reference","title":"PrecompileTools.@compile_workload","text":"@compile_workload f(args...)\n\nprecompile (and save in the compileworkload file) any method-calls that occur inside the expression. All calls (direct or indirect) inside a `@compileworkload` block will be cached.\n\n@compile_workload has three key features:\n\ncode inside runs only when the package is being precompiled (i.e., a *.ji precompile compile_workload file is being written)\nthe interpreter is disabled, ensuring your calls will be compiled\nboth direct and indirect callees will be precompiled, even for methods defined in other packages and even for runtime-dispatched callees (requires Julia 1.8 and above).\n\nnote: Note\nFor comprehensive precompilation, ensure the first usage of a given method/argument-type combination occurs inside @compile_workload.In detail: runtime-dispatched callees are captured only when type-inference is executed, and they are inferred only on first usage. Inferrable calls that trace back to a method defined in your package, and their inferrable callees, will be precompiled regardless of \"ownership\" of the callees (Julia 1.8 and higher).Consequently, this recommendation matters only for:- direct calls to methods defined in Base or other packages OR\n- indirect runtime-dispatched calls to such methods.\n\n\n\n\n\n","category":"macro"},{"location":"reference.html#PrecompileTools.@recompile_invalidations-Tuple{Any}","page":"Reference","title":"PrecompileTools.@recompile_invalidations","text":"@recompile_invalidations begin\n using PkgA\n ⋮\nend\n\nRecompile any invalidations that occur within the given expression. This is generally intended to be used by users in creating \"Startup\" packages to ensure that the code compiled by package authors is not invalidated.\n\n\n\n\n\n","category":"macro"},{"location":"reference.html#PrecompileTools.@setup_workload-Tuple{Expr}","page":"Reference","title":"PrecompileTools.@setup_workload","text":"@setup_workload begin\n vars = ...\n ⋮\nend\n\nRun the code block only during package precompilation. @setup_workload is often used in combination with @compile_workload, for example:\n\n@setup_workload begin\n vars = ...\n @compile_workload begin\n y = f(vars...)\n g(y)\n ⋮\n end\nend\n\n@setup_workload does not force compilation (though it may happen anyway) nor intentionally capture runtime dispatches (though they will be precompiled anyway if the runtime-callee is for a method belonging to your package).\n\n\n\n\n\n","category":"macro"},{"location":"index.html#PrecompileTools","page":"Home","title":"PrecompileTools","text":"","category":"section"},{"location":"index.html#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"PrecompileTools is designed to help reduce delay on first usage of Julia code. It can force precompilation of specific workloads; particularly with Julia 1.9 and higher, the precompiled code is automatically saved to disk, so that it doesn't need to be compiled freshly in each Julia session. You can use PrecompileTools as a package developer, to reduce the latency experienced by users of your package for \"typical\" workloads; you can also use PrecompileTools as a user, creating custom \"Startup\" package(s) that precompile workloads important for your work.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"The main tool in PrecompileTools is a macro, @compile_workload, which precompiles all the code needed to execute the workload. It also includes a second macro, @setup_workload, which can be used to \"mark\" a block of code as being relevant only for precompilation but which does not itself force compilation of @setup_workload code. (@setup_workload is typically used to generate test data using functions that you don't need to precompile in your package.) Finally, PrecompileTools includes @recompile_invalidations to mitigate the undesirable consequences of invalidations. These different tools are demonstrated below.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"note: Note\nThe latency reductions from PrecompileTools are maximally effective for Julia versions 1.9 and higher, and intermediate for Julia 1.8. Julia versions 1.7 and earlier may see some limited benefit as well, but have also occasionally been found to suffer from precompilation-induced runtime performance regressions. If you wish, you can disable precompilation on older Julia versions by wrapping precompilation statements (see below) with if Base.VERSION >= v\"1.8\" ... end. On older Julia versions, you may wish to test packages for performance regressions when introducing precompilation directives.","category":"page"},{"location":"index.html#Tutorial:-forcing-precompilation-with-workloads","page":"Home","title":"Tutorial: forcing precompilation with workloads","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"No matter whether you're a package developer or a user looking to make your own workloads start faster, the basic workflow of PrecompileTools is the same. Here's an illustration of how you might use @compile_workload and @setup_workload:","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"module MyPackage\n\nusing PrecompileTools: @setup_workload, @compile_workload # this is a small dependency\n\nstruct MyType\n x::Int\nend\nstruct OtherType\n str::String\nend\n\n@setup_workload begin\n # Putting some things in `@setup_workload` instead of `@compile_workload` can reduce the size of the\n # precompile file and potentially make loading faster.\n list = [OtherType(\"hello\"), OtherType(\"world!\")]\n @compile_workload begin\n # all calls in this block will be precompiled, regardless of whether\n # they belong to your package or not (on Julia 1.8 and higher)\n d = Dict(MyType(1) => list)\n x = get(d, MyType(2), nothing)\n last(d[MyType(1)])\n end\nend\n\nend","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"When you build MyPackage, it will precompile the following, including all their callees:","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Pair(::MyPackage.MyType, ::Vector{MyPackage.OtherType})\nDict(::Pair{MyPackage.MyType, Vector{MyPackage.OtherType}})\nget(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType, ::Nothing)\ngetindex(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType)\nlast(::Vector{MyPackage.OtherType})","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"In this case, the \"top level\" calls were fully inferrable, so there are no entries on this list that were called by runtime dispatch. Thus, here you could have gotten the same result with manual precompile directives. The key advantage of @compile_workload is that it works even if the functions you're calling have runtime dispatch.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Once you set up a block using PrecompileTools, try your package and see if it reduces the time to first execution, using the same workload you put inside the @compile_workload block.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"If you're happy with the results, you're done! If you want deeper verification of whether it worked as expected, or if you suspect problems, the SnoopCompile package provides diagnostic tools. Potential sources of trouble include invalidation (diagnosed with SnoopCompileCore.@snoopr and related tools) and omission of intended calls from inside the @compile_workload block (diagnosed with SnoopCompileCore.@snoopi_deep and related tools).","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"note: Note\n@compile_workload works by monitoring type-inference. If the code was already inferred prior to @compile_workload (e.g., from prior usage), you might omit any external methods that were called via runtime dispatch.You can use multiple @compile_workload blocks if you need to interleave @setup_workload code with code that you want precompiled. You can use @snoopi_deep to check for any (re)inference when you use the code in your package. To fix any specific problems, you can combine @compile_workload with manual precompile directives.","category":"page"},{"location":"index.html#Tutorial:-local-\"Startup\"-packages","page":"Home","title":"Tutorial: local \"Startup\" packages","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"Users who want to precompile workloads that have not been precompiled by the packages they use can follow the recipe above, creating custom \"Startup\" packages for each project. Imagine that you have three different kinds of analyses you do: you could have a folder","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"MyData/\n Project1/\n Project2/\n Project3/","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"From each one of those Project folders you could do the following:","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"(@v1.9) pkg> activate .\n Activating new project at `/tmp/Project1`\n\n(Project1) pkg> generate Startup\n Generating project Startup:\n Startup/Project.toml\n Startup/src/Startup.jl\n\n(Project1) pkg> dev ./Startup\n Resolving package versions...\n Updating `/tmp/Project1/Project.toml`\n [e9c42744] + Startup v0.1.0 `Startup`\n Updating `/tmp/Project1/Manifest.toml`\n [e9c42744] + Startup v0.1.0 `Startup`\n\n(Project1) pkg> activate Startup/\n Activating project at `/tmp/Project1/Startup`\n\n(Startup) pkg> add PrecompileTools LotsOfPackages...","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"In the last step, you add PrecompileTools and all the package you'll need for your work on Project1 as dependencies of Startup. Then edit the Startup/src/Startup.jl file to look similar to the tutorial previous section, e.g.,","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"module Startup\n\nusing LotsOfPackages...\nusing PrecompileTools\n\n@compile_workload begin\n # inside here, put a \"toy example\" of everything you want to be fast\nend\n\nend","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Then when you're ready to start work, from the Project1 environment just say using Startup. All the packages will be loaded, together with their precompiled code.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"tip: Tip\nIf desired, the Reexport package can be used to ensure these packages are also exported by Startup.","category":"page"},{"location":"index.html#Tutorial:-\"healing\"-invalidations","page":"Home","title":"Tutorial: \"healing\" invalidations","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"Julia sometimes invalidates previously compiled code (see Why does Julia invalidate code?). PrecompileTools provides a mechanism to recompile the invalidated code so that you get the full benefits of precompilation. This capability can be used in \"Startup\" packages (like the one described above), as well as by package developers.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"tip: Tip\nExcepting piracy (which is heavily discouraged), type-stable (i.e., well-inferred) code cannot be invalidated. If invalidations are a problem, an even better option than \"healing\" the invalidations is improving the inferrability of the \"victim\": not only will you prevent invalidations, you may get faster performance and slimmer binaries. Packages that can help identify inference problems and invalidations include SnoopCompile, JET, and Cthulhu.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"The basic usage is simple: wrap expressions that might invalidate with @recompile_invalidations. Invalidation can be triggered by defining new methods of external functions, including during package loading. Using the \"Startup\" package above, you might wrap the using statements:","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"module Startup\n\nusing PrecompileTools\n@recompile_invalidations begin\n using LotsOfPackages...\nend\n\n# Maybe a @compile_workload here?\n\nend","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Note that recompiling invalidations can be useful even if you don't add any additional workloads.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Alternatively, if you're a package developer worried about \"collateral damage\" you may cause by extending functions owned by Base or other package (i.e., those that require import or module-scoping when defining the method), you can wrap those method definitions:","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"module MyContainers\n\nusing AnotherPackage\nusing PrecompileTools\n\nstruct Container\n list::Vector{Any}\nend\n\n# This is a function created by this package, so it doesn't need to be wrapped\nmake_container() = Container([])\n\n@recompile_invalidations begin\n # Only those methods extending Base or other packages need to go here\n Base.push!(obj::Container, x) = ...\n function AnotherPackage.foo(obj::Container)\n ⋮\n end\nend\n\nend","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"You can have more than one @recompile_invalidations block in a module. For example, you might use one to wrap your usings, and a second to wrap your method extensions.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"warning: Warning\nPackage developers should be aware of the tradeoffs in using @recompile_invalidations to wrap method extensions:the benefit is that you might deliver a better out-of-the-box experience for your users, without them needing to customize anything\nthe downside is that it will increase the precompilation time for your package. Worse, what can be invalidated once can sometimes be invalidated again by a later package, and if that happens the time spent recompiling is wasted.Using @recompile_invalidations in a \"Startup\" package is, in a sense, safer because it waits for all the code to be loaded before recompiling anything. On the other hand, this requires users to implement their own customizations.Package developers are encouraged to try to fix \"known\" invalidations rather than relying reflexively on @recompile_invalidations.","category":"page"},{"location":"index.html#When-you-can't-run-a-workload","page":"Home","title":"When you can't run a workload","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"There are cases where you might want to precompile code but cannot safely execute that code: for example, you may need to connect to a database, or perhaps this is a plotting package but you may be currently on a headless server lacking a display, etc. In that case, your best option is to fall back on Julia's own precompile function. However, as explained in How PrecompileTools works, there are some differences between precompile and @compile_workload; most likely, you may need multiple precompile directives. Analysis with SnoopCompile may be required to obtain the results you want; in particular, combining @snoopi_deep and parcel will allow you to generate a set of precompile directives that can be included in your module definition.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Be aware that precompile directives are more specific to the Julia version, CPU (integer width), and OS than running a workload.","category":"page"},{"location":"index.html#Troubleshooting","page":"Home","title":"Troubleshooting","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"Ensure your workload \"works\" (runs without error) when copy/pasted into the REPL. If it produces an error only when placed inside @precompile_workload, check whether your workload runs when wrapped in a","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"let\n # workload goes here\nend","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"block.","category":"page"},{"location":"index.html#Package-developers:-reducing-the-cost-of-precompilation-during-development","page":"Home","title":"Package developers: reducing the cost of precompilation during development","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"If you're frequently modifying one or more packages, you may not want to spend the extra time precompiling the full set of workloads that you've chosen to make fast for your \"shipped\" releases. One can locally reduce the cost of precompilation for selected packages using the Preferences.jl-based mechanism and the \"precompile_workload\" key: from within your development environment, use","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"using MyPackage, Preferences\nset_preferences!(MyPackage, \"precompile_workload\" => false; force=true)","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"This will write the following to LocalPreferences.toml alongside your active environment Project.toml","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"[MyPackage]\nprecompile_workload = false","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"After restarting julia, the @compile_workload and @setup_workload workloads will be disabled (locally) for MyPackage. You can also specify additional packages (e.g., dependencies of MyPackage) if you're co-developing a suite of packages. Simply run set_preferences! for the additional packages, or edit LocalPreferences.toml directly.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"note: Note\nChanging precompile_workload will result in a one-time recompilation of all packages that depend on the package(s) from the current environment. Package developers may wish to set this preference locally within the \"main\" package's environment; precompilation will be skipped while you're actively developing the project, but not if you use the package from an external environment. This will also keep the precompile_workload setting independent and avoid needless recompilation of large environments.","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"Finally, it is possible to fully disable PrecompileTools.jl for all packages with ","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"using PrecompileTools, Preferences\nset_preferences!(PrecompileTools, \"precompile_workloads\" => false; force=true)","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"This can be helpful to reduce the system image size generated when using PackageCompiler.jl by only compiling calls made in a precompilation script. ","category":"page"},{"location":"index.html#Seeing-what-got-precompiled","page":"Home","title":"Seeing what got precompiled","text":"","category":"section"},{"location":"index.html","page":"Home","title":"Home","text":"If you want to see the list of calls that will be precompiled, navigate to the MyPackage folder and use","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"julia> using PrecompileTools\n\njulia> PrecompileTools.verbose[] = true # runs the block even if you're not precompiling, and print precompiled calls\n\njulia> include(\"src/MyPackage.jl\");","category":"page"},{"location":"index.html","page":"Home","title":"Home","text":"This will only show the direct- or runtime-dispatched method instances that got precompiled (omitting their inferrable callees). For a more comprehensive list of all items stored in the compile_workload file, see PkgCacheInspector.","category":"page"}]
}
{
"docs": [
{
"location": "invalidations.html#Why-does-Julia-invalidate-code?",
"page": "Invalidations",
"title": "Why does Julia invalidate code?",
"text": "",
"category": "section"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Julia may be unique among computer languages in supporting all four of the following features:",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "interactive development\n\"method overloading\" by packages that don't own the function\naggressive compilation\nconsistent compilation: same result no matter how you got there",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "The combination of these features requires that you sometimes \"throw away\" code that you have previously compiled.",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "To illustrate: suppose you have a function numchildren with one method,",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "numchildren(::Any) = 1",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "and then write",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "total_children(list) = sum(numchildren.(list))",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Now let list be a Vector{Any}. You can compile a fast total_children(::Vector{Any}) (aggressive compilation) by leveraging the fact that you know there's only one possible method of numchildren, and you know that it returns 1 for every input. Thus, total_children(list) gives you just length(list), which would indeed be a very highly-optimized implementation!",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "But now suppose you add a second method (interactive development + method overloading)",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "numchildren(::BinaryNode) = 2",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "where BinaryNode is a new type you've defined (so it's not type-piracy). If you want to get the right answer (consistent compilation) from an arbitrary list::Vector{Any}, there are only two options:",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Option A: plan for this eventuality from the beginning, by making every numchildren(::Any) be called by runtime dispatch. But when there is only one method of numchildren, forcing runtime dispatch makes the code vastly slower. Thus, this option at least partly violates aggressive compilation.",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Option B: throw away the code for total_children that you created when there was only one method of numchildren, and recompile it in this new world where there are two.",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Julia does a mix of these: it does B up to 3 methods, and then A thereafter. (Recent versions of Julia have experimental support for customizing this behavior with Base.Experimental.@max_methods.)",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "This example was framed as an experiment at the REPL, but it is also relevant if you load two packages: PkgX might define numchildren and total_children, and PkgY might load PkgX and define a second method of PkgX.numchildren. Any precompilation that occurs in PkgX doesn't know what's going to happen in PkgY. Therefore, unless you want to defer all compilation, including for Julia itself, until the entire session is loaded and then closed to further extension (similar to how compilers for C, Rust, etc. work), you have to make the same choice between options A and B.",
"category": "page"
},
{
"location": "invalidations.html",
"page": "Invalidations",
"title": "Invalidations",
"text": "Given that invalidation is necessary if Julia code is to be both fast and deliver the answers you expect, invalidation is a good thing! But sometimes Julia \"defensively\" throws out code that might be correct but can't be proved to be correct by Julia's type-inference machinery; such cases of \"spurious invalidation\" serve to (uselessly) increase latency and worsen the Julia experience. Except in cases of piracy, invalidation is a risk only for poorly-inferred code. With our example of numchildren and total_children above, the invalidations were necessary because list was a Vector{Any}, meaning that the elements might be of Any type and therefore Julia can't predict in advance which method(s) of numchildren would be applicable. Were one to create list as, say, list = Union{BinaryNode,TrinaryNode}[] (where TrinaryNode is some other kind of object with children), Julia would know much more about the types of the objects to which it applies numchildren: defining yet another new method like numchildren(::ArbitraryNode) would not trigger invalidations of code that was compiled for a list::Vector{Union{BinaryNode,TrinaryNode}}.",
"category": "page"
},
{
"location": "explanations.html#How-PrecompileTools-works",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "",
"category": "section"
},
{
"location": "explanations.html",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "Julia itself has a function precompile, to which you can pass specific signatures to force precompilation. For example, precompile(foo, (ArgType1, ArgType2)) will precompile foo(::ArgType1, ::ArgType2) and all of its inferrable callees. Alternatively, you can just execute some code at \"top level\" within the module, and during precompilation any method or signature \"owned\" by your package will also be precompiled. Thus, base Julia itself has substantial facilities for precompiling code.",
"category": "page"
},
{
"location": "explanations.html#The-workload-macros",
"page": "How PrecompileTools works",
"title": "The workload macros",
"text": "",
"category": "section"
},
{
"location": "explanations.html",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "@compile_workload adds one key feature: the non-inferrable callees (i.e., those called via runtime dispatch) that get made inside the @compile_workload block will also be cached, regardless of module ownership. In essence, it's like you're adding an explicit precompile(noninferrable_callee, (OtherArgType1, ...)) for every runtime-dispatched call made inside @compile_workload.",
"category": "page"
},
{
"location": "explanations.html",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "These workload macros add other features as well:",
"category": "page"
},
{
"location": "explanations.html",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "Statements that occur inside a @compile_workload block are executed only if the package is being actively precompiled; it does not run when the package is loaded, nor if you're running Julia with --compiled-modules=no.\nCompared to just running some workload at top-level, @compile_workload ensures that your code will be compiled (it disables the interpreter inside the block)\nPrecompileTools also defines @setup_workload, which you can use to create data for use inside a @compile_workload block. Like @compile_workload, this code only runs when you are precompiling the package, but it does not necessarily result in the @setup_workload code being stored in the package precompile file.",
"category": "page"
},
{
"location": "explanations.html#@recompile_invalidations",
"page": "How PrecompileTools works",
"title": "@recompile_invalidations",
"text": "",
"category": "section"
},
{
"location": "explanations.html",
"page": "How PrecompileTools works",
"title": "How PrecompileTools works",
"text": "@recompile_invalidations activates logging of invalidations before executing code in the block. It then parses the log to extract the \"leaves\" of the trees of invalidations, which generally represent the top-level calls (typically made by runtime dispatch). It then triggers their recompilation. Note that the recompiled code may return different results than the original (this possibility is why the code had to be invalidated in the first place).",
"category": "page"
},
{
"location": "reference.html",
"page": "Reference",
"title": "Reference",
"text": "CurrentModule = PrecompileTools",
"category": "page"
},
{
"location": "reference.html#Reference-(API)",
"page": "Reference",
"title": "Reference (API)",
"text": "",
"category": "section"
},
{
"location": "reference.html",
"page": "Reference",
"title": "Reference",
"text": "API documentation for PrecompileTools.",
"category": "page"
},
{
"location": "reference.html",
"page": "Reference",
"title": "Reference",
"text": "",
"category": "page"
},
{
"location": "reference.html",
"page": "Reference",
"title": "Reference",
"text": "Modules = [PrecompileTools]",
"category": "page"
},
{
"location": "reference.html#PrecompileTools.check_edges-Tuple{Any}",
"page": "Reference",
"title": "PrecompileTools.check_edges",
"text": "check_edges(node)\n\nRecursively ensure that all callees of node are precompiled. This is (rarely) necessary because sometimes there is no backedge from callee to caller (xref https://github.com/JuliaLang/julia/issues/49617), and staticdata.c relies on the backedge to trace back to a MethodInstance that is tagged mi.precompiled.\n\n\n\n\n\n",
"category": "method"
},
{
"location": "reference.html#PrecompileTools.@compile_workload-Tuple{Expr}",
"page": "Reference",
"title": "PrecompileTools.@compile_workload",
"text": "@compile_workload f(args...)\n\nprecompile (and save in the compileworkload file) any method-calls that occur inside the expression. All calls (direct or indirect) inside a `@compileworkload` block will be cached.\n\n@compile_workload has three key features:\n\ncode inside runs only when the package is being precompiled (i.e., a *.ji precompile compile_workload file is being written)\nthe interpreter is disabled, ensuring your calls will be compiled\nboth direct and indirect callees will be precompiled, even for methods defined in other packages and even for runtime-dispatched callees (requires Julia 1.8 and above).\n\nnote: Note\nFor comprehensive precompilation, ensure the first usage of a given method/argument-type combination occurs inside @compile_workload.In detail: runtime-dispatched callees are captured only when type-inference is executed, and they are inferred only on first usage. Inferrable calls that trace back to a method defined in your package, and their inferrable callees, will be precompiled regardless of \"ownership\" of the callees (Julia 1.8 and higher).Consequently, this recommendation matters only for:- direct calls to methods defined in Base or other packages OR\n- indirect runtime-dispatched calls to such methods.\n\n\n\n\n\n",
"category": "macro"
},
{
"location": "reference.html#PrecompileTools.@recompile_invalidations-Tuple{Any}",
"page": "Reference",
"title": "PrecompileTools.@recompile_invalidations",
"text": "@recompile_invalidations begin\n using PkgA\n ⋮\nend\n\nRecompile any invalidations that occur within the given expression. This is generally intended to be used by users in creating \"Startup\" packages to ensure that the code compiled by package authors is not invalidated.\n\n\n\n\n\n",
"category": "macro"
},
{
"location": "reference.html#PrecompileTools.@setup_workload-Tuple{Expr}",
"page": "Reference",
"title": "PrecompileTools.@setup_workload",
"text": "@setup_workload begin\n vars = ...\n ⋮\nend\n\nRun the code block only during package precompilation. @setup_workload is often used in combination with @compile_workload, for example:\n\n@setup_workload begin\n vars = ...\n @compile_workload begin\n y = f(vars...)\n g(y)\n ⋮\n end\nend\n\n@setup_workload does not force compilation (though it may happen anyway) nor intentionally capture runtime dispatches (though they will be precompiled anyway if the runtime-callee is for a method belonging to your package).\n\n\n\n\n\n",
"category": "macro"
},
{
"location": "index.html#PrecompileTools",
"page": "Home",
"title": "PrecompileTools",
"text": "",
"category": "section"
},
{
"location": "index.html#Overview",
"page": "Home",
"title": "Overview",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "PrecompileTools is designed to help reduce delay on first usage of Julia code. It can force precompilation of specific workloads; particularly with Julia 1.9 and higher, the precompiled code is automatically saved to disk, so that it doesn't need to be compiled freshly in each Julia session. You can use PrecompileTools as a package developer, to reduce the latency experienced by users of your package for \"typical\" workloads; you can also use PrecompileTools as a user, creating custom \"Startup\" package(s) that precompile workloads important for your work.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "The main tool in PrecompileTools is a macro, @compile_workload, which precompiles all the code needed to execute the workload. It also includes a second macro, @setup_workload, which can be used to \"mark\" a block of code as being relevant only for precompilation but which does not itself force compilation of @setup_workload code. (@setup_workload is typically used to generate test data using functions that you don't need to precompile in your package.) Finally, PrecompileTools includes @recompile_invalidations to mitigate the undesirable consequences of invalidations. These different tools are demonstrated below.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "note: Note\nThe latency reductions from PrecompileTools are maximally effective for Julia versions 1.9 and higher, and intermediate for Julia 1.8. Julia versions 1.7 and earlier may see some limited benefit as well, but have also occasionally been found to suffer from precompilation-induced runtime performance regressions. If you wish, you can disable precompilation on older Julia versions by wrapping precompilation statements (see below) with if Base.VERSION >= v\"1.8\" ... end. On older Julia versions, you may wish to test packages for performance regressions when introducing precompilation directives.",
"category": "page"
},
{
"location": "index.html#Tutorial:-forcing-precompilation-with-workloads",
"page": "Home",
"title": "Tutorial: forcing precompilation with workloads",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "No matter whether you're a package developer or a user looking to make your own workloads start faster, the basic workflow of PrecompileTools is the same. Here's an illustration of how you might use @compile_workload and @setup_workload:",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "module MyPackage\n\nusing PrecompileTools: @setup_workload, @compile_workload # this is a small dependency\n\nstruct MyType\n x::Int\nend\nstruct OtherType\n str::String\nend\n\n@setup_workload begin\n # Putting some things in `@setup_workload` instead of `@compile_workload` can reduce the size of the\n # precompile file and potentially make loading faster.\n list = [OtherType(\"hello\"), OtherType(\"world!\")]\n @compile_workload begin\n # all calls in this block will be precompiled, regardless of whether\n # they belong to your package or not (on Julia 1.8 and higher)\n d = Dict(MyType(1) => list)\n x = get(d, MyType(2), nothing)\n last(d[MyType(1)])\n end\nend\n\nend",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "When you build MyPackage, it will precompile the following, including all their callees:",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Pair(::MyPackage.MyType, ::Vector{MyPackage.OtherType})\nDict(::Pair{MyPackage.MyType, Vector{MyPackage.OtherType}})\nget(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType, ::Nothing)\ngetindex(::Dict{MyPackage.MyType, Vector{MyPackage.OtherType}}, ::MyPackage.MyType)\nlast(::Vector{MyPackage.OtherType})",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "In this case, the \"top level\" calls were fully inferrable, so there are no entries on this list that were called by runtime dispatch. Thus, here you could have gotten the same result with manual precompile directives. The key advantage of @compile_workload is that it works even if the functions you're calling have runtime dispatch.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Once you set up a block using PrecompileTools, try your package and see if it reduces the time to first execution, using the same workload you put inside the @compile_workload block.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "If you're happy with the results, you're done! If you want deeper verification of whether it worked as expected, or if you suspect problems, the SnoopCompile package provides diagnostic tools. Potential sources of trouble include invalidation (diagnosed with SnoopCompileCore.@snoopr and related tools) and omission of intended calls from inside the @compile_workload block (diagnosed with SnoopCompileCore.@snoopi_deep and related tools).",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "note: Note\n@compile_workload works by monitoring type-inference. If the code was already inferred prior to @compile_workload (e.g., from prior usage), you might omit any external methods that were called via runtime dispatch.You can use multiple @compile_workload blocks if you need to interleave @setup_workload code with code that you want precompiled. You can use @snoopi_deep to check for any (re)inference when you use the code in your package. To fix any specific problems, you can combine @compile_workload with manual precompile directives.",
"category": "page"
},
{
"location": "index.html#Tutorial:-local-\"Startup\"-packages",
"page": "Home",
"title": "Tutorial: local \"Startup\" packages",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Users who want to precompile workloads that have not been precompiled by the packages they use can follow the recipe above, creating custom \"Startup\" packages for each project. Imagine that you have three different kinds of analyses you do: you could have a folder",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "MyData/\n Project1/\n Project2/\n Project3/",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "From each one of those Project folders you could do the following:",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "(@v1.9) pkg> activate .\n Activating new project at `/tmp/Project1`\n\n(Project1) pkg> generate Startup\n Generating project Startup:\n Startup/Project.toml\n Startup/src/Startup.jl\n\n(Project1) pkg> dev ./Startup\n Resolving package versions...\n Updating `/tmp/Project1/Project.toml`\n [e9c42744] + Startup v0.1.0 `Startup`\n Updating `/tmp/Project1/Manifest.toml`\n [e9c42744] + Startup v0.1.0 `Startup`\n\n(Project1) pkg> activate Startup/\n Activating project at `/tmp/Project1/Startup`\n\n(Startup) pkg> add PrecompileTools LotsOfPackages...",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "In the last step, you add PrecompileTools and all the package you'll need for your work on Project1 as dependencies of Startup. Then edit the Startup/src/Startup.jl file to look similar to the tutorial previous section, e.g.,",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "module Startup\n\nusing LotsOfPackages...\nusing PrecompileTools\n\n@compile_workload begin\n # inside here, put a \"toy example\" of everything you want to be fast\nend\n\nend",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Then when you're ready to start work, from the Project1 environment just say using Startup. All the packages will be loaded, together with their precompiled code.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "tip: Tip\nIf desired, the Reexport package can be used to ensure these packages are also exported by Startup.",
"category": "page"
},
{
"location": "index.html#Tutorial:-\"healing\"-invalidations",
"page": "Home",
"title": "Tutorial: \"healing\" invalidations",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Julia sometimes invalidates previously compiled code (see Why does Julia invalidate code?). PrecompileTools provides a mechanism to recompile the invalidated code so that you get the full benefits of precompilation. This capability can be used in \"Startup\" packages (like the one described above), as well as by package developers.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "tip: Tip\nExcepting piracy (which is heavily discouraged), type-stable (i.e., well-inferred) code cannot be invalidated. If invalidations are a problem, an even better option than \"healing\" the invalidations is improving the inferrability of the \"victim\": not only will you prevent invalidations, you may get faster performance and slimmer binaries. Packages that can help identify inference problems and invalidations include SnoopCompile, JET, and Cthulhu.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "The basic usage is simple: wrap expressions that might invalidate with @recompile_invalidations. Invalidation can be triggered by defining new methods of external functions, including during package loading. Using the \"Startup\" package above, you might wrap the using statements:",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "module Startup\n\nusing PrecompileTools\n@recompile_invalidations begin\n using LotsOfPackages...\nend\n\n# Maybe a @compile_workload here?\n\nend",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Note that recompiling invalidations can be useful even if you don't add any additional workloads.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Alternatively, if you're a package developer worried about \"collateral damage\" you may cause by extending functions owned by Base or other package (i.e., those that require import or module-scoping when defining the method), you can wrap those method definitions:",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "module MyContainers\n\nusing AnotherPackage\nusing PrecompileTools\n\nstruct Container\n list::Vector{Any}\nend\n\n# This is a function created by this package, so it doesn't need to be wrapped\nmake_container() = Container([])\n\n@recompile_invalidations begin\n # Only those methods extending Base or other packages need to go here\n Base.push!(obj::Container, x) = ...\n function AnotherPackage.foo(obj::Container)\n ⋮\n end\nend\n\nend",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "You can have more than one @recompile_invalidations block in a module. For example, you might use one to wrap your usings, and a second to wrap your method extensions.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "warning: Warning\nPackage developers should be aware of the tradeoffs in using @recompile_invalidations to wrap method extensions:the benefit is that you might deliver a better out-of-the-box experience for your users, without them needing to customize anything\nthe downside is that it will increase the precompilation time for your package. Worse, what can be invalidated once can sometimes be invalidated again by a later package, and if that happens the time spent recompiling is wasted.Using @recompile_invalidations in a \"Startup\" package is, in a sense, safer because it waits for all the code to be loaded before recompiling anything. On the other hand, this requires users to implement their own customizations.Package developers are encouraged to try to fix \"known\" invalidations rather than relying reflexively on @recompile_invalidations.",
"category": "page"
},
{
"location": "index.html#When-you-can't-run-a-workload",
"page": "Home",
"title": "When you can't run a workload",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "There are cases where you might want to precompile code but cannot safely execute that code: for example, you may need to connect to a database, or perhaps this is a plotting package but you may be currently on a headless server lacking a display, etc. In that case, your best option is to fall back on Julia's own precompile function. However, as explained in How PrecompileTools works, there are some differences between precompile and @compile_workload; most likely, you may need multiple precompile directives. Analysis with SnoopCompile may be required to obtain the results you want; in particular, combining @snoopi_deep and parcel will allow you to generate a set of precompile directives that can be included in your module definition.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Be aware that precompile directives are more specific to the Julia version, CPU (integer width), and OS than running a workload.",
"category": "page"
},
{
"location": "index.html#Troubleshooting",
"page": "Home",
"title": "Troubleshooting",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Ensure your workload \"works\" (runs without error) when copy/pasted into the REPL. If it produces an error only when placed inside @precompile_workload, check whether your workload runs when wrapped in a",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "let\n # workload goes here\nend",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "block.",
"category": "page"
},
{
"location": "index.html#Package-developers:-reducing-the-cost-of-precompilation-during-development",
"page": "Home",
"title": "Package developers: reducing the cost of precompilation during development",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "If you're frequently modifying one or more packages, you may not want to spend the extra time precompiling the full set of workloads that you've chosen to make fast for your \"shipped\" releases. One can locally reduce the cost of precompilation for selected packages using the Preferences.jl-based mechanism and the \"precompile_workload\" key: from within your development environment, use",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "using MyPackage, Preferences\nset_preferences!(MyPackage, \"precompile_workload\" => false; force=true)",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "This will write the following to LocalPreferences.toml alongside your active environment Project.toml",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "[MyPackage]\nprecompile_workload = false",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "After restarting julia, the @compile_workload and @setup_workload workloads will be disabled (locally) for MyPackage. You can also specify additional packages (e.g., dependencies of MyPackage) if you're co-developing a suite of packages. Simply run set_preferences! for the additional packages, or edit LocalPreferences.toml directly.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "note: Note\nChanging precompile_workload will result in a one-time recompilation of all packages that depend on the package(s) from the current environment. Package developers may wish to set this preference locally within the \"main\" package's environment; precompilation will be skipped while you're actively developing the project, but not if you use the package from an external environment. This will also keep the precompile_workload setting independent and avoid needless recompilation of large environments.",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "Finally, it is possible to fully disable PrecompileTools.jl for all packages with ",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "using PrecompileTools, Preferences\nset_preferences!(PrecompileTools, \"precompile_workloads\" => false; force=true)",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "This can be helpful to reduce the system image size generated when using PackageCompiler.jl by only compiling calls made in a precompilation script. ",
"category": "page"
},
{
"location": "index.html#Seeing-what-got-precompiled",
"page": "Home",
"title": "Seeing what got precompiled",
"text": "",
"category": "section"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "If you want to see the list of calls that will be precompiled, navigate to the MyPackage folder and use",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "julia> using PrecompileTools\n\njulia> PrecompileTools.verbose[] = true # runs the block even if you're not precompiling, and print precompiled calls\n\njulia> include(\"src/MyPackage.jl\");",
"category": "page"
},
{
"location": "index.html",
"page": "Home",
"title": "Home",
"text": "This will only show the direct- or runtime-dispatched method instances that got precompiled (omitting their inferrable callees). For a more comprehensive list of all items stored in the compile_workload file, see PkgCacheInspector.",
"category": "page"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment