Skip to content

Update type-cache after each user interaction#1014

Open
timholy wants to merge 1 commit intomasterfrom
teh/inccache
Open

Update type-cache after each user interaction#1014
timholy wants to merge 1 commit intomasterfrom
teh/inccache

Conversation

@timholy
Copy link
Owner

@timholy timholy commented Feb 26, 2026

The intent here is to keep the type-cache up to date, so that if and when a type is redefined, there isn't a large latency before the revision can start.

This is the last piece of the puzzle needed to
Fix #988

Note this also re-enables type revision by default. CC @lassepe, @JamesWrigley . If you haven't followed, #1013 yielded a dramatic speedup. This PR should keep the cache up-to-date after each REPL command, which I hope will mean that it will be quite rare to hit long latencies on type-revision.

@JamesWrigley
Copy link
Contributor

Very cool 🎉 Slightly offtopic, but would it be possible to put that expression in a separate function? I was thinking that then IJulia could call it directly instead of reimplementing it: https://github.com/JuliaLang/IJulia.jl/blob/8e17d35ec29c89a7b51b75fd649021a94c647c3c/ext/IJuliaReviseExt.jl#L18

@lassepe
Copy link

lassepe commented Feb 26, 2026

In my test case from #988 (comment) , populating the type cache takes 20s every time, even after the cache has already been populated and no changes to any types have been made. Hence, I'm not sure if this extra background work is worth it. Perhaps we can limit the invocation of Revise.repopulate_typecache to scenarios where a new package is being loaded or a new type is being defined?

@timholy
Copy link
Owner Author

timholy commented Feb 26, 2026

It only takes 20s because you've just loaded a ton of packages. Once they are "processed" it will be ~15ms. You can try this manually: after loading your package with all its dependencies, call

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
    Revise.fieldtypes_cached(type)
end

and you should see it take ~20s. Then do the exact same thing again in this same session (without loading a whole bunch of new packages), and it should be very fast.

In other words, the way this fixes the slow-revision problem is that it starts scanning loaded packages in a background thread as soon as they load. That process should be finished (presuming it will take the user some to make some edits in their editor) before the next type revision occurs and the user will experience little latency.

@timholy timholy closed this Feb 26, 2026
@timholy timholy reopened this Feb 26, 2026
@JamesWrigley
Copy link
Contributor

20s hogging a thread just by loading packages seems a bit much to enable by default 👀

@timholy
Copy link
Owner Author

timholy commented Feb 26, 2026

@JamesWrigley

but would it be possible to put that expression in a separate function?

Done

@timholy
Copy link
Owner Author

timholy commented Feb 26, 2026

20s hogging a thread just by loading packages seems a bit much to enable by default 👀

Fair enough. It's a pretty big package, though (not quite "OmniPackage" but getting there), and just loading it is also on par with that amount of time.

Not sure what to do about the default. I might leave it on a bit and play with it myself for a while. I still haven't abandoned 1.10 for real work but I think now is the time...

@lassepe
Copy link

lassepe commented Feb 26, 2026

It only takes 20s because you've just loaded a ton of packages. Once they are "processed" it will be ~15ms. You can try this manually: after loading your package with all its dependencies, call

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
    Revise.fieldtypes_cached(type)
end

and you should see it take ~20s. Then do the exact same thing again in this same session (without loading a whole bunch of new packages), and it should be very fast.

I don't find this to be true in my tests. For testing purposes, I've modified the invocation of repopulate_typecache in revise_first to track timings:

    return Expr(:toplevel,
        :($isempty($revision_queue) || $(Base.invokelatest)($revise)),
        quote
            let $result = $ex
                # Base.Threads.@spawn :default $(repopulate_typecache)()
                t = time()
                println("Repopulating type cache...")
                $repopulate_typecache()
                println("Repopulation done after ", round(time() - t, sigdigits=3), " seconds.")
                $result
            end
        end
    )

With those changes, here's what I get with the test package from #988 (comment)

❯ JULIA_LOAD_PATH="@." julia --startup-file=no
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.5 (2026-02-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> using Revise

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
           Revise.fieldtypes_cached(type)
       end
  0.577651 seconds (245.60 k allocations: 176.847 MiB, 2.10% gc time, 2.21% compilation time)
Repopulating type cache...
Repopulation done after 0.57 seconds.

julia> using Foo
Repopulating type cache...
Repopulation done after 12.4 seconds.

julia> Foo.Bar()
Repopulating type cache...
Repopulation done after 12.6 seconds.
Foo.Bar(4.0, 5)

julia> Foo.Bar()
Repopulating type cache...
Repopulation done after 12.5 seconds.
Foo.Bar(4.0, 5)

@lassepe
Copy link

lassepe commented Feb 26, 2026

Similarly:

Revise.jl-struct-revision-kwdef on  main [!?] is 📦 v0.1.0 via ஃ v1.12.5 took 2m6s
❯ JULIA_LOAD_PATH="@." julia --startup-file=no
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.5 (2026-02-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> using Revise

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
           Revise.fieldtypes_cached(type)
       end
  0.563205 seconds (245.60 k allocations: 176.633 MiB, 2.12% gc time, 2.26% compilation time)
Repopulating type cache...
Repopulation done after 0.582 seconds.

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
           Revise.fieldtypes_cached(type)
       end
  0.560553 seconds (236.09 k allocations: 176.144 MiB, 2.12% gc time, 1.86% compilation time)
Repopulating type cache...
Repopulation done after 0.556 seconds.

julia> using Foo
Repopulating type cache...
Repopulation done after 12.6 seconds.

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
           Revise.fieldtypes_cached(type)
       end
 12.659920 seconds (3.93 M allocations: 3.295 GiB, 3.33% gc time, 0.07% compilation time)
Repopulating type cache...
Repopulation done after 12.7 seconds.

julia> @time Revise.foreach_subtype(Any) do @nospecialize type
           Revise.fieldtypes_cached(type)
       end
 12.824380 seconds (3.93 M allocations: 3.295 GiB, 4.56% gc time, 0.08% compilation time)
Repopulating type cache...
Repopulation done after 12.6 seconds.

Note that I did not make any modifications to source code in any of these tests.

@timholy
Copy link
Owner Author

timholy commented Feb 26, 2026

Ah shoot, I must have benchmarked on a small system. (Or maybe on julia master?) Just the call to names takes most of the time.

OK, I agree we probably have to turn this off by default, at least until JuliaLang/julia#60736 can be backported.

The intent here is to keep the type-cache up to date, so that if and
when a type is redefined, there isn't a large latency before the
revision can start.

This is the last piece of the puzzle needed to
Fix #988
@timholy
Copy link
Owner Author

timholy commented Feb 26, 2026

Note to anyone who might be tempted to merge this: it's not really feasible at present, need to wait for more performance work on the Julia side.

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

Successfully merging this pull request may close these issues.

Struct revision takes several minutes for packages with large type trees

3 participants