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

Cache parsing results in incremental build #14852

Merged
merged 39 commits into from
Mar 14, 2023

Conversation

majocha
Copy link
Contributor

@majocha majocha commented Mar 6, 2023

Addresses #14848.
In this implementation IncrementalBuilder keeps one most recent result of parsing for each file.
Cached items are removed on source change of corresponding file.
This works the same with live buffers and the normal on disk mechanism.

Memory impact does not look bad, for example FCS solution open in Visual Studio:
image

note: with enablePartialTypeChecking it does not cache files backed with signature.

This is not a new feature. In IncrementalBuild.fs there was code for similar caching before, but it stopped functioning at some point, I guess? Probably a good idea to put a small test for this, but I'm not sure how to do it.

This can be toggled in performance settings in VS:
image

to do:

  • add option to enable / disable
  • mention this feature in docs
  • add a test

@majocha majocha requested a review from a team as a code owner March 6, 2023 13:42
@majocha majocha marked this pull request as draft March 6, 2023 13:42
@T-Gro
Copy link
Member

T-Gro commented Mar 6, 2023

Can we estimate the memory impact of this, e.g. on FCS solution ?

@majocha
Copy link
Contributor Author

majocha commented Mar 6, 2023

Can we estimate the memory impact of this, e.g. on FCS solution ?

Hard to tell how much it allocate, but there seems to be less GC pressure.
image

Above - main, below this PR.
Both FCS.sln, editing SemanticClassification.fs, with ServiceAnalysis.fs open in editor.

I guess we can finetune the cache to our liking.

@majocha
Copy link
Contributor Author

majocha commented Mar 6, 2023

FCS solution, after typecheck:
image

@majocha
Copy link
Contributor Author

majocha commented Mar 7, 2023

OK, so IncrementalBuilder currently don't seem to have a clear notion of source text change. NotifyFileChanged is being called internally in situations where there obviously is no source change, complicating things and leading to cascades of reparsing everything when typing in editor.

A vague idea:
We keep FSharpSource objects encapsulating access to ISourceText but they are long lived and kept in IncrementalBuilderInitialState. Probably they should be tracked by IncrementalBuilderState, carrying some source text version information. That would also give us something to just weakly attach cached parse results to, without care of keying and lifetime.

@0101
Copy link
Contributor

0101 commented Mar 7, 2023

@majocha NotifyFileChanged is part of this experimental feature: #14076 and can be altered or even removed altogether if we find a better solution. If it's being called where we can obviously tell there's no change we can probably just not call it. Also it shouldn't be called at all by default unless someone opts-in to this feature. So if your improvements work fine when this is disabled it's still good and we can figure out this part later.

@majocha
Copy link
Contributor Author

majocha commented Mar 7, 2023

Thanks I'll take a look. Yeah, I'm running with live buffers because its 2023 after all 🙂. Possibly some problems with excessive parsing that people report are related to that.

@majocha
Copy link
Contributor Author

majocha commented Mar 7, 2023

Let's see what breaks.

@majocha
Copy link
Contributor Author

majocha commented Mar 8, 2023

I'm making some changes to better grasp the code, I will reduce the diff later.

This is slowly taking shape, and feels a bit better when editing with live buffers. Should now work also with the dead ones 🙂.

@0101, about the live buffers: When the user types fast it fires a lot of NotifyFileChanged calls. I wonder whether some debounce / cancellation on the client is needed or does the builder deal with it smartly on it's own?

@0101
Copy link
Contributor

0101 commented Mar 8, 2023

@0101, about the live buffers: When the user types fast it fires a lot of NotifyFileChanged calls. I wonder whether some debounce / cancellation on the client is needed or does the builder deal with it smartly on it's own?

Calling NotifyFileChanged shouldn't be very expensive, it just updates an array in the incremental builder state. Although it does involve a lock and some NodeCode operations. Might be worth to try adding some debounce to it and making a benchmark for it to see if it makes a difference.

@majocha
Copy link
Contributor Author

majocha commented Mar 8, 2023

Almost done, let's see if it's still green.

@vzarytovskii
Copy link
Member

@majocha This looks good so far. Can I please ask you to make sure that the new behaviour is docummented somewhere under docs in the repo?

@majocha
Copy link
Contributor Author

majocha commented Mar 10, 2023

OK testing this need some more thought. We should actually count cache hits.

@majocha
Copy link
Contributor Author

majocha commented Mar 10, 2023

@T-Gro, I added some tests that actually count cache hits with telemetry.
This is definitely not something I'm familiar with, so please say if you see anything dumb 🙂.

@majocha
Copy link
Contributor Author

majocha commented Mar 10, 2023

Tests done, docs done. This is ready for review when green.

Copy link
Member

@psfinaki psfinaki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@majocha thanks for your awesome work. Please also add a resource here, that's for the purpose of searching this setting in the VS options window.

@auduchinok @safesparrow are you fine with merging this?

@vzarytovskii
Copy link
Member

That is great, thanks for it. I think we should merge and insert it, so we can start testing it.

Copy link
Contributor

@0101 0101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good!

src/Compiler/Service/service.fs Show resolved Hide resolved
@safesparrow
Copy link
Contributor

safesparrow commented Mar 13, 2023

@safesparrow are you fine with merging this?

I'm ok with this if it either doesn't make caching other usecases harder in the future (it likely doesn't) or already works for all IDEs including Rider.

Last time I tested this it didn't work for Rider, but I didn't test the latest version.

@majocha
Copy link
Contributor Author

majocha commented Mar 13, 2023

Please also add a resource here, that's for the purpose of searching this setting in the VS options window.

@psfinaki, cool, it shows up in search now.

@majocha
Copy link
Contributor Author

majocha commented Mar 13, 2023

@safesparrow this should work now for all cases involving incremental builder, whether ISourceText is used or not. There are still cases in VS (Rider probably too) where some ParseInput calls are being made that are not going through IncrementalBuilder. Maybe it's viable to direct these calls to IncrementalBuilder.GetParseResultsForFile instead, I don't know, it's something to investigate...

@psfinaki psfinaki merged commit fdba974 into dotnet:main Mar 14, 2023
@majocha majocha deleted the cache-parse-incremental branch March 14, 2023 10:18
@T-Gro
Copy link
Member

T-Gro commented Mar 14, 2023

@T-Gro, I added some tests that actually count cache hits with telemetry. This is definitely not something I'm familiar with, so please say if you see anything dumb 🙂.

This looks good, thank you!

vzarytovskii pushed a commit that referenced this pull request Mar 14, 2023
* Cache parsing results in incremental build (#14852)

* First take on the F# telemetry (#14889)

* Array.Parallel - search functions (tryFindIndex,tryFind,tryPick) (#14827)

* Searching functions for Array.Parallel added

* with [<Experimental("Experimental library feature, requires '--langversion:preview'")>] annotation to give us space to change/remove this API in the future if needed

* Add `GraphNode.FromResult` (#14894)

* Add GraphNode.FromResult

* fantomas

* Fix missing reference (#14892)

* Fix missing reference

* undo whitespace change

---------

Co-authored-by: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Co-authored-by: Petr <psfinaki@users.noreply.github.com>
Co-authored-by: Tomas Grosup <tomasgrosup@microsoft.com>
Co-authored-by: Petr Pokorny <petrpokorny@microsoft.com>
Co-authored-by: Kevin Ransom (msft) <codecutter@hotmail.com>
@dawedawe
Copy link
Contributor

FYI: We modified a local Rider instance to use this and we can see the cache being hit in some sequences of user interactions.

Taking the Fantomas repository as an example, it can be seen how opening some files is filling up the cache. Especially the files in our .deps folder :)
Opening depending files or building then hits the cache. I need to dig deeper to really see what kind of user interaction sequences with the IDE are profiting from this, but it looks quite good already.
Thanks a lot for this work!

@safesparrow
Copy link
Contributor

I tested it in Rider and can see that files in between the modified file and the file that gets opened/checked are not reparsed.
So in the scenario below:
image
changing A.fs and opening C.fs does not reparse B.fs (which is good).

There are some other Rider issues/reasons that cause many parsing requests, as described in JetBrains/resharper-fsharp#492 - that might explain the nonconsistent caching behaviour you've seen @dawedawe . But I think the feature is working as expected in the scenario where a chain of files are checked due to a change at the start of the chain.

kant2002 pushed a commit to kant2002/fsharp that referenced this pull request Apr 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

8 participants