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
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
Loading