A few basic questions about Precompilation

Hi everyone,
I have recently tried with little success to add a precompilation step to my own package project and hope someone can help me clarify a few problems.
In this post

it is explained that one can precompile methods by calling precompile(method, (types...), however making it work in practice seems quite cumbersome. In particular, it is pointed out that the compiler cannot really precompile functions called within method without further steps.

  1. If I have a lot of functions is it really the only way to precompile them in a reasonable manner that I specify the argument types of each function individually?

  2. If I have a function exemplaryRun(), which performs a typical workload with typical types, i can call this function within the definition of my module. From what I have seen, this executes the function only at the precompilation step. I would have expected that this would reduce compilation time so that methods only need to be compiled after loading the module if they have different types. Unfortunately, I noticed that somehow this introduces nasty run-time allocations when I use methods from my module, so somehow this approach does not seem to work. Why?

  3. Is there an easy but relatively effective way to incorporate precompilation? I am aware of PackageCompiler but having to create a new sys-image everytime something is changed in the package is not really worth it as of now.

I hope there are some insightful answers to any of these :slight_smile:

I am facing same issue …

Unfortunately believe only way is to indeed call every function with each argument type. Then include output of @snoopi_deep via something similar to:

ccall(:jl_generating_output, Cint, ()) == 1 || begin
    for (root, dirs, files) in walkdir("precompile")
        for file in files
            include(joinpath(root, file))
        end
    end
end

Hope this enlightens you somehow.
Let me know if you have any additional tips

1 Like

The easiest way to force precompilation is to execute code in your package definition: just take whatever “work” you would have run under @snoopi_deep and just run it right before the final end of your package’s module. ImageCore.jl is a good example to look at.

There are a few cases where this isn’t possible, like when running the code requires an active display, or there would be other side effects. In such cases you still need precompile.

4 Likes

Thank you!

thanks for your answer. I feel like I am missing something obvious here:
Isn’t what you propose similar to my example (2.) where I run a function exemparyRun() in the definition of my module? For me this resulted in a crippling performance loss and many runtime allocations (which I still do not quite understand).
I had a look at ImageCore.jl, but in the file src/ImageCore.jl there are only method and type definitions and I cannot see any code being executed, also not in the included files.

__precompile__()

module ImageCore

using Colors, FixedPointNumbers, MappedArrays, Graphics, ShowItLikeYouBuildIt
using OffsetArrays # for show.jl
using Colors: Fractional
[...]

"""
    rawview(img::AbstractArray{FixedPoint})
returns a "view" of `img` where the values are interpreted in terms of
their raw underlying storage. For example, if `img` is an `Array{N0f8}`,
the view will act like an `Array{UInt8}`.
"""
rawview{T<:FixedPoint}(a::AbstractArray{T}) = mappedarray((reinterpret, y->T(y,0)), a)
rawview{T<:FixedPoint}(a::Array{T}) = reinterpret(FixedPointNumbers.rawtype(T), a)
rawview{T<:Real}(a::AbstractArray{T}) = a

"""
    normedview([T], img::AbstractArray{Unsigned})
returns a "view" of `img` where the values are interpreted in terms of
`Normed` number types. For example, if `img` is an `Array{UInt8}`, the
view will act like an `Array{N0f8}`.  Supply `T` if the element
type of `img` is `UInt16`, to specify whether you want a `N6f10`,
`N4f12`, `N2f14`, or `N0f16` result.
"""
normedview{T<:FixedPoint,S<:Unsigned}(::Type{T}, a::AbstractArray{S}) = mappedarray((y->T(y,0),reinterpret), a)
normedview{T<:FixedPoint,S<:Unsigned}(::Type{T}, a::Array{S}) = reinterpret(T, a)
normedview{T<:Normed}(::Type{T}, a::AbstractArray{T}) = a
normedview(a::AbstractArray{UInt8}) = normedview(N0f8, a)
normedview{T<:Normed}(a::AbstractArray{T}) = a

"""
    permuteddimsview(A, perm)
returns a "view" of `A` with its dimensions permuted as specified by
`perm`. This is like `permutedims`, except that it produces a view
rather than a copy of `A`; consequently, any manipulations you make to
the output will be mirrored in `A`. Compared to the copy, the view is
much faster to create, but generally slower to use.
"""
permuteddimsview(A, perm) = Base.PermutedDimsArrays.PermutedDimsArray(A, perm)

# Support transpose
Base.transpose{C<:Colorant}(a::AbstractMatrix{C}) = permutedims(a, (2,1))
function Base.transpose{C<:Colorant}(a::AbstractVector{C})
    ind = indices(a, 1)
    out = similar(Array{C}, (oftype(ind, Base.OneTo(1)), ind))
    outr = reshape(out, ind)
    copy!(outr, a)
    out
end

Base.ctranspose{C<:Colorant}(a::AbstractMatrix{C}) = transpose(a)
Base.ctranspose{C<:Colorant}(a::AbstractVector{C}) = transpose(a)

end ## module

I assume I am not looking in the right position or I misunderstood you. Could you clarify this for me?

For ImageCore, see ImageCore.jl/ImageCore.jl at ff7afc048c5832d8f3e6269f40ed06c4399cec7b · JuliaImages/ImageCore.jl · GitHub and ImageCore.jl/precompile.jl at master · JuliaImages/ImageCore.jl · GitHub.

i can call this function within the definition of my module. From what I have seen, this executes the function only at the precompilation step. I would have expected that this would reduce compilation time so that methods only need to be compiled after loading the module if they have different types. Unfortunately, I noticed that somehow this introduces nasty run-time allocations when I use methods from my module, so somehow this approach does not seem to work. Why?

That’s probably Allocates when precompiled · Issue #35972 · JuliaLang/julia · GitHub, which should be fixed (and more) in Julia 1.8 (see Cache external CodeInstances by timholy · Pull Request #43990 · JuliaLang/julia · GitHub).

1 Like

Thanks for the pointers, thats great!
Also thanks for the link to the issue, I am looking forward to Julia 1.8 then :).