JuliaStaging / JuliaStaging/GeneralizedGenerated.jl
Base.show method on Type{RuntimeFn{...}} invalidates ~1550 method instances on load
- Dominant language
- Julia
- Stars
- 89
- Forks
- 12
- PR merge metrics
- No merged PRs in 30d
Description
`using GeneralizedGenerated` invalidates ~1550 precompiled method instances in a fresh session. The cause is a single method:
```julia
# src/ngg/runtime_fns.jl:15
Base.show(io::IO, ::Type{RuntimeFn{Args,Kwargs,Body,Name}}) where {Args,Kwargs,Body,Name} =
print(io, "ggfunc-$Name")
```
This adds a `Base.show` method on a `Type{...}` argument. Inference has already cached a great many `show(::IO, ::Type)` / `print` / `string` call sites by the time a package loads, and adding a method that could match some of them invalidates that cached code. Every downstream package then recompiles on first use.
This is distinct from #73, which reports that these same two methods *throw* when the type parameters aren't bound. This issue is about their cost even when nothing calls them.
### Measurement
Julia 1.12.6, GeneralizedGenerated 0.3.3, CSV.jl 1.0.0, as a representative downstream consumer. I loaded CSV, did one read to warm it, then measured a fresh session with and without `using GeneralizedGenerated`:
| | invalidations on load | 1st `CSV.File` (7x500) | 1st `CSV.File` (60x2000) |
|---|---|---|---|
| without GG | — | 0.22s | 0.41s |
| with GG | 1550 | 3.39s | 3.40s |
| with GG, method deleted | 17 | 0.26s | 0.39s |
Deleting that one method — leaving the instance-level `show(io::IO, rtfn::RuntimeFn{...})` on the line above untouched — removes the entire penalty. Invalidations drop from 1550 to 17, and the cost to the downstream package disappears.
Note that the ~3s is flat regardless of file size. It is fixed recompilation, not work proportional to anything the caller does.
### Reproducer
```julia
# Pkg.add("CSV"); Pkg.add("GeneralizedGenerated"); Pkg.add(["SnoopCompileCore", "SnoopCompile"])
using SnoopCompileCore
using CSV
path = tempname() * ".csv"
write(path, "a,b,c\n" * join(("$i,x$i,$(i/3)" for i in 1:500), "\n"))
CSV.File(path) # warm
invs = @snoop_invalidations using GeneralizedGenerated
using SnoopCompile
length(uinvalidated(invs)) # ~1550
```
And the timing half, in two fresh sessions:
```julia
using CSV # vs: using GeneralizedGenerated; using CSV
@time CSV.File(path)
```
### Why this matters downstream
This surfaced while closing JuliaData/CSV.jl#969, a 2022 report that `CSV.File` hung when Soss.jl was loaded. Soss depends on GeneralizedGenerated. On Julia 1.7, with no package-image native-code caching, this invalidation forced CSV to recompile its whole parse path from scratch in-session, which is consistent with the hang that was reported. On current Julia it is a bounded ~3s, but it is still paid by every package downstream of GG on every session.
### Possible fixes
1. **Delete the `Type{...}` method.** Types then print with the default representation. This is the change I measured, and it costs nothing but verbose type display.
2. **Keep it but make it opt-in**, e.g. behind a function a user calls explicitly, so loading the package doesn't perturb the method table.
I'd suggest 1. Any method on `Base.show(::IO, ::Type{...})` is inherently invalidating, so there isn't a version of this that keeps the pretty type display for free. Happy to open a PR if a maintainer prefers one of these.
[issue investigated and resolution proposed by AI; reviewed by quinnj; please respond with discrepancies/inaccuracies]
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/ngg/runtime_fns.jl at the Base.show method on Type{RuntimeFn{...}} and reproduce the invalidations with the SnoopCompileCore and CSV example. Compare fresh-session invalidation counts and the first CSV.File timing after addressing that method; done means the large invalidation and downstream recompilation penalty are gone while the instance-level show method remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- performance
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100