Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Be more selective about cache invalidation, instead of always wiping the entire thing. #5

Open
CameronBieganek opened this issue Aug 10, 2020 · 2 comments

Comments

@CameronBieganek
Copy link

I just came across this while playing around in the REPL. I could see it potentially coming up in real life if somebody is using @memoize in a script.

julia> using Memoization

julia> @memoize function foo(x::Int)
           println("Computing...")
           return 2x
       end
foo (generic function with 1 method)

julia> foo(3)
Computing...
6

julia> foo(3)
6

julia> Memoization.caches[(typeof(foo), IdDict)]
IdDict{Any,Any} with 1 entry:
  ((3,), NamedTuple()) => 6

julia> @memoize function foo(x::String)
           println("Computing...")
           return x * "asdf"
       end
foo (generic function with 2 methods)

julia> foo("hello")
Computing...
"helloasdf"

julia> foo("hello")
"helloasdf"

julia> Memoization.caches[(typeof(foo), IdDict)]
IdDict{Any,Any} with 1 entry:
  (("hello",), NamedTuple()) => "helloasdf"

julia> foo(3)
Computing...
6

julia> Memoization.caches[(typeof(foo), IdDict)]
IdDict{Any,Any} with 2 entries:
  (("hello",), NamedTuple()) => "helloasdf"
  ((3,), NamedTuple())       => 6
@CameronBieganek CameronBieganek changed the title Using a memoized method before defining second memoized method results in cache being overwritten Using first memoized method before defining second memoized method results in cache being overwritten Aug 10, 2020
@marius311
Copy link
Owner

This is kind of intentional, the idea being that you don't want a later-defined method to lead to an incosistent memoized result. Instead, you always want the memoized thing returned to be exactly what you'd get if you actually called the function, e.g.

@memoize foo(x) = "original"
foo(1) # memoizes 1 -> "original"
@memoize foo(x::Int) = "something else"
foo(1) # would return "original" if we didn't clear the cache just above

However, your example brings up a point I hadn't thought of which is that its possible to be more selective than wiping the entire cache. Ie if you define foo(::Int) and then foo(::String), then because typeintersect(Int,String)=Union{} you don't need to clear the cache at all (conversely above, typeintersect(Int,Any)=Int, so you know there's the potential for an invalid cached result). Even if there were some non-null intersection, I think you could in theory scan through the cache and only delete certain entries that you knew need to be invalidated, I think all the information is there in the method signature and the types of the memoized arguments. This would be something cool to have. I've changed the title of this issue to reflect that and put it on my todo / wishlist (more than happy to take a PR too should you be interested in it).

@marius311 marius311 changed the title Using first memoized method before defining second memoized method results in cache being overwritten Be more selective about cache invalidation, instead of always wiping the entire thing. Aug 10, 2020
@marius311
Copy link
Owner

marius311 commented Apr 16, 2021

Actually the ultimate solution here is to use backedges to just piggyback off of Julia method invalidations, which would mean we're being as selective as possible (see e.g. https://www.oxinabox.net/2021/02/13/Julia-1.6-what-has-changed-since-1.0.html#manually-created-back-edges-for-lowered-code-generated-functions). Doubt I myself have time to work on this in the near future but would be really cool to have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants