Skip to content

Commit

Permalink
Minor compiler perf improvements
Browse files Browse the repository at this point in the history
* Override `ToString` on `BuildPhase`.

* Cache the delegate passed into `ConcurrentDictionary.GetOrAdd` where
  possible. See dotnet#14582, fsharp/fslang-suggestions#1083, etc.
  • Loading branch information
brianrourkeboll committed May 9, 2024
1 parent fec36c9 commit 769d622
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 41 deletions.
22 changes: 13 additions & 9 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,23 @@ let rec splitNamespaceAux (nm: string) =
| -1 -> [ nm ]
| idx ->
let s1, s2 = splitNameAt nm idx
let s1 = memoizeNamespacePartTable.GetOrAdd(s1, id)
let s1 = memoizeNamespacePartTable.GetOrAdd(s1, s1)
s1 :: splitNamespaceAux s2

// Cache this as a delegate.
let splitNamespaceAuxDelegate = Func<string, string list> splitNamespaceAux

let splitNamespace nm =
memoizeNamespaceTable.GetOrAdd(nm, splitNamespaceAux)
memoizeNamespaceTable.GetOrAdd(nm, splitNamespaceAuxDelegate)

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

// Cache this as a delegate.
let splitNamespaceToArrayDelegate = Func<string, string array>(splitNamespace >> Array.ofList)

let splitNamespaceToArray nm =
memoizeNamespaceArrayTable.GetOrAdd(
nm,
fun nm ->
let x = Array.ofList (splitNamespace nm)
x
)
memoizeNamespaceArrayTable.GetOrAdd(nm, splitNamespaceToArrayDelegate)

let splitILTypeName (nm: string) =
match nm.LastIndexOf '.' with
Expand Down Expand Up @@ -156,8 +157,11 @@ let splitTypeNameRightAux (nm: string) =
let s1, s2 = splitNameAt nm idx
Some s1, s2

// Cache this as a delegate.
let splitTypeNameRightDelegate = Func<string, string option * string> splitTypeNameRightAux

let splitTypeNameRight nm =
memoizeNamespaceRightTable.GetOrAdd(nm, splitTypeNameRightAux)
memoizeNamespaceRightTable.GetOrAdd(nm, splitTypeNameRightDelegate)

// --------------------------------------------------------------------
// Ordered lists with a lookup table
Expand Down
14 changes: 14 additions & 0 deletions src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,20 @@ type BuildPhase =
| Output
| Interactive // An error seen during interactive execution

override this.ToString() =
match this with
| DefaultPhase -> nameof DefaultPhase
| Compile -> nameof Compile
| Parameter -> nameof Parameter
| Parse -> nameof Parse
| TypeCheck -> nameof TypeCheck
| CodeGen -> nameof CodeGen
| Optimize -> nameof Optimize
| IlxGen -> nameof IlxGen
| IlGen -> nameof IlGen
| Output -> nameof Output
| Interactive -> nameof Interactive

/// Literal build phase subcategory strings.
module BuildPhaseSubcategory =
[<Literal>]
Expand Down
43 changes: 22 additions & 21 deletions src/Compiler/SyntaxTree/PrettyNaming.fs
Original file line number Diff line number Diff line change
Expand Up @@ -389,29 +389,30 @@ let compileCustomOpName =
/// They're typically used more than once so this avoids some CPU and GC overhead.
let compiledOperators = ConcurrentDictionary<_, string> StringComparer.Ordinal

// Cache this as a delegate.
let compiledOperatorsAddDelegate =
Func<string, string>(fun (op: string) ->
let opLength = op.Length

let sb =
StringBuilder(opNamePrefix, opNamePrefix.Length + (opLength * maxOperatorNameLength))

for i = 0 to opLength - 1 do
let c = op[i]

match t2.TryGetValue c with
| true, x -> sb.Append(x) |> ignore
| false, _ -> sb.Append(c) |> ignore

/// The compiled (mangled) operator name.
let opName = sb.ToString()

// Cache the compiled name so it can be reused.
opName)

fun opp ->
// Has this operator already been compiled?
compiledOperators.GetOrAdd(
opp,
fun (op: string) ->
let opLength = op.Length

let sb =
StringBuilder(opNamePrefix, opNamePrefix.Length + (opLength * maxOperatorNameLength))

for i = 0 to opLength - 1 do
let c = op[i]

match t2.TryGetValue c with
| true, x -> sb.Append(x) |> ignore
| false, _ -> sb.Append(c) |> ignore

/// The compiled (mangled) operator name.
let opName = sb.ToString()

// Cache the compiled name so it can be reused.
opName
)
compiledOperators.GetOrAdd(opp, compiledOperatorsAddDelegate)

/// Maps the built-in F# operators to their mangled operator names.
let standardOpNames =
Expand Down
21 changes: 11 additions & 10 deletions src/Compiler/TypedTree/CompilerGlobalState.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
module FSharp.Compiler.CompilerGlobalState

open System
open System.Collections.Generic
open System.Collections.Concurrent
open System.Threading
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.Text

Expand All @@ -19,14 +17,17 @@ open FSharp.Compiler.Text
/// policy to make all globally-allocated objects concurrency safe in case future versions of the compiler
/// are used to host multiple concurrent instances of compilation.
type NiceNameGenerator() =
let basicNameCounts = ConcurrentDictionary<string,Ref<int>>(max Environment.ProcessorCount 1, 127)
let basicNameCounts = ConcurrentDictionary<string, int>(max Environment.ProcessorCount 1, 127)
// Cache this as a delegate.
let basicNameCountsUpdateDelegate = Func<string, int, int>(fun _k count -> count + 1)

member _.FreshCompilerGeneratedName (name, m: range) =
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
let countCell = basicNameCounts.GetOrAdd(basicName,fun k -> ref 0)
let count = Interlocked.Increment(countCell)

CompilerGeneratedNameSuffix basicName (string m.StartLine + (match (count-1) with 0 -> "" | n -> "-" + string n))
member _.FreshCompilerGeneratedNameOfBasicName (basicName, m: range) =
let count = basicNameCounts.AddOrUpdate(basicName, 0, basicNameCountsUpdateDelegate)
let suffix = match count with 0 -> string m.StartLine | n -> string m.StartLine + "-" + string n
CompilerGeneratedNameSuffix basicName suffix

member this.FreshCompilerGeneratedName (name, m: range) =
this.FreshCompilerGeneratedNameOfBasicName (GetBasicNameOfPossibleCompilerGeneratedName name, m)

/// Generates compiler-generated names marked up with a source code location, but if given the same unique value then
/// return precisely the same name. Each name generated also includes the StartLine number of the range passed in
Expand All @@ -42,7 +43,7 @@ type StableNiceNameGenerator() =
member x.GetUniqueCompilerGeneratedName (name, m: range, uniq) =
let basicName = GetBasicNameOfPossibleCompilerGeneratedName name
let key = basicName, uniq
niceNames.GetOrAdd(key, fun _ -> innerGenerator.FreshCompilerGeneratedName(name, m))
niceNames.GetOrAdd(key, fun (basicName, _) -> innerGenerator.FreshCompilerGeneratedNameOfBasicName(basicName, m))

type internal CompilerGlobalState () =
/// A global generator of compiler generated names
Expand Down
4 changes: 3 additions & 1 deletion src/FSharp.Core/QueryExtensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ module internal Adapters =

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

fun x -> d.GetOrAdd(x, (fun r -> f r))
fun x -> d.GetOrAdd(x, valueFactory)

let isPartiallyImmutableRecord: Type -> bool =
memoize (fun t ->
Expand Down

0 comments on commit 769d622

Please sign in to comment.