Skip to content

Commit

Permalink
Reduce excess memory usage in TransparentCompiler (#17543)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheAngryByrd authored Jan 6, 2025
1 parent 2ece164 commit fbdb7cb
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 79 deletions.
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)
* Fix a race condition in file book keeping in the compiler service ([#18008](https://github.com/dotnet/fsharp/pull/18008))
* Fix trimming '%' characters when lowering interpolated string to a concat call [PR #18123](https://github.com/dotnet/fsharp/pull/18123)
* Completion: fix qualified completion in sequence expressions [PR #18111](https://github.com/dotnet/fsharp/pull/18111)
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
170 changes: 125 additions & 45 deletions src/Compiler/Service/TransparentCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -259,44 +259,137 @@ 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
}

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

member _.SizeFactor = sf
type internal CompilerCaches(cacheSizes: CacheSizes) =
let cs = cacheSizes

member val ParseFile = AsyncMemoize(keepStrongly = 50 * sf, keepWeakly = 20 * sf, name = "ParseFile")
member _.CacheSizes = cs

member val ParseFile = AsyncMemoize(keepStrongly = cs.ParseFileKeepStrongly, keepWeakly = cs.ParseFileKeepWeakly, name = "ParseFile")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

let documentSource =
Expand All @@ -337,8 +431,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,17 +1467,6 @@ type internal TransparentCompiler
node,
(fun tcInfo ->

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

//if
// tcInfo.stateContainsNodes
// Signature files don't have to be right above the impl file... if we need this check then
// we need to do it differently
// |> Set.contains (NodeToTypeCheck.ArtificialImplFile(index - 1))
//then
// failwith $"Oops???"

let partialResult, tcState = finisher tcInfo.tcState

let tcEnv, topAttribs, _checkImplFileOpt, ccuSigForFile = partialResult
Expand Down Expand Up @@ -1417,15 +1502,6 @@ 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!!!"

let parsedInput = projectSnapshot.SourceFiles[index].ParsedInput
let prefixPathOpt = None
// Retrieve the type-checked signature information and add it to the TcEnvFromImpls.
Expand Down Expand Up @@ -2087,9 +2163,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 +2231,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: cacheSizes: 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

0 comments on commit fbdb7cb

Please sign in to comment.