Starting to dabble in moving long calls from runtime into compile-time, and I’m finding that repeated calls in compile-time are also annoying. Naively, I tried compile-time memoization, also avoiding cache access overhead or type instability at runtime. A base Julia-only toy example:
const _minusoneto = IdDict{Any, Any}() # typical type instability in memoization packages
Base.@assume_effects :foldable function minusoneto(n::Int)
if n < 0
throw(DomainError(n, "minusoneto only supports nonnegative integer exponents"))
elseif haskey(_minusoneto, n)
return _minusoneto[n] # ::Any
else
x = 1
for _ in 1:n x *= -1 end
_minusoneto[n] = x
return x
end
end
foo(::Val{N}) where N = minusoneto(N)
bar(::Val{N}) where N = 10*minusoneto(N)
Memoization improves compile times for different callers that repeat a call. For anyone who tries this example, note that @time @eval is needed to capture JIT compile times for :foldable callees. This example’s callers optimize to constant return values, so I just time precompile:
julia> @time precompile(foo, (Val{123456789012},))
4.154013 seconds (36.21 k allocations: 1.743 MiB, 99.99% compilation time)
true
julia> @time precompile(bar, (Val{123456789012},))
0.000127 seconds (232 allocations: 11.578 KiB, 83.42% compilation time)
true
julia> @time precompile(bar, (Val{123456789013},))
4.129439 seconds (232 allocations: 11.656 KiB, 100.00% compilation time)
true
julia> empty!(_minusoneto); # often need this when minusoneto is redefined
julia> @time precompile(foo, (Val{123456789013},))
4.139190 seconds (170 allocations: 8.641 KiB, 100.00% compilation time)
true
:foldable is a sum of these effects:
:consistent:effect_free:terminates_globally:noub:nortcall
I think minusoneto simply satisfies the last 3, and I think it is :consistent as long as the cache strictly follows the method’s results, not the other way around on any level. It doesn’t satisfy the documented requirements for :effect_free because it’s mutating an externally heap-allocated object, but I can’t imagine how this can be a problem if said object only caches a consistent method (or potentially several within a world age). Did I stumble into an undocumented exception, or am I missing a danger here?