Issue | Using PrecompileTools to reduce TTFX

Hello,

As the TTFX has become significant in one of my projects,
I tried to implement PrecompileTools to reduce it.

However, there was no significant reduction in the TTFX.

Below is a MNWE representing how I attempted to implement
PrecompileTools:

# test_PrecompileTools.jl

using PrecompileTools

include("test_PC_math_operations.jl")
using .MathOperations: scale_and_sum

function main()
    println("Data initialization")
    println("")
    
    scalar_1 = 10.0
    scalar_2 = 1.0
    vctr = rand(Float64, 10_000_000)
    
    # First execution
    print("First call:")
    @time result_1 = scale_and_sum(scalar_1, scalar_2, vctr)
    println("Result: ", result_1)
    println("")
    
    # Second execution
    print("Second call:")
    @time result_2 = scale_and_sum(scalar_1, scalar_2, vctr)
    println("Result: ", result_2)
end

begin
    main()
end

which calls

# test_PC_math_operations.jl

module MathOperations

using PrecompileTools

PrecompileTools.verbose[] = true

export scale_and_sum
function scale_and_sum(scalar_1::Float64, scalar_2::Float64, vctr::Vector{Float64})
    result = 0.0
    for val in vctr
        result += val * scalar_1 + scalar_2
    end
    return result
end

# PrecompileTools block
@setup_workload begin
    dummy_scalar_1 = 1.0
    dummy_scalar_2 = 1.0
    dummy_vector = rand(Float64, 10)
    
    @compile_workload begin
        scale_and_sum(dummy_scalar_1, dummy_scalar_2, dummy_vector)
    end
end

end  # module MathOperations

In VS Code, I first use Ctrl P to clear the compiled Julia modules and functions.

I then start Julia and execute the scripts.

First run output:

julia> include("test_PrecompileTools.jl")
MethodInstance for scale_and_sum(::Float64, ::Float64, ::Vector{Float64})
MethodInstance for PrecompileTools.tag_newly_inferred_disable()
Data initialization

First call:  0.011713 seconds
Result: 6.000131988730281e7

Second call:  0.012140 seconds
Result: 6.000131988730281e7

No difference in the execution time between the First and Second call.

When I execute the scripts a second time:

Julia> include("test_PrecompileTools.jl")
MethodInstance for scale_and_sum(::Float64, ::Float64, ::Vector{Float64})
MethodInstance for PrecompileTools.tag_newly_inferred_disable()
MethodInstance for scale_and_sum(::Float64, ::Float64, ::Vector{Float64})
WARNING: ignoring conflicting import of MathOperations.scale_and_sum into Main
Data initialization

First call:  0.011959 seconds
Result: 5.9982883771461785e7

Second call:  0.012122 seconds
Result: 5.9982883771461785e7

WARNING: ignoring conflicting import of MathOperations.scale_and_sum into Main

is generated.

My understanding is the precompiled files should be stored in

~/.julia/compiled/v1.13

However, when I look in this directory, no MathOperations
subdirectory has been created.

Check the location of the primary depot path

joinpath(DEPOT_PATH\[1\], "compiled")

~/.julia/compiled

System:
Julia v1.13.3
PrecompileTools v1.4.0
Revise v3.18.0

WSL2
WSL version: 3.0.5.0
Kernel version: 6.18.58.1-1
WSLg version: 1.0.79

Ubuntu 26.04.1 LTS

Any advance as to what I am missing would be appreciated.

It probably works better if you use julia --project to specify a virtual environment where the module is available, then use import rather than include in your script to load the module. Also, using PrecompileTools should only be present in the loaded module, not the main script.

Indeed, if you dev your module into another environment, you will get files under v1.13/compiled.

Another thing to point out is that even without precompilation, your test would not show anything:

julia> function scale_and_sum(scalar_1::Float64, scalar_2::Float64, vctr::Vector{Float64})
           result = 0.0
           for val in vctr
               result += val * scalar_1 + scalar_2
           end
           return result
       end
scale_and_sum (generic function with 1 method)

julia> function main()
           println("Data initialization")
           println("")

           scalar_1 = 10.0
           scalar_2 = 1.0
           vctr = rand(Float64, 10_000_000)

           # First execution
           print("First call:")
           @time result_1 = scale_and_sum(scalar_1, scalar_2, vctr)
           println("Result: ", result_1)
           println("")

           # Second execution
           print("Second call:")
           @time result_2 = scale_and_sum(scalar_1, scalar_2, vctr)
           println("Result: ", result_2)
       end
main (generic function with 1 method)

julia> main()
Data initialization

First call:  0.011481 seconds
Result: 6.0018120142104186e7

Second call:  0.011294 seconds
Result: 6.0018120142104186e7

It’s still compiling just in time, but this is done at the level of main itself, so that the @times within main do not show it.

In a fresh REPL:

julia> @time main()
Data initialization

First call:  0.010885 seconds
Result: 6.000674576878514e7

Second call:  0.010775 seconds
Result: 6.000674576878514e7
  0.077704 seconds (7.94 k allocations: 76.765 MiB, 11.22% gc time, 38.99% compilation time)

julia> @time main()
Data initialization

First call:  0.010693 seconds
Result: 5.998071017933558e7

Second call:  0.011344 seconds
Result: 5.998071017933558e7
  0.060838 seconds (34 allocations: 76.296 MiB, 35.76% gc time)