-
Notifications
You must be signed in to change notification settings - Fork 788
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
Transparent Compiler memory reduction in editors #17543
base: main
Are you sure you want to change the base?
Conversation
❗ Release notes requiredCaution No release notes found for the changed paths (see table below). Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format. The following format is recommended for this repository:
If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request. You can open this PR in browser to add release notes: open in github.dev
|
@@ -258,45 +258,119 @@ module private TypeCheckingGraphProcessing = | |||
|
|||
return finalFileResults, state | |||
} | |||
|
|||
type internal CompilerCaches(sizeFactor: int) = | |||
type CacheSizes = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should be internal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows an editor to finely tweak these options. This potentially trades memory for CPU usage but on larger solutions it a tradeoff someone can make it possible to have multiple solutions open.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, how would the editor come up with these numbers? I wouldn't suggest to expose this to the user.
I think you should be able to say "use more memory" or "use less memory", potentially even "hold more/less stuff strongly/weakly" but does it really make sense to have such granular control in the editor? So that different editors would benefit from different configurations somehow?
And if there is some smart way to set these based the solutions the user is working here it would be nice to have that code here so everyone can use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I exposed these numbers so it was easier to try to adjust them individually but I do agree it's not something I'd want to expose to a user directly.
I think the only settings come down to "memory" vs "recacluating". Specifically, my work project can be around 10-15gb once fully type checked, if I have multiple versions of open for various reasons, I'd rather try to be aggressive with collecting and keeping it's profile small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can expose it now for easier experimenting with some [<Experimental>]
comment that it will be removed/simplified for sure. What I initially put there was pretty arbitrary but hopefully we can eventually come to some consensus here and expose only a few parameters that will be enough to tweak it.
@@ -559,7 +559,7 @@ and [<Experimental("This FCS API is experimental and subject to change.")>] FSha | |||
referencedProjects: FSharpReferencedProjectSnapshot list, | |||
isIncompleteTypeCheckEnvironment: bool, | |||
useScriptResolutionRules: bool, | |||
loadTime: DateTime, | |||
loadTime: DateTime, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the space?
<InternalsVisibleTo Include="FsAutoComplete.Core" /> | ||
<InternalsVisibleTo Include="FsAutoComplete" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FSAC InternalsVisibleTo to allow the cache tweaks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels very wrong, is this a temporary thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transparent compiler itself is marked internal and unless the F# team wants to make it public, this is a better method than forcing FSAC to do reflection all the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's true, but there's actually a way to enable it using FSharpChecker.Create(useTransparentCompiler = true)
. Shouldn't more things be driven by that?
I just think this approach feels like a workaround that avoids proper communication, which seems like a missed opportunity. But if the team doesn't see it that way and everyone's fine with it, then I suppose it's okay.
One other thing that concerns me is that this could potentially be a common request for every FCS user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should expose everything before we stabilize it? Or, we can expose necessary parts, but put Experimental
on them. Or expose them via the checker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong preference here. I went with the least invasive changes first to do exactly this, facilitate discussion :)
One other thing that concerns me is that this could potentially be a common request for every FCS user.
Yeah I agree here. Though, it would be nice to also be able to access other things like LanguageFeatures instead of our hack
I wonder if we should expose everything before we stabilize it? We can expose necessary parts, but put Experimental on them. Or expose them via the checker?
Since FCS makes breaking changes that FSAC needs to handle anyway, it's all kind of the same thing in the end to me. Whatever makes more sense for your team.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd be happy to have such InternalsVisibleTo
entries too in Rider plugin. 🙂
We're also used to the changes in APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case we should just expose everything needed via APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also propose to add [Experimental] APIs for everything you need. Then at least we see everything that's needed when deciding on a stable API.
let internal snapshotTable = ConditionalWeakTable<ProjectSnapshot, FSharpProjectOptions>() | ||
|
||
let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshot) = | ||
snapshotTable.GetValue(projectSnapshot, fun projectSnapshot -> | ||
{ | ||
ProjectFileName = projectSnapshot.ProjectFileName | ||
ProjectId = projectSnapshot.ProjectId | ||
SourceFiles = projectSnapshot.SourceFiles |> Seq.map (fun x -> x.FileName) |> Seq.toArray | ||
OtherOptions = projectSnapshot.CommandLineOptions |> List.toArray | ||
ReferencedProjects = | ||
projectSnapshot.ReferencedProjects | ||
|> Seq.map (function | ||
| FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions) | ||
| PEReference(getStamp, reader) -> FSharpReferencedProject.PEReference(getStamp, reader) | ||
| ILModuleReference(name, getStamp, getReader) -> FSharpReferencedProject.ILModuleReference(name, getStamp, getReader)) | ||
|> Seq.toArray | ||
IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment | ||
UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules | ||
LoadTime = projectSnapshot.LoadTime | ||
UnresolvedReferences = projectSnapshot.UnresolvedReferences | ||
OriginalLoadReferences = projectSnapshot.OriginalLoadReferences | ||
Stamp = projectSnapshot.Stamp | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the main fix around creating too many options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense. Out of curiosity what are you using the resulting options for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be paths that still want a FSharpProjectOptions
. Following the current use of snapshotToOptions
should show them.
Specifically they show up in transparent compiler which get turned into longer lived objects here:
projectSnapshot.ToOptions(), |
projectSnapshot.ToOptions()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, didn't notice these usages. Wonder what would be a good way to phase them out of there... But for now at least your fix improves it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that would the real fix. I did do before that but it was more invasive.
Yeah, that's good enough. Also, if possible, CPU consumption. |
@@ -258,45 +258,119 @@ module private TypeCheckingGraphProcessing = | |||
|
|||
return finalFileResults, state | |||
} | |||
|
|||
type internal CompilerCaches(sizeFactor: int) = | |||
type CacheSizes = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, how would the editor come up with these numbers? I wouldn't suggest to expose this to the user.
I think you should be able to say "use more memory" or "use less memory", potentially even "hold more/less stuff strongly/weakly" but does it really make sense to have such granular control in the editor? So that different editors would benefit from different configurations somehow?
And if there is some smart way to set these based the solutions the user is working here it would be nice to have that code here so everyone can use it.
static member Default = | ||
let sizeFactor = 100 | ||
CacheSizes.Create sizeFactor | ||
type internal CompilerCaches(sizeFactor: CacheSizes) = | ||
|
||
let sf = sizeFactor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be renamed if we keep it like this.
<InternalsVisibleTo Include="FsAutoComplete.Core" /> | ||
<InternalsVisibleTo Include="FsAutoComplete" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also propose to add [Experimental] APIs for everything you need. Then at least we see everything that's needed when deciding on a stable API.
@@ -1363,8 +1440,8 @@ type internal TransparentCompiler | |||
node, | |||
(fun tcInfo -> | |||
|
|||
if tcInfo.stateContainsNodes |> Set.contains fileNode then | |||
failwith $"Oops!" | |||
// if tcInfo.stateContainsNodes |> Set.contains fileNode then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like an unrelated fix. Not sure if it's been discussed elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Been discussed in slack before (probably beyond any history now 😢 ). Yeah this could be reverted but I've seen this popup crashing the TC hard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. It seems a bit fishy. I need to understand if this is something that should legitimately be happening, clearly previously I thought it shouldn't 😅
let internal snapshotTable = ConditionalWeakTable<ProjectSnapshot, FSharpProjectOptions>() | ||
|
||
let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshot) = | ||
snapshotTable.GetValue(projectSnapshot, fun projectSnapshot -> | ||
{ | ||
ProjectFileName = projectSnapshot.ProjectFileName | ||
ProjectId = projectSnapshot.ProjectId | ||
SourceFiles = projectSnapshot.SourceFiles |> Seq.map (fun x -> x.FileName) |> Seq.toArray | ||
OtherOptions = projectSnapshot.CommandLineOptions |> List.toArray | ||
ReferencedProjects = | ||
projectSnapshot.ReferencedProjects | ||
|> Seq.map (function | ||
| FSharpReference(name, opts) -> FSharpReferencedProject.FSharpReference(name, opts.ProjectSnapshot |> snapshotToOptions) | ||
| PEReference(getStamp, reader) -> FSharpReferencedProject.PEReference(getStamp, reader) | ||
| ILModuleReference(name, getStamp, getReader) -> FSharpReferencedProject.ILModuleReference(name, getStamp, getReader)) | ||
|> Seq.toArray | ||
IsIncompleteTypeCheckEnvironment = projectSnapshot.IsIncompleteTypeCheckEnvironment | ||
UseScriptResolutionRules = projectSnapshot.UseScriptResolutionRules | ||
LoadTime = projectSnapshot.LoadTime | ||
UnresolvedReferences = projectSnapshot.UnresolvedReferences | ||
OriginalLoadReferences = projectSnapshot.OriginalLoadReferences | ||
Stamp = projectSnapshot.Stamp | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense. Out of curiosity what are you using the resulting options for?
Description
This fixes #16979 with some additions that have been discussed in various chats/twitter threads. I've combined them for discussion purposes, we can split them up as desired.
The 3 parts are:
ConditionalWeakTable
rather than recreating possibly hundreds of options objects.InternalsVisibleTo
so we can utilize these cache tweaking changes.❓ Outstanding questions ❓
Checklist
Test cases added
Performance benchmarks added in case of performance changes
Release notes entry updated: