From a040e8acfb125252e6405ce1b59cd8d0c6e45594 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Fri, 3 Jul 2026 16:28:53 +0200 Subject: [PATCH 1/3] Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap Both types allocated a dedicated `syncObj = obj()` for their one-time initialisation. These instances are internal and never locked externally, and there are enough of them (one per lazy IL member, per ILTypeDefs / ILMethodDefs, etc.) that the extra bare System.Object adds up to tens of MB on a large project. Lock on `this` instead and drop the field. Measured on a single-file FCS check against a project with ~486 references: bare System.Object instances dropped from ~1,000,000 to ~29,000 (~-22 MB). Co-Authored-By: Claude Opus 4.8 --- src/Compiler/Utilities/illib.fs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Compiler/Utilities/illib.fs b/src/Compiler/Utilities/illib.fs index 87434b07720..0b8d51be0a3 100644 --- a/src/Compiler/Utilities/illib.fs +++ b/src/Compiler/Utilities/illib.fs @@ -15,8 +15,6 @@ open FSharp.Compiler.Caches [] type InterruptibleLazy<'T> private (value, valueFactory: unit -> 'T) = - let syncObj = obj () - [] // TODO nullness - this is boxed to obj because of an attribute targets bug fixed in main, but not yet shipped (needs shipped 8.0.400) let mutable valueFactory: objnull = valueFactory @@ -34,7 +32,7 @@ type InterruptibleLazy<'T> private (value, valueFactory: unit -> 'T) = match valueFactory with | null -> value | _ -> - Monitor.Enter(syncObj) + Monitor.Enter(this) try match valueFactory with @@ -44,7 +42,7 @@ type InterruptibleLazy<'T> private (value, valueFactory: unit -> 'T) = value <- (valueFactory |> unbox 'T>) () valueFactory <- Unchecked.defaultof<_> finally - Monitor.Exit(syncObj) + Monitor.Exit(this) value @@ -151,8 +149,6 @@ module internal PervasiveAutoOpens = [] type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) = - let syncObj = obj () - let mutable arrayStore: (_ array | null) = null let mutable dictStore: (_ | null) = null @@ -162,7 +158,7 @@ type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) = match arrayStore with | NonNull value -> value | _ -> - Monitor.Enter(syncObj) + Monitor.Enter(this) try match arrayStore with @@ -174,14 +170,14 @@ type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) = func <- Unchecked.defaultof<_> freshArray finally - Monitor.Exit(syncObj) + Monitor.Exit(this) member this.GetDictionary() = match dictStore with | NonNull value -> value | _ -> let array = this.GetArray() - Monitor.Enter(syncObj) + Monitor.Enter(this) try match dictStore with @@ -191,7 +187,7 @@ type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) = dictStore <- dict dict finally - Monitor.Exit(syncObj) + Monitor.Exit(this) abstract CreateDictionary: 'T[] -> IDictionary<'TDictKey, 'TDictValue> From cc2d1c2872bbb4e6ec8e2349f91123530f165f62 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Mon, 10 Aug 2026 12:11:11 +0200 Subject: [PATCH 2/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index ab65df29a6e..eb41774c51e 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -130,6 +130,7 @@ * Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934)) * Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873)) * Fix #5795: Allow attributes defined in a `module rec` / `namespace rec` scope to be used on union cases, record fields, and generic type parameters of types in the same recursive scope. ([Issue #5795](https://github.com/dotnet/fsharp/issues/5795), [PR #19744](https://github.com/dotnet/fsharp/pull/19744)) +* Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap (PR [dotnet/fsharp#20088](https://github.com/dotnet/fsharp/pull/20088)) ### Added From eed9571a4017ef1f2ec6abfc8eb07220da6bc514 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Mon, 10 Aug 2026 12:27:59 +0200 Subject: [PATCH 3/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index eb41774c51e..879be45c437 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -130,7 +130,7 @@ * Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934)) * Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873)) * Fix #5795: Allow attributes defined in a `module rec` / `namespace rec` scope to be used on union cases, record fields, and generic type parameters of types in the same recursive scope. ([Issue #5795](https://github.com/dotnet/fsharp/issues/5795), [PR #19744](https://github.com/dotnet/fsharp/pull/19744)) -* Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap (PR [dotnet/fsharp#20088](https://github.com/dotnet/fsharp/pull/20088)) +* Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap (PR [#20088](https://github.com/dotnet/fsharp/pull/20088)) ### Added