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

Reduce excess memory usage in TransparentCompiler #17543

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.200.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* Fix locals allocating for the special `copyOfStruct` defensive copy ([PR #18025](https://github.com/dotnet/fsharp/pull/18025))
* Fix lowering of computed array expressions when the expression consists of a simple mapping from a `uint64` or `unativeint` array. [PR #18081](https://github.com/dotnet/fsharp/pull/18081)
* Add missing nullable-metadata for C# consumers of records,exceptions and DU subtypes generated from F# code. [PR #18079](https://github.com/dotnet/fsharp/pull/18079)
* Reduce excess memory usage in TransparentCompiler. [PR #17543](https://github.com/dotnet/fsharp/pull/17543)


### Added
Expand Down
49 changes: 29 additions & 20 deletions src/Compiler/Service/FSharpProjectSnapshot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -747,26 +747,35 @@ and [<Experimental("This FCS API is experimental and subject to change.")>] FSha

FSharpProjectSnapshot.FromOptions(options, getFileSnapshot)

let rec internal snapshotToOptions (projectSnapshot: ProjectSnapshotBase<_>) =
{
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
}
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
}
)

[<Extension>]
type internal Extensions =
Expand Down
163 changes: 129 additions & 34 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -259,44 +259,138 @@ module private TypeCheckingGraphProcessing =
return finalFileResults, state
}

type internal CompilerCaches(sizeFactor: int) =
type CacheSizes =
{
ParseFileKeepStrongly: int
ParseFileKeepWeakly: int
ParseFileWithoutProjectKeepStrongly: int
ParseFileWithoutProjectKeepWeakly: int
ParseAndCheckFileInProjectKeepStrongly: int
ParseAndCheckFileInProjectKeepWeakly: int
ParseAndCheckAllFilesInProjectKeepStrongly: int
ParseAndCheckAllFilesInProjectKeepWeakly: int
ParseAndCheckProjectKeepStrongly: int
ParseAndCheckProjectKeepWeakly: int
FrameworkImportsKeepStrongly: int
FrameworkImportsKeepWeakly: int
BootstrapInfoStaticKeepStrongly: int
BootstrapInfoStaticKeepWeakly: int
BootstrapInfoKeepStrongly: int
BootstrapInfoKeepWeakly: int
TcLastFileKeepStrongly: int
TcLastFileKeepWeakly: int
TcIntermediateKeepStrongly: int
TcIntermediateKeepWeakly: int
DependencyGraphKeepStrongly: int
DependencyGraphKeepWeakly: int
ProjectExtrasKeepStrongly: int
ProjectExtrasKeepWeakly: int
AssemblyDataKeepStrongly: int
AssemblyDataKeepWeakly: int
SemanticClassificationKeepStrongly: int
SemanticClassificationKeepWeakly: int
ItemKeyStoreKeepStrongly: int
ItemKeyStoreKeepWeakly: int
ScriptClosureKeepStrongly: int
ScriptClosureKeepWeakly: int
}

static member Create sizeFactor =

{
ParseFileKeepStrongly = 50 * sizeFactor
ParseFileKeepWeakly = 20 * sizeFactor
ParseFileWithoutProjectKeepStrongly = 5 * sizeFactor
ParseFileWithoutProjectKeepWeakly = 2 * sizeFactor
ParseAndCheckFileInProjectKeepStrongly = sizeFactor
ParseAndCheckFileInProjectKeepWeakly = 2 * sizeFactor
ParseAndCheckAllFilesInProjectKeepStrongly = sizeFactor
ParseAndCheckAllFilesInProjectKeepWeakly = 2 * sizeFactor
ParseAndCheckProjectKeepStrongly = sizeFactor
ParseAndCheckProjectKeepWeakly = 2 * sizeFactor
FrameworkImportsKeepStrongly = sizeFactor
FrameworkImportsKeepWeakly = 2 * sizeFactor
BootstrapInfoStaticKeepStrongly = sizeFactor
BootstrapInfoStaticKeepWeakly = 2 * sizeFactor
BootstrapInfoKeepStrongly = sizeFactor
BootstrapInfoKeepWeakly = 2 * sizeFactor
TcLastFileKeepStrongly = sizeFactor
TcLastFileKeepWeakly = 2 * sizeFactor
TcIntermediateKeepStrongly = 20 * sizeFactor
TcIntermediateKeepWeakly = 20 * sizeFactor
DependencyGraphKeepStrongly = sizeFactor
DependencyGraphKeepWeakly = 2 * sizeFactor
ProjectExtrasKeepStrongly = sizeFactor
ProjectExtrasKeepWeakly = 2 * sizeFactor
AssemblyDataKeepStrongly = sizeFactor
AssemblyDataKeepWeakly = 2 * sizeFactor
SemanticClassificationKeepStrongly = sizeFactor
SemanticClassificationKeepWeakly = 2 * sizeFactor
ItemKeyStoreKeepStrongly = sizeFactor
ItemKeyStoreKeepWeakly = 2 * sizeFactor
ScriptClosureKeepStrongly = sizeFactor
ScriptClosureKeepWeakly = 2 * sizeFactor
}

static member Default =
let sizeFactor = 100
CacheSizes.Create sizeFactor

type internal CompilerCaches(sizeFactor: CacheSizes) =

let sf = sizeFactor
Copy link
Contributor

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.


member _.SizeFactor = sf
member _.CacheSizes = sf

member val ParseFile = AsyncMemoize(keepStrongly = 50 * sf, keepWeakly = 20 * sf, name = "ParseFile")
member val ParseFile = AsyncMemoize(keepStrongly = sf.ParseFileKeepStrongly, keepWeakly = sf.ParseFileKeepWeakly, name = "ParseFile")

member val ParseFileWithoutProject =
AsyncMemoize<string, string, FSharpParseFileResults>(keepStrongly = 5 * sf, keepWeakly = 2 * sf, name = "ParseFileWithoutProject")
AsyncMemoize<string, string, FSharpParseFileResults>(
sf.ParseFileWithoutProjectKeepStrongly,
keepWeakly = sf.ParseFileWithoutProjectKeepWeakly,
name = "ParseFileWithoutProject"
)

member val ParseAndCheckFileInProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckFileInProject")
member val ParseAndCheckFileInProject =
AsyncMemoize(
sf.ParseAndCheckFileInProjectKeepStrongly,
sf.ParseAndCheckFileInProjectKeepWeakly,
name = "ParseAndCheckFileInProject"
)

member val ParseAndCheckAllFilesInProject = AsyncMemoizeDisabled(sf, 2 * sf, name = "ParseAndCheckFullProject")
member val ParseAndCheckAllFilesInProject =
AsyncMemoizeDisabled(
sf.ParseAndCheckAllFilesInProjectKeepStrongly,
sf.ParseAndCheckAllFilesInProjectKeepWeakly,
name = "ParseAndCheckFullProject"
)

member val ParseAndCheckProject = AsyncMemoize(sf, 2 * sf, name = "ParseAndCheckProject")
member val ParseAndCheckProject =
AsyncMemoize(sf.ParseAndCheckProjectKeepStrongly, sf.ParseAndCheckProjectKeepWeakly, name = "ParseAndCheckProject")

member val FrameworkImports = AsyncMemoize(sf, 2 * sf, name = "FrameworkImports")
member val FrameworkImports = AsyncMemoize(sf.FrameworkImportsKeepStrongly, sf.FrameworkImportsKeepWeakly, name = "FrameworkImports")

member val BootstrapInfoStatic = AsyncMemoize(sf, 2 * sf, name = "BootstrapInfoStatic")
member val BootstrapInfoStatic =
AsyncMemoize(sf.BootstrapInfoStaticKeepStrongly, sf.BootstrapInfoStaticKeepWeakly, name = "BootstrapInfoStatic")

member val BootstrapInfo = AsyncMemoize(sf, 2 * sf, name = "BootstrapInfo")
member val BootstrapInfo = AsyncMemoize(sf.BootstrapInfoKeepStrongly, sf.BootstrapInfoKeepWeakly, name = "BootstrapInfo")

member val TcLastFile = AsyncMemoizeDisabled(sf, 2 * sf, name = "TcLastFile")
member val TcLastFile = AsyncMemoizeDisabled(sf.TcLastFileKeepStrongly, sf.TcLastFileKeepWeakly, name = "TcLastFile")

member val TcIntermediate = AsyncMemoize(20 * sf, 20 * sf, name = "TcIntermediate")
member val TcIntermediate = AsyncMemoize(sf.TcIntermediateKeepStrongly, sf.TcIntermediateKeepWeakly, name = "TcIntermediate")

member val DependencyGraph = AsyncMemoize(sf, 2 * sf, name = "DependencyGraph")
member val DependencyGraph = AsyncMemoize(sf.DependencyGraphKeepStrongly, sf.DependencyGraphKeepWeakly, name = "DependencyGraph")

member val ProjectExtras = AsyncMemoizeDisabled(sf, 2 * sf, name = "ProjectExtras")
member val ProjectExtras = AsyncMemoizeDisabled(sf.ProjectExtrasKeepStrongly, sf.ProjectExtrasKeepWeakly, name = "ProjectExtras")

member val AssemblyData = AsyncMemoize(sf, 2 * sf, name = "AssemblyData")
member val AssemblyData = AsyncMemoize(sf.AssemblyDataKeepStrongly, sf.AssemblyDataKeepWeakly, name = "AssemblyData")

member val SemanticClassification = AsyncMemoize(sf, 2 * sf, name = "SemanticClassification")
member val SemanticClassification =
AsyncMemoize(sf.SemanticClassificationKeepStrongly, sf.SemanticClassificationKeepWeakly, name = "SemanticClassification")

member val ItemKeyStore = AsyncMemoize(sf, 2 * sf, name = "ItemKeyStore")
member val ItemKeyStore = AsyncMemoize(sf.ItemKeyStoreKeepStrongly, sf.ItemKeyStoreKeepWeakly, name = "ItemKeyStore")

member val ScriptClosure = AsyncMemoize(sf, 2 * sf, name = "ScriptClosure")
member val ScriptClosure = AsyncMemoize(sf.ScriptClosureKeepStrongly, sf.ScriptClosureKeepWeakly, name = "ScriptClosure")

member this.Clear(projects: Set<FSharpProjectIdentifier>) =
let shouldClear project = projects |> Set.contains project
Expand Down Expand Up @@ -326,7 +420,8 @@ type internal TransparentCompiler
parallelReferenceResolution,
captureIdentifiersWhenParsing,
getSource: (string -> Async<ISourceText option>) option,
useChangeNotifications
useChangeNotifications,
?cacheSizes
) as self =

let documentSource =
Expand All @@ -337,8 +432,10 @@ type internal TransparentCompiler
// Is having just one of these ok?
let lexResourceManager = Lexhelp.LexResourceManager()

let cacheSizes = defaultArg cacheSizes CacheSizes.Default

// Mutable so we can easily clear them by creating a new instance
let mutable caches = CompilerCaches(100)
let mutable caches = CompilerCaches(cacheSizes)

// TODO: do we need this?
//let maxTypeCheckingParallelism = max 1 (Environment.ProcessorCount / 2)
Expand Down Expand Up @@ -1371,8 +1468,8 @@ type internal TransparentCompiler
node,
(fun tcInfo ->

if tcInfo.stateContainsNodes |> Set.contains fileNode then
failwith $"Oops!"
// if tcInfo.stateContainsNodes |> Set.contains fileNode then
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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 😅

// failwith $"Oops!"

//if
// tcInfo.stateContainsNodes
Expand Down Expand Up @@ -1417,14 +1514,8 @@ type internal TransparentCompiler
fileNode,
(fun tcInfo ->

if tcInfo.stateContainsNodes |> Set.contains fileNode then
failwith $"Oops!"

// if
// tcInfo.stateContainsNodes
// |> Set.contains (NodeToTypeCheck.PhysicalFile(index + 1))
// then
// failwith $"Oops!!!"
// if tcInfo.stateContainsNodes |> Set.contains fileNode then
// failwith $"Oops!"

let parsedInput = projectSnapshot.SourceFiles[index].ParsedInput
let prefixPathOpt = None
Expand Down Expand Up @@ -2087,9 +2178,13 @@ type internal TransparentCompiler

member _.Caches = caches

member _.SetCacheSizeFactor(sizeFactor: int) =
if sizeFactor <> caches.SizeFactor then
caches <- CompilerCaches(sizeFactor)
member _.SetCacheSize(cacheSize: CacheSizes) =
if cacheSize <> caches.CacheSizes then
caches <- CompilerCaches(cacheSize)

member x.SetCacheSizeFactor(sizeFactor: int) =
let newCacheSize = CacheSizes.Create sizeFactor
x.SetCacheSize newCacheSize

interface IBackgroundCompiler with

Expand Down Expand Up @@ -2151,7 +2246,7 @@ type internal TransparentCompiler

member _.ClearCaches() : unit =
backgroundCompiler.ClearCaches()
caches <- CompilerCaches(100) // TODO: check
caches <- CompilerCaches(cacheSizes) // TODO: check

member _.DownsizeCaches() : unit = backgroundCompiler.DownsizeCaches()

Expand Down
45 changes: 42 additions & 3 deletions src/Compiler/Service/TransparentCompiler.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,46 @@ type internal Extensions =
fileSnapshots: #ProjectSnapshot.IFileSnapshot list * ?extraKeyFlag: DependencyGraphType ->
ICacheKey<(DependencyGraphType option * byte array), string>

[<Experimental("This FCS type is experimental and will likely change or be removed in the future.")>]
type CacheSizes =
{ ParseFileKeepStrongly: int
ParseFileKeepWeakly: int
ParseFileWithoutProjectKeepStrongly: int
ParseFileWithoutProjectKeepWeakly: int
ParseAndCheckFileInProjectKeepStrongly: int
ParseAndCheckFileInProjectKeepWeakly: int
ParseAndCheckAllFilesInProjectKeepStrongly: int
ParseAndCheckAllFilesInProjectKeepWeakly: int
ParseAndCheckProjectKeepStrongly: int
ParseAndCheckProjectKeepWeakly: int
FrameworkImportsKeepStrongly: int
FrameworkImportsKeepWeakly: int
BootstrapInfoStaticKeepStrongly: int
BootstrapInfoStaticKeepWeakly: int
BootstrapInfoKeepStrongly: int
BootstrapInfoKeepWeakly: int
TcLastFileKeepStrongly: int
TcLastFileKeepWeakly: int
TcIntermediateKeepStrongly: int
TcIntermediateKeepWeakly: int
DependencyGraphKeepStrongly: int
DependencyGraphKeepWeakly: int
ProjectExtrasKeepStrongly: int
ProjectExtrasKeepWeakly: int
AssemblyDataKeepStrongly: int
AssemblyDataKeepWeakly: int
SemanticClassificationKeepStrongly: int
SemanticClassificationKeepWeakly: int
ItemKeyStoreKeepStrongly: int
ItemKeyStoreKeepWeakly: int
ScriptClosureKeepStrongly: int
ScriptClosureKeepWeakly: int }

static member Create: sizeFactor: int -> CacheSizes

type internal CompilerCaches =

new: sizeFactor: int -> CompilerCaches
new: sizeFactor: CacheSizes -> CompilerCaches

member AssemblyData: AsyncMemoize<FSharpProjectIdentifier, (string * string), ProjectAssemblyDataResult>

Expand Down Expand Up @@ -134,7 +171,7 @@ type internal CompilerCaches =
member SemanticClassification:
AsyncMemoize<(string * FSharpProjectIdentifier), string, SemanticClassificationView option>

member SizeFactor: int
member CacheSizes: CacheSizes

member TcIntermediate: AsyncMemoize<(string * FSharpProjectIdentifier), (string * int), TcIntermediate>

Expand All @@ -158,7 +195,8 @@ type internal TransparentCompiler =
parallelReferenceResolution: ParallelReferenceResolution *
captureIdentifiersWhenParsing: bool *
getSource: (string -> Async<ISourceText option>) option *
useChangeNotifications: bool ->
useChangeNotifications: bool *
?cacheSizes: CacheSizes ->
TransparentCompiler

member FindReferencesInFile:
Expand All @@ -177,6 +215,7 @@ type internal TransparentCompiler =
fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * _userOpName: 'a ->
Async<FSharpParseFileResults>

member SetCacheSize: cacheSize: CacheSizes -> unit
member SetCacheSizeFactor: sizeFactor: int -> unit

member Caches: CompilerCaches
Loading
Loading