JuliaStaging / JuliaStaging/GeneralizedGenerated.jl

Base.show method on Type{RuntimeFn{...}} invalidates ~1550 method instances on load

Open Beginner friendly
#76 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.