Package warmup / SnoopCompile

Looking at SnoopCompile.jl I was wondering whether I could write a simple “hint” file that would pre-compile the standard paths in the program making loading the package a bit slower but hopefully the first call faster. However that’s not really what I observed and I’m not sure why. In fact I even tried to have the exact same call in the warmup and in the first call but with the same result.

What I tried:

module SomeModule
# ...
include("warmup.jl") # contains "_warmup()_ = some_function(1, 2, 3)"
@show @elapsed _warmup_()
end

I was a bit surprised to see that the first call to exactly some_function(1, 2, 3) is not much faster. Results as follow to give an idea:

  • warmup call ~ 2.2s (using SomeModule)
  • first_call ~1.8s (@elapsed some_function(1, 2, 3))
  • second_call ~0.1s (@elapsed some_function(1, 2, 3))

I would have expected the first call to be closer to the second call in terms of time given the “warmup” but I “only” seem to be gaining between 10 and 30%. Why is that the case?

If I remove the “warmup”, the results are

  • first call ~2.8s
  • second call ~0.1s

This is maybe similar to what was experienced in this thread about specifying a precompile sequence.

Thanks!

(fwiw, I’m on Julia 1.2, macOS.)

Edit, interestingly if I call the _warmup_ twice in the module, I get

  • first warmup 2.2s
  • second warmup 1.1s :eyes:
  • first call 2s :eyes:
  • second call 0.1s
2 Likes

You’ll need to use PackageCompiler!
It automatically snoops your package from the runtests.jl (note that it isn’t using SnoopCompile anymore, since the functionality basically moved into base julia).
The important step is, that it compiles a new system image that caches your ahead of time compiled code!
i recommend using the new, compile_incremental function!

edit i think i didnt read your question carefully enough, so it’s a bit besides the topic, but i think it’s still worth looking into packagecompiler!

2 Likes

Thanks, I think PackageCompiler is super promising but a bit overkill for me at this point (though I will have another look at it).

I’m mostly surprised that warming up by calling stuff in the module has practically no effect to calls out of it; I would have expected the compiled stuff to be available globally & to not have to be recompiled yet again (which, based on timings, seems to be partially the case).
Of course I’m probably phrasing/seeing this wrong as I know pretty much nothing about the compiler in Julia.

Yeah I realized your question is more complicated - but because of that you actually need to give more data for us to say anything meaningful :wink:
Maybe make an minimal working example?

Yes I figured someone might tell me off for not putting an MWE, I wasn’t sure I’d be able to reproduce the observation with something minimal but actually I was :slight_smile: Here’s a example (PS: the example is only meant to require specialisation and show something, not to be good code…)

file Blah.jl

module Blah

using LinearAlgebra

export Foo, foo

struct Foo{A<:Real, B<:Real, C<:Real}
    a::Vector{A}
    b::Vector{B}
    c::Vector{C}
end

bar(v::Vector{<:Real}) = maximum(v) - minimum(v) + norm(v)

foo(f::Foo) = bar(f.a) + bar(f.b) + bar(f.c)

function warmup()
    a = rand(Float16, 2)
    b = rand(Float32, 2)
    c = randn(2)
    foo(Foo(a, b, c))
end

warmup()

end # module

file load.jl

using Blah

a = rand(Float16, 100)
b = rand(Float32, 100)
c = randn(100)

@show @elapsed begin
    f = Foo(a, b, c)
    foo(f)
end

@show @elapsed begin
    f = Foo(a, b, c)
    foo(f)
end

Results commenting out warmup():

0.557871823 # first call
1.805e-5 # second call

results with warmup() (in a fresh session)

0.547161082 # first call
1.6461e-5 # second call

I would have expected the first call, with the warmup, to have been much faster given that it’s pretty much exactly the same call (apart from dimensions).

Note that modules are precompiled by default. So for module Blah, warmup() is only run when it is compiled. It will not run when using Blah. To see this, insert a print statement in warmup().

To have warmup() run whenever Blah is first loaded, put warmup() in __init__()

function warmup()
    println("warmup")
    a = rand(Float16, 2)
    b = rand(Float32, 2)
    c = randn(2)
    foo(Foo(a, b, c))
end

function __init__()
    warmup()
end

https://docs.julialang.org/en/v1/manual/modules/index.html#Module-initialization-and-precompilation-1

3 Likes

Awesome that makes perfect sense and I was not aware of the init thank you!