Skip to content

Parse proc bodies only in a background thread - #486

Closed
ZeWaka wants to merge 4 commits into
SpaceManiac:masterfrom
ZeWaka:split-proc-parse
Closed

ZeWaka wants to merge 4 commits into
SpaceManiac:masterfrom
ZeWaka:split-proc-parse

Conversation

@ZeWaka

@ZeWaka ZeWaka commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #484, this is based off of it (otherwise there's a regression in Linux memory usage).

The langserver now keeps proc bodies out of the object tree it holds onto during idle.

The main thread parses without proc bodies, which is what hover, completion, goto, etc. use.

At the same time a background thread does the full parse, sends the diagnostics, runs dreamchecker, builds the Find References table, and then throws its tree away.

It's not just as easy as dropping the proc bodies after dreamchecker because their allocations are mixed into the same pages as the data we keep.

Diagram

A thick border means it's kept in memory while idle

flowchart TD
    S[Load or reparse] --> M1
    S --> B1
    subgraph main[Main thread]
        M1[Parse, skipping proc bodies] --> M2[Object tree<br/>for hover, completion, goto]
    end
    subgraph bg[Background thread]
        B1[Full parse, with proc bodies] --> B2[Send parse errors]
        B2 --> B3[Build Find References table]
        B3 --> R[Find References table]
    end
    subgraph dc[Dreamchecker thread]
        D1[Check procs] --> D2[Send dreamchecker errors]
    end
    B2 -->|starts| D1
    B3 --> F[Full tree freed once both are done]
    D2 --> F
    classDef kept stroke-width:3px
    class M2,R kept
Loading

Median of 3 runs each on my local Windows machine:

Repo Build Editor Ready Errors ready Peak memory (load) Idle memory Peak memory (reparse) Idle memory (after reparse)
goon master 3.14 s 3.91 s 541 MB 504 MB 906 MB 678 MB
goon #484 1.92 s 2.38 s 515 MB 434 MB 803 MB 551 MB
goon this 1.16 s (-40%) 2.35 s 662 MB (+29%) 205 MB (-53%) 673 MB (-16%) 238 MB (-57%)
tg master 4.33 s 5.37 s 710 MB 654 MB 1176 MB 862 MB
tg #484 2.55 s 3.21 s 674 MB 566 MB 1059 MB 697 MB
tg this 1.71 s (-33%) 3.15 s 895 MB (+33%) 321 MB (-43%) 928 MB (-12%) 369 MB (-47%)

Percentages are vs #484. Similar percentages on WSL.

Thoughts

The main downside is a higher peak on the first load, since both parses are in memory at once for roughly the last third of the load. However, reparses actually peak lower than #484.

If the higher peak on the first load is a problem, a langclient setting to opt out of this could help users on memory-constrained systems.

Related: #113

@ZeWaka
ZeWaka marked this pull request as ready for review October 2, 2026 07:48
@SpaceManiac

Copy link
Copy Markdown
Owner

I'm not a fan of double-parsing. I'd prefer to put proc bodies in some set-aside heap so that dropping them afterwards actually works. Maybe we should let this ride until custom allocators stabilize in Rust 1.100.

@ZeWaka

ZeWaka commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

I'm not a fan of double-parsing. I'd prefer to put proc bodies in some set-aside heap so that dropping them afterwards actually works.

I looked into this a little bit but it seemed like it'd be a lot of code churn, maybe having to add lifetimes to the AST.

@ZeWaka

ZeWaka commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Superseded by #487, which puts proc bodies in a seperate mimalloc heap instead of double-parsing.

@ZeWaka ZeWaka closed this Oct 4, 2026
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.

2 participants