Precompile() for function with kwargs

question

#1

hi everyone,
I am using julia 1.0.2, and try to precompile for function. precompile() works for function with args(no kwargs), but when have kwargs, it seems not worked, so how to precompile for function with kwargs? my code is this:

function test_kwargs(x::Int; y::Int=2)
	num = 1000000
	res = zeros(Int, num)
	x = Float64(x+y)
	for i in 1:num
		res[i] = i * x + i * i
	end
end
#@time test(2)
precompile(test_kwargs, (Int,))
@time test_kwargs(2)
@time test_kwargs(2)


function test_args(x::Int)
	num = 1000000
	y = 2    
	res = zeros(Int, num)
	x = Float64(x+y)
	for i in 1:num
		res[i] = i * x + i * i
	end
end
precompile(test_args, (Int,))
@time test_args(2)
@time test_args(2)

output :

  0.038996 seconds (16.46 k allocations: 8.472 MiB)
  0.009107 seconds (6 allocations: 7.630 MiB)
  0.008354 seconds (6 allocations: 7.630 MiB)
  0.007994 seconds (6 allocations: 7.630 MiB)

#2

This confused me, thanks for any help~
From the run time of test_kwargs and test_args, the first run of test_kwargs(2) seem not had been precompiled, but the first run of test_args(2) seem to had been precompiled.


#3

The short answer is to do

precompile(Core.kwfunc(test_kwargs), (Vector{Any}, typeof(test_kwargs), Int))

if you absolutely need to manually precompile. When benchmarking, I strongly recommend using BenchmarkTools, which takes into account function precompilation in its time reporting, so you don’t need to call precompile yourself at all.

Now, the reason this precompile call looks kind of funny is that what’s called when using keyword arguments in a function f is actually the function #kw##f. This function can be retrieved using Core.kwfunc. Its method signature starts with Any, which is where the collection of keyword arguments ends up going, the type of the function f, then the types of f's positional arguments.

One way to visualize this is with Cassette, defining prehook to pretty-print each function call being made when using keyword arguments:

julia> f(; x=1) = x + 1

julia> using Cassette

julia> Cassette.@context ExampleCtx;

julia> function Cassette.prehook(::ExampleCtx, f, args...)
           f isa Type ? print('(', f, ')') : print(f)
           print('(')
           join(stdout, args, ", ")
           print(')')
       end

julia> Cassette.overdub(ExampleCtx(), ()->f(x=4))
Core.apply_type(NamedTuple, (:x,))
tuple(4)
(NamedTuple{(:x,),T} where T<:Tuple)((4,))
typeof((4,))
Core.apply_type(NamedTuple, (:x,), Tuple{Int64})
(NamedTuple{(:x,),Tuple{Int64}})((4,))
Core.apply_type(NamedTuple, (:x,), Tuple{Int64})
getfield((4,), 1)
Core.kwfunc(f)
getfield(Main, Symbol("#kw##f"))()((x = 4,), f)
+(4, 1)
add_int(4, 1)
5

Note in particular the lines 4th and 5th from the bottom, which use Core.kwfunc to retrieve #kw##f and then call it.


#4

Thanks a lot~ the function test_kwargs only run once when I use the script in fact, so if use @btime I will not see the actual run time of test_kwargs.

function test_kwargs(x::Int; y::Int=2)
	num = 1000000
	res = zeros(Int, num)
	x = Float64(x+y)
	for i in 1:num
		res[i] = i * x + i * i
	end
end
precompile(Core.kwfunc(test_kwargs), (Vector{Any}, typeof(test_kwargs), Int))
@time test_kwargs(2)
@time test_kwargs(3)
@time test_kwargs(4)

when I change to that, still output

  0.031534 seconds (35.89 k allocations: 9.466 MiB)
  0.008747 seconds (6 allocations: 7.630 MiB)
  0.008436 seconds (6 allocations: 7.630 MiB)

I miss something ?
My origin plan is precompile the function which only run once, so the run time of the funciton will decrease a lot.


#5

you can quite easily create a precompile file with:

julia --compile=all -O0 --trace-compile=precompile.jl testfile.jl

with testfile.jl containing:

function test_kwargs(x::Int; y::Int=2)
	num = 1000000
	res = zeros(Int, num)
	x = Float64(x+y)
	for i in 1:num
		res[i] = i * x + i * i
	end
end

test_kwargs(2)
test_kwargs(2, y = 3)

Then the precompile.jl file will contain:

precompile(Tuple{typeof(Main.test_kwargs), Int64})
precompile(Tuple{getfield(Main, Symbol("##test_kwargs#3")), Int64, typeof(Main.test_kwargs), Int64})
precompile(Tuple{Type{NamedTuple{(:y,), T} where T<:Tuple}, Tuple{Int64}})
precompile(Tuple{getfield(Main, Symbol("#kw##test_kwargs")), NamedTuple{(:y,), Tuple{Int64}}, typeof(Main.test_kwargs), Int64})

So there is more to this than what the previous solution implied :wink:
As you can see, the main function that runs your code, is actually gensymed.
So it will be pretty hacky/impossible, to get that function name manually!