Skip to content

Commit 75c3558

Browse files
A few super-minor compiler perf improvements (#17130)
* Minor compiler perf improvements * Override `ToString` on `BuildPhase`. * Cache the delegate passed into `ConcurrentDictionary.GetOrAdd` where possible. See #14582, fsharp/fslang-suggestions#1083, etc. * Name * Update release notes * Fmt * Remove unneeded `StringBuilder` * Start count at 1 * Go back to `GetOrAdd` * I don't think I fully understand why, but I did some rough microbenchmarking, and for some reason the `GetOrAdd` and `Interlocked.Increment` on a ref cell technique is actually something like twice as fast as `AddOrUpdate`. --------- Co-authored-by: Petr <[email protected]>
1 parent b63bd26 commit 75c3558

File tree

8 files changed

+70
-37
lines changed

8 files changed

+70
-37
lines changed

docs/release-notes/.FSharp.Compiler.Service/8.0.400.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@
1919

2020
### Changed
2121

22+
* Minor compiler perf improvements. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130))
2223
* Improve error of Active Pattern case Argument Count Not Match ([PR #16846](https://github.com/dotnet/fsharp/pull/16846))
2324
* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
### Fixed
2+
3+
### Added
4+
5+
### Changed
6+
7+
* Cache delegate in query extensions. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130))

src/Compiler/AbstractIL/il.fs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,22 +90,24 @@ let rec splitNamespaceAux (nm: string) =
9090
| -1 -> [ nm ]
9191
| idx ->
9292
let s1, s2 = splitNameAt nm idx
93-
let s1 = memoizeNamespacePartTable.GetOrAdd(s1, id)
93+
let s1 = memoizeNamespacePartTable.GetOrAdd(s1, s1)
9494
s1 :: splitNamespaceAux s2
9595

96+
// Cache this as a delegate.
97+
let splitNamespaceAuxDelegate = Func<string, string list> splitNamespaceAux
98+
9699
let splitNamespace nm =
97-
memoizeNamespaceTable.GetOrAdd(nm, splitNamespaceAux)
100+
memoizeNamespaceTable.GetOrAdd(nm, splitNamespaceAuxDelegate)
98101

99102
// ++GLOBAL MUTABLE STATE (concurrency-safe)
100103
let memoizeNamespaceArrayTable = ConcurrentDictionary<string, string[]>()
101104

105+
// Cache this as a delegate.
106+
let splitNamespaceToArrayDelegate =
107+
Func<string, string array>(splitNamespace >> Array.ofList)
108+
102109
let splitNamespaceToArray nm =
103-
memoizeNamespaceArrayTable.GetOrAdd(
104-
nm,
105-
fun nm ->
106-
let x = Array.ofList (splitNamespace nm)
107-
x
108-
)
110+
memoizeNamespaceArrayTable.GetOrAdd(nm, splitNamespaceToArrayDelegate)
109111

110112
let splitILTypeName (nm: string) =
111113
match nm.LastIndexOf '.' with
@@ -156,8 +158,12 @@ let splitTypeNameRightAux (nm: string) =
156158
let s1, s2 = splitNameAt nm idx
157159
Some s1, s2
158160

161+
// Cache this as a delegate.
162+
let splitTypeNameRightAuxDelegate =
163+
Func<string, string option * string> splitTypeNameRightAux
164+
159165
let splitTypeNameRight nm =
160-
memoizeNamespaceRightTable.GetOrAdd(nm, splitTypeNameRightAux)
166+
memoizeNamespaceRightTable.GetOrAdd(nm, splitTypeNameRightAuxDelegate)
161167

162168
// --------------------------------------------------------------------
163169
// Ordered lists with a lookup table

src/Compiler/Checking/CheckExpressions.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7442,8 +7442,7 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
74427442

74437443
if List.isEmpty synFillExprs then
74447444
if isString then
7445-
let sb = System.Text.StringBuilder(printfFormatString).Replace("%%", "%")
7446-
let str = mkString g m (sb.ToString())
7445+
let str = mkString g m (printfFormatString.Replace("%%", "%"))
74477446
TcPropagatingExprLeafThenConvert cenv overallTy g.string_ty env (* true *) m (fun () ->
74487447
str, tpenv
74497448
)

src/Compiler/Facilities/DiagnosticsLogger.fs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,20 @@ type BuildPhase =
221221
| Output
222222
| Interactive // An error seen during interactive execution
223223

224+
override this.ToString() =
225+
match this with
226+
| DefaultPhase -> nameof DefaultPhase
227+
| Compile -> nameof Compile
228+
| Parameter -> nameof Parameter
229+
| Parse -> nameof Parse
230+
| TypeCheck -> nameof TypeCheck
231+
| CodeGen -> nameof CodeGen
232+
| Optimize -> nameof Optimize
233+
| IlxGen -> nameof IlxGen
234+
| IlGen -> nameof IlGen
235+
| Output -> nameof Output
236+
| Interactive -> nameof Interactive
237+
224238
/// Literal build phase subcategory strings.
225239
module BuildPhaseSubcategory =
226240
[<Literal>]

src/Compiler/SyntaxTree/PrettyNaming.fs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -389,29 +389,30 @@ let compileCustomOpName =
389389
/// They're typically used more than once so this avoids some CPU and GC overhead.
390390
let compiledOperators = ConcurrentDictionary<_, string> StringComparer.Ordinal
391391

392-
fun opp ->
393-
// Has this operator already been compiled?
394-
compiledOperators.GetOrAdd(
395-
opp,
396-
fun (op: string) ->
397-
let opLength = op.Length
392+
// Cache this as a delegate.
393+
let compiledOperatorsAddDelegate =
394+
Func<string, string>(fun (op: string) ->
395+
let opLength = op.Length
398396

399-
let sb =
400-
StringBuilder(opNamePrefix, opNamePrefix.Length + (opLength * maxOperatorNameLength))
397+
let sb =
398+
StringBuilder(opNamePrefix, opNamePrefix.Length + (opLength * maxOperatorNameLength))
401399

402-
for i = 0 to opLength - 1 do
403-
let c = op[i]
400+
for i = 0 to opLength - 1 do
401+
let c = op[i]
404402

405-
match t2.TryGetValue c with
406-
| true, x -> sb.Append(x) |> ignore
407-
| false, _ -> sb.Append(c) |> ignore
403+
match t2.TryGetValue c with
404+
| true, x -> sb.Append(x) |> ignore
405+
| false, _ -> sb.Append(c) |> ignore
408406

409-
/// The compiled (mangled) operator name.
410-
let opName = sb.ToString()
407+
/// The compiled (mangled) operator name.
408+
let opName = sb.ToString()
411409

412-
// Cache the compiled name so it can be reused.
413-
opName
414-
)
410+
// Cache the compiled name so it can be reused.
411+
opName)
412+
413+
fun opp ->
414+
// Has this operator already been compiled?
415+
compiledOperators.GetOrAdd(opp, compiledOperatorsAddDelegate)
415416

416417
/// Maps the built-in F# operators to their mangled operator names.
417418
let standardOpNames =

src/Compiler/TypedTree/CompilerGlobalState.fs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
module FSharp.Compiler.CompilerGlobalState
66

77
open System
8-
open System.Collections.Generic
98
open System.Collections.Concurrent
109
open System.Threading
1110
open FSharp.Compiler.Syntax.PrettyNaming
@@ -19,15 +18,19 @@ open FSharp.Compiler.Text
1918
/// policy to make all globally-allocated objects concurrency safe in case future versions of the compiler
2019
/// are used to host multiple concurrent instances of compilation.
2120
type NiceNameGenerator() =
22-
let basicNameCounts = ConcurrentDictionary<string,Ref<int>>(max Environment.ProcessorCount 1, 127)
21+
let basicNameCounts = ConcurrentDictionary<string, int ref>(max Environment.ProcessorCount 1, 127)
22+
// Cache this as a delegate.
23+
let basicNameCountsAddDelegate = Func<string, int ref>(fun _ -> ref 0)
2324

24-
member _.FreshCompilerGeneratedName (name, m: range) =
25-
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
26-
let countCell = basicNameCounts.GetOrAdd(basicName,fun k -> ref 0)
25+
member _.FreshCompilerGeneratedNameOfBasicName (basicName, m: range) =
26+
let countCell = basicNameCounts.GetOrAdd(basicName, basicNameCountsAddDelegate)
2727
let count = Interlocked.Increment(countCell)
28-
28+
2929
CompilerGeneratedNameSuffix basicName (string m.StartLine + (match (count-1) with 0 -> "" | n -> "-" + string n))
3030

31+
member this.FreshCompilerGeneratedName (name, m: range) =
32+
this.FreshCompilerGeneratedNameOfBasicName (GetBasicNameOfPossibleCompilerGeneratedName name, m)
33+
3134
/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
3235
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in
3336
/// at the point of first generation.
@@ -42,7 +45,7 @@ type StableNiceNameGenerator() =
4245
member x.GetUniqueCompilerGeneratedName (name, m: range, uniq) =
4346
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
4447
let key = basicName, uniq
45-
niceNames.GetOrAdd(key, fun _ -> innerGenerator.FreshCompilerGeneratedName(name, m))
48+
niceNames.GetOrAdd(key, fun (basicName, _) -> innerGenerator.FreshCompilerGeneratedNameOfBasicName(basicName, m))
4649

4750
type internal CompilerGlobalState () =
4851
/// A global generator of compiler generated names

src/FSharp.Core/QueryExtensions.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ module internal Adapters =
3838

3939
let memoize f =
4040
let d = new ConcurrentDictionary<Type, 'b>(HashIdentity.Structural)
41+
// Cache this as a delegate.
42+
let valueFactory = Func<Type, 'b> f
4143

42-
fun x -> d.GetOrAdd(x, (fun r -> f r))
44+
fun x -> d.GetOrAdd(x, valueFactory)
4345

4446
let isPartiallyImmutableRecord: Type -> bool =
4547
memoize (fun t ->

0 commit comments

Comments
 (0)