diff --git a/VisualFSharp.sln b/VisualFSharp.sln
index a51b52cc044..92927a4af56 100644
--- a/VisualFSharp.sln
+++ b/VisualFSharp.sln
@@ -170,7 +170,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VisualFSharpDebug", "vsinte
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{DFB6ADD7-3149-43D9-AFA0-FC4A818B472B}"
EndProject
-Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "CompilerServiceBenchmarks", "tests\benchmarks\CompilerServiceBenchmarks\CompilerServiceBenchmarks.fsproj", "{564E7DC5-11CB-4FCF-ABDD-23AD93AF3A61}"
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Compiler.Benchmarks", "tests\benchmarks\CompilerServiceBenchmarks\FSharp.Compiler.Benchmarks.fsproj", "{564E7DC5-11CB-4FCF-ABDD-23AD93AF3A61}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroPerfCSharp", "tests\benchmarks\MicroPerf\CS\MicroPerfCSharp.csproj", "{208E36EE-665C-42D2-B767-C6DB03C4FEB2}"
EndProject
diff --git a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj
index 6f40101419c..ab1c7adefad 100644
--- a/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj
+++ b/src/fsharp/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj
@@ -74,7 +74,7 @@
-
+
diff --git a/tests/benchmarks/CompilerServiceBenchmarks/Benchmarks.fs b/tests/benchmarks/CompilerServiceBenchmarks/Benchmarks.fs
new file mode 100644
index 00000000000..41437eb015e
--- /dev/null
+++ b/tests/benchmarks/CompilerServiceBenchmarks/Benchmarks.fs
@@ -0,0 +1,381 @@
+namespace FSharp.Compiler.Benchmarks
+
+open System
+open System.IO
+open System.Text
+open System.Threading.Tasks
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Compiler.Diagnostics
+open FSharp.Compiler.EditorServices
+open FSharp.Compiler.Text
+open FSharp.Compiler.AbstractIL.IL
+open FSharp.Compiler.AbstractIL.ILBinaryReader
+open BenchmarkDotNet.Attributes
+open BenchmarkDotNet.Running
+open FSharp.Compiler.Benchmarks
+
+[]
+module BenchmarkHelpers =
+
+ type Async with
+ static member RunImmediate (computation: Async<'T>, ?cancellationToken ) =
+ let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken
+ let ts = TaskCompletionSource<'T>()
+ let task = ts.Task
+ Async.StartWithContinuations(
+ computation,
+ (fun k -> ts.SetResult k),
+ (fun exn -> ts.SetException exn),
+ (fun _ -> ts.SetCanceled()),
+ cancellationToken)
+ task.Result
+
+ let createProject name referencedProjects =
+ let tmpPath = Path.GetTempPath()
+ let file = Path.Combine(tmpPath, Path.ChangeExtension(name, ".fs"))
+ {
+ ProjectFileName = Path.Combine(tmpPath, Path.ChangeExtension(name, ".dll"))
+ ProjectId = None
+ SourceFiles = [|file|]
+ OtherOptions =
+ Array.append [|"--optimize+"; "--target:library" |] (referencedProjects |> Array.ofList |> Array.map (fun x -> "-r:" + x.ProjectFileName))
+ ReferencedProjects =
+ referencedProjects
+ |> List.map (fun x -> FSharpReferencedProject.CreateFSharp (x.ProjectFileName, x))
+ |> Array.ofList
+ IsIncompleteTypeCheckEnvironment = false
+ UseScriptResolutionRules = false
+ LoadTime = DateTime()
+ UnresolvedReferences = None
+ OriginalLoadReferences = []
+ Stamp = Some 0L (* set the stamp to 0L on each run so we don't evaluate the whole project again *)
+ }
+
+ let generateSourceCode moduleName =
+ sprintf """
+module Benchmark.%s
+
+type %s =
+
+ val X : int
+
+ val Y : int
+
+ val Z : int
+
+let function%s (x: %s) =
+ let x = 1
+ let y = 2
+ let z = x + y
+ z""" moduleName moduleName moduleName moduleName
+
+ let decentlySizedStandAloneFile = File.ReadAllText(Path.Combine(__SOURCE_DIRECTORY__, "decentlySizedStandAloneFile.fsx"))
+
+[]
+type TypeCheckingBenchmark1() =
+ let mutable checkerOpt = None
+ let mutable assembliesOpt = None
+ let mutable testFileOpt = None
+
+ let parsingOptions =
+ {
+ SourceFiles = [|"decentlySizedStandAloneFile.fsx"|]
+ ConditionalCompilationDefines = []
+ ErrorSeverityOptions = FSharpDiagnosticOptions.Default
+ IsInteractive = false
+ LightSyntax = None
+ CompilingFsLib = false
+ IsExe = false
+ }
+
+ []
+ member __.Setup() =
+ match checkerOpt with
+ | None -> checkerOpt <- Some(FSharpChecker.Create(projectCacheSize = 200))
+ | _ -> ()
+
+ match assembliesOpt with
+ | None ->
+ assembliesOpt <-
+ System.AppDomain.CurrentDomain.GetAssemblies()
+ |> Array.map (fun x -> (x.Location))
+ |> Some
+
+ | _ -> ()
+
+ match testFileOpt with
+ | None ->
+ let options, _ =
+ checkerOpt.Value.GetProjectOptionsFromScript("decentlySizedStandAloneFile.fsx", SourceText.ofString decentlySizedStandAloneFile)
+ |> Async.RunImmediate
+ testFileOpt <- Some options
+ | _ -> ()
+
+ []
+ member __.Run() =
+ match checkerOpt, testFileOpt with
+ | None, _ -> failwith "no checker"
+ | _, None -> failwith "no test file"
+ | Some(checker), Some(options) ->
+ let _, result =
+ checker.ParseAndCheckFileInProject("decentlySizedStandAloneFile.fsx", 0, SourceText.ofString decentlySizedStandAloneFile, options)
+ |> Async.RunImmediate
+ match result with
+ | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
+ | FSharpCheckFileAnswer.Succeeded results ->
+ if results.Diagnostics.Length > 0 then failwithf "had errors: %A" results.Diagnostics
+
+ []
+ member __.Cleanup() =
+ match checkerOpt with
+ | None -> failwith "no checker"
+ | Some(checker) ->
+ checker.InvalidateAll()
+ checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
+ ClearAllILModuleReaderCache()
+
+
+[]
+type CompilerService() =
+ let mutable checkerOpt = None
+ let mutable sourceOpt = None
+ let mutable assembliesOpt = None
+ let mutable decentlySizedStandAloneFileCheckResultOpt = None
+
+ let parsingOptions =
+ {
+ SourceFiles = [|"CheckExpressions.fs"|]
+ ConditionalCompilationDefines = []
+ ErrorSeverityOptions = FSharpDiagnosticOptions.Default
+ IsInteractive = false
+ LightSyntax = None
+ CompilingFsLib = false
+ IsExe = false
+ }
+
+ let readerOptions =
+ {
+ pdbDirPath = None
+ reduceMemoryUsage = ReduceMemoryFlag.No
+ metadataOnly = MetadataOnlyFlag.Yes
+ tryGetMetadataSnapshot = fun _ -> None
+ }
+
+ []
+ member __.Setup() =
+ match checkerOpt with
+ | None -> checkerOpt <- Some(FSharpChecker.Create(projectCacheSize = 200))
+ | _ -> ()
+
+ match sourceOpt with
+ | None ->
+ sourceOpt <- Some <| FSharpSourceText.From(File.OpenRead("""..\..\..\..\..\..\..\..\..\src\fsharp\CheckExpressions.fs"""), Encoding.Default, FSharpSourceHashAlgorithm.Sha1, true)
+ | _ -> ()
+
+ match assembliesOpt with
+ | None ->
+ assembliesOpt <-
+ System.AppDomain.CurrentDomain.GetAssemblies()
+ |> Array.map (fun x -> (x.Location))
+ |> Some
+
+ | _ -> ()
+
+ match decentlySizedStandAloneFileCheckResultOpt with
+ | None ->
+ let options, _ =
+ checkerOpt.Value.GetProjectOptionsFromScript("decentlySizedStandAloneFile.fsx", SourceText.ofString decentlySizedStandAloneFile)
+ |> Async.RunImmediate
+ let _, checkResult =
+ checkerOpt.Value.ParseAndCheckFileInProject("decentlySizedStandAloneFile.fsx", 0, SourceText.ofString decentlySizedStandAloneFile, options)
+ |> Async.RunImmediate
+ decentlySizedStandAloneFileCheckResultOpt <- Some checkResult
+ | _ -> ()
+
+ []
+ member __.ParsingTypeCheckerFs() =
+ match checkerOpt, sourceOpt with
+ | None, _ -> failwith "no checker"
+ | _, None -> failwith "no source"
+ | Some(checker), Some(source) ->
+ let results = checker.ParseFile("CheckExpressions.fs", source.ToFSharpSourceText(), parsingOptions) |> Async.RunImmediate
+ if results.ParseHadErrors then failwithf "parse had errors: %A" results.Diagnostics
+
+ []
+ member __.ParsingTypeCheckerFsSetup() =
+ match checkerOpt with
+ | None -> failwith "no checker"
+ | Some(checker) ->
+ checker.InvalidateAll()
+ checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
+ checker.ParseFile("dummy.fs", SourceText.ofString "dummy", parsingOptions) |> Async.RunImmediate |> ignore
+ ClearAllILModuleReaderCache()
+
+ []
+ member __.ILReading() =
+ match assembliesOpt with
+ | None -> failwith "no assemblies"
+ | Some(assemblies) ->
+ // We try to read most of everything in the assembly that matter, mainly types with their properties, methods, and fields.
+ // CustomAttrs and SecurityDecls are lazy until you call them, so we call them here for benchmarking.
+ assemblies
+ |> Array.iter (fun (fileName) ->
+ let reader = OpenILModuleReader fileName readerOptions
+
+ let ilModuleDef = reader.ILModuleDef
+
+ let ilAssemblyManifest = ilModuleDef.Manifest.Value
+
+ ilAssemblyManifest.CustomAttrs |> ignore
+ ilAssemblyManifest.SecurityDecls |> ignore
+ ilAssemblyManifest.ExportedTypes.AsList
+ |> List.iter (fun x ->
+ x.CustomAttrs |> ignore
+ )
+
+ ilModuleDef.CustomAttrs |> ignore
+ ilModuleDef.TypeDefs.AsArray
+ |> Array.iter (fun ilTypeDef ->
+ ilTypeDef.CustomAttrs |> ignore
+ ilTypeDef.SecurityDecls |> ignore
+
+ ilTypeDef.Methods.AsArray
+ |> Array.iter (fun ilMethodDef ->
+ ilMethodDef.CustomAttrs |> ignore
+ ilMethodDef.SecurityDecls |> ignore
+ )
+
+ ilTypeDef.Fields.AsList
+ |> List.iter (fun ilFieldDef ->
+ ilFieldDef.CustomAttrs |> ignore
+ )
+
+ ilTypeDef.Properties.AsList
+ |> List.iter (fun ilPropertyDef ->
+ ilPropertyDef.CustomAttrs |> ignore
+ )
+ )
+ )
+
+ []
+ member __.ILReadingSetup() =
+ // With caching, performance increases an order of magnitude when re-reading an ILModuleReader.
+ // Clear it for benchmarking.
+ ClearAllILModuleReaderCache()
+
+ member val TypeCheckFileWith100ReferencedProjectsOptions =
+ createProject "MainProject"
+ [ for i = 1 to 100 do
+ yield
+ createProject ("ReferencedProject" + string i) []
+ ]
+
+ member this.TypeCheckFileWith100ReferencedProjectsRun() =
+ let options = this.TypeCheckFileWith100ReferencedProjectsOptions
+ let file = options.SourceFiles.[0]
+
+ match checkerOpt with
+ | None -> failwith "no checker"
+ | Some checker ->
+ let parseResult, checkResult =
+ checker.ParseAndCheckFileInProject(file, 0, SourceText.ofString (File.ReadAllText(file)), options)
+ |> Async.RunImmediate
+
+ if parseResult.Diagnostics.Length > 0 then
+ failwithf "%A" parseResult.Diagnostics
+
+ match checkResult with
+ | FSharpCheckFileAnswer.Aborted -> failwith "aborted"
+ | FSharpCheckFileAnswer.Succeeded checkFileResult ->
+
+ if checkFileResult.Diagnostics.Length > 0 then
+ failwithf "%A" checkFileResult.Diagnostics
+
+ []
+ member this.TypeCheckFileWith100ReferencedProjectsSetup() =
+ this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
+ |> Seq.iter (fun file ->
+ File.WriteAllText(file, generateSourceCode (Path.GetFileNameWithoutExtension(file)))
+ )
+
+ this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
+ |> Seq.iter (function
+ | FSharpReferencedProject.FSharpReference(_, referencedProjectOptions) ->
+ referencedProjectOptions.SourceFiles
+ |> Seq.iter (fun file ->
+ File.WriteAllText(file, generateSourceCode (Path.GetFileNameWithoutExtension(file)))
+ )
+ | _ -> ()
+ )
+
+ this.TypeCheckFileWith100ReferencedProjectsRun()
+
+ []
+ member this.TypeCheckFileWith100ReferencedProjects() =
+ // Because the checker's projectcachesize is set to 200, this should be fast.
+ // If set to 3, it will be almost as slow as re-evaluating all project and it's projects references on setup; this could be a bug or not what we want.
+ this.TypeCheckFileWith100ReferencedProjectsRun()
+
+ member val TypeCheckFileWithNoReferencesOptions = createProject "MainProject" []
+
+ []
+ member this.TypeCheckFileWith100ReferencedProjectsCleanup() =
+ this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
+ |> Seq.iter (fun file ->
+ try File.Delete(file) with | _ -> ()
+ )
+
+ this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
+ |> Seq.iter (function
+ | FSharpReferencedProject.FSharpReference(_, referencedProjectOptions) ->
+ referencedProjectOptions.SourceFiles
+ |> Seq.iter (fun file ->
+ try File.Delete(file) with | _ -> ()
+ )
+ | _ -> ()
+ )
+
+ match checkerOpt with
+ | None -> failwith "no checker"
+ | Some(checker) ->
+ checker.InvalidateAll()
+ checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
+ ClearAllILModuleReaderCache()
+
+ []
+ member this.SimplifyNames() =
+ match decentlySizedStandAloneFileCheckResultOpt with
+ | Some checkResult ->
+ match checkResult with
+ | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
+ | FSharpCheckFileAnswer.Succeeded results ->
+ let sourceLines = decentlySizedStandAloneFile.Split ([|"\r\n"; "\n"; "\r"|], StringSplitOptions.None)
+ let ranges = SimplifyNames.getSimplifiableNames(results, fun lineNum -> sourceLines.[Line.toZ lineNum]) |> Async.RunImmediate
+ ignore ranges
+ ()
+ | _ -> failwith "oopsie"
+
+ []
+ member this.UnusedOpens() =
+ match decentlySizedStandAloneFileCheckResultOpt with
+ | Some checkResult ->
+ match checkResult with
+ | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
+ | FSharpCheckFileAnswer.Succeeded results ->
+ let sourceLines = decentlySizedStandAloneFile.Split ([|"\r\n"; "\n"; "\r"|], StringSplitOptions.None)
+ let decls = UnusedOpens.getUnusedOpens(results, fun lineNum -> sourceLines.[Line.toZ lineNum]) |> Async.RunImmediate
+ ignore decls
+ ()
+ | _ -> failwith "oopsie"
+
+ []
+ member this.UnusedDeclarations() =
+ match decentlySizedStandAloneFileCheckResultOpt with
+ | Some checkResult ->
+ match checkResult with
+ | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
+ | FSharpCheckFileAnswer.Succeeded results ->
+ let decls = UnusedDeclarations.getUnusedDeclarations(results, true) |> Async.RunImmediate
+ ignore decls // should be 16
+ ()
+ | _ -> failwith "oopsie"
\ No newline at end of file
diff --git a/tests/benchmarks/CompilerServiceBenchmarks/CompilerServiceBenchmarks.fsproj b/tests/benchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj
similarity index 91%
rename from tests/benchmarks/CompilerServiceBenchmarks/CompilerServiceBenchmarks.fsproj
rename to tests/benchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj
index 27336d1c50e..68b175fc21a 100644
--- a/tests/benchmarks/CompilerServiceBenchmarks/CompilerServiceBenchmarks.fsproj
+++ b/tests/benchmarks/CompilerServiceBenchmarks/FSharp.Compiler.Benchmarks.fsproj
@@ -8,11 +8,11 @@
+
+
-
-
diff --git a/tests/benchmarks/CompilerServiceBenchmarks/Helpers.fs b/tests/benchmarks/CompilerServiceBenchmarks/Helpers.fs
new file mode 100644
index 00000000000..21952786f1c
--- /dev/null
+++ b/tests/benchmarks/CompilerServiceBenchmarks/Helpers.fs
@@ -0,0 +1,116 @@
+[]
+module internal FSharp.Compiler.Benchmarks.Helpers
+
+open System
+open System.IO
+open System.Threading.Tasks
+open Microsoft.CodeAnalysis.Text
+open FSharp.Compiler.Text
+open FSharp.Compiler.CodeAnalysis
+
+module private SourceText =
+
+ open System.Runtime.CompilerServices
+
+ let weakTable = ConditionalWeakTable()
+
+ let create (sourceText: SourceText) =
+
+ let sourceText =
+ { new ISourceText with
+
+ member __.Item with get index = sourceText.[index]
+
+ member __.GetLineString(lineIndex) =
+ sourceText.Lines.[lineIndex].ToString()
+
+ member __.GetLineCount() =
+ sourceText.Lines.Count
+
+ member __.GetLastCharacterPosition() =
+ if sourceText.Lines.Count > 0 then
+ (sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length)
+ else
+ (0, 0)
+
+ member __.GetSubTextString(start, length) =
+ sourceText.GetSubText(TextSpan(start, length)).ToString()
+
+ member __.SubTextEquals(target, startIndex) =
+ if startIndex < 0 || startIndex >= sourceText.Length then
+ raise (ArgumentOutOfRangeException("startIndex"))
+
+ if String.IsNullOrEmpty(target) then
+ raise (ArgumentException("Target is null or empty.", "target"))
+
+ let lastIndex = startIndex + target.Length
+ if lastIndex <= startIndex || lastIndex >= sourceText.Length then
+ raise (ArgumentException("Target is too big.", "target"))
+
+ let mutable finished = false
+ let mutable didEqual = true
+ let mutable i = 0
+ while not finished && i < target.Length do
+ if target.[i] <> sourceText.[startIndex + i] then
+ didEqual <- false
+ finished <- true // bail out early
+ else
+ i <- i + 1
+
+ didEqual
+
+ member __.ContentEquals(sourceText) =
+ match sourceText with
+ | :? SourceText as sourceText -> sourceText.ContentEquals(sourceText)
+ | _ -> false
+
+ member __.Length = sourceText.Length
+
+ member __.CopyTo(sourceIndex, destination, destinationIndex, count) =
+ sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
+ }
+
+ sourceText
+
+type SourceText with
+
+ member this.ToFSharpSourceText() =
+ SourceText.weakTable.GetValue(this, Runtime.CompilerServices.ConditionalWeakTable<_,_>.CreateValueCallback(SourceText.create))
+
+type FSharpSourceText = SourceText
+type FSharpSourceHashAlgorithm = SourceHashAlgorithm
+
+type Async with
+ static member RunImmediate (computation: Async<'T>, ?cancellationToken ) =
+ let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken
+ let ts = TaskCompletionSource<'T>()
+ let task = ts.Task
+ Async.StartWithContinuations(
+ computation,
+ (fun k -> ts.SetResult k),
+ (fun exn -> ts.SetException exn),
+ (fun _ -> ts.SetCanceled()),
+ cancellationToken)
+ task.Result
+
+let CreateProject name referencedProjects =
+ let tmpPath = Path.GetTempPath()
+ let file = Path.Combine(tmpPath, Path.ChangeExtension(name, ".fs"))
+ {
+ ProjectFileName = Path.Combine(tmpPath, Path.ChangeExtension(name, ".dll"))
+ ProjectId = None
+ SourceFiles = [|file|]
+ OtherOptions =
+ Array.append [|"--optimize+"; "--target:library" |] (referencedProjects |> Array.ofList |> Array.map (fun x -> "-r:" + x.ProjectFileName))
+ ReferencedProjects =
+ referencedProjects
+ |> List.map (fun x -> FSharpReferencedProject.CreateFSharp (x.ProjectFileName, x))
+ |> Array.ofList
+ IsIncompleteTypeCheckEnvironment = false
+ UseScriptResolutionRules = false
+ LoadTime = DateTime()
+ UnresolvedReferences = None
+ OriginalLoadReferences = []
+ Stamp = Some 0L (* set the stamp to 0L on each run so we don't evaluate the whole project again *)
+ }
+
diff --git a/tests/benchmarks/CompilerServiceBenchmarks/Program.fs b/tests/benchmarks/CompilerServiceBenchmarks/Program.fs
index ced6ad6fcd9..d624def2d8c 100644
--- a/tests/benchmarks/CompilerServiceBenchmarks/Program.fs
+++ b/tests/benchmarks/CompilerServiceBenchmarks/Program.fs
@@ -1,389 +1,8 @@
-open System
-open System.IO
-open System.Text
-open System.Threading.Tasks
-open FSharp.Compiler.CodeAnalysis
-open FSharp.Compiler.Diagnostics
-open FSharp.Compiler.EditorServices
-open FSharp.Compiler.Text
-open FSharp.Compiler.AbstractIL.IL
-open FSharp.Compiler.AbstractIL.ILBinaryReader
-open Microsoft.CodeAnalysis.Text
-open BenchmarkDotNet.Attributes
-open BenchmarkDotNet.Running
-
-module private SourceText =
-
- open System.Runtime.CompilerServices
-
- let weakTable = ConditionalWeakTable()
-
- let create (sourceText: SourceText) =
-
- let sourceText =
- { new ISourceText with
-
- member __.Item with get index = sourceText.[index]
-
- member __.GetLineString(lineIndex) =
- sourceText.Lines.[lineIndex].ToString()
-
- member __.GetLineCount() =
- sourceText.Lines.Count
-
- member __.GetLastCharacterPosition() =
- if sourceText.Lines.Count > 0 then
- (sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length)
- else
- (0, 0)
-
- member __.GetSubTextString(start, length) =
- sourceText.GetSubText(TextSpan(start, length)).ToString()
-
- member __.SubTextEquals(target, startIndex) =
- if startIndex < 0 || startIndex >= sourceText.Length then
- raise (ArgumentOutOfRangeException("startIndex"))
-
- if String.IsNullOrEmpty(target) then
- raise (ArgumentException("Target is null or empty.", "target"))
-
- let lastIndex = startIndex + target.Length
- if lastIndex <= startIndex || lastIndex >= sourceText.Length then
- raise (ArgumentException("Target is too big.", "target"))
-
- let mutable finished = false
- let mutable didEqual = true
- let mutable i = 0
- while not finished && i < target.Length do
- if target.[i] <> sourceText.[startIndex + i] then
- didEqual <- false
- finished <- true // bail out early
- else
- i <- i + 1
-
- didEqual
-
- member __.ContentEquals(sourceText) =
- match sourceText with
- | :? SourceText as sourceText -> sourceText.ContentEquals(sourceText)
- | _ -> false
-
- member __.Length = sourceText.Length
-
- member __.CopyTo(sourceIndex, destination, destinationIndex, count) =
- sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
- }
-
- sourceText
-
-type SourceText with
-
- member this.ToFSharpSourceText() =
- SourceText.weakTable.GetValue(this, Runtime.CompilerServices.ConditionalWeakTable<_,_>.CreateValueCallback(SourceText.create))
-
-[]
-module Helpers =
-
- type Async with
- static member RunImmediate (computation: Async<'T>, ?cancellationToken ) =
- let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken
- let ts = TaskCompletionSource<'T>()
- let task = ts.Task
- Async.StartWithContinuations(
- computation,
- (fun k -> ts.SetResult k),
- (fun exn -> ts.SetException exn),
- (fun _ -> ts.SetCanceled()),
- cancellationToken)
- task.Result
-
- let createProject name referencedProjects =
- let tmpPath = Path.GetTempPath()
- let file = Path.Combine(tmpPath, Path.ChangeExtension(name, ".fs"))
- {
- ProjectFileName = Path.Combine(tmpPath, Path.ChangeExtension(name, ".dll"))
- ProjectId = None
- SourceFiles = [|file|]
- OtherOptions =
- Array.append [|"--optimize+"; "--target:library" |] (referencedProjects |> Array.ofList |> Array.map (fun x -> "-r:" + x.ProjectFileName))
- ReferencedProjects =
- referencedProjects
- |> List.map (fun x -> FSharpReferencedProject.CreateFSharp (x.ProjectFileName, x))
- |> Array.ofList
- IsIncompleteTypeCheckEnvironment = false
- UseScriptResolutionRules = false
- LoadTime = DateTime()
- UnresolvedReferences = None
- OriginalLoadReferences = []
- Stamp = Some 0L (* set the stamp to 0L on each run so we don't evaluate the whole project again *)
- }
-
- let generateSourceCode moduleName =
- sprintf """
-module Benchmark.%s
-
-type %s =
-
- val X : int
-
- val Y : int
-
- val Z : int
-
-let function%s (x: %s) =
- let x = 1
- let y = 2
- let z = x + y
- z""" moduleName moduleName moduleName moduleName
-
- let decentlySizedStandAloneFile = File.ReadAllText(Path.Combine(__SOURCE_DIRECTORY__, "decentlySizedStandAloneFile.fsx"))
-
-[]
-type CompilerService() =
- let mutable checkerOpt = None
- let mutable sourceOpt = None
- let mutable assembliesOpt = None
- let mutable decentlySizedStandAloneFileCheckResultOpt = None
-
- let parsingOptions =
- {
- SourceFiles = [|"CheckExpressions.fs"|]
- ConditionalCompilationDefines = []
- ErrorSeverityOptions = FSharpDiagnosticOptions.Default
- IsInteractive = false
- LightSyntax = None
- CompilingFsLib = false
- IsExe = false
- }
-
- let readerOptions =
- {
- pdbDirPath = None
- reduceMemoryUsage = ReduceMemoryFlag.No
- metadataOnly = MetadataOnlyFlag.Yes
- tryGetMetadataSnapshot = fun _ -> None
- }
-
- []
- member __.Setup() =
- match checkerOpt with
- | None -> checkerOpt <- Some(FSharpChecker.Create(projectCacheSize = 200))
- | _ -> ()
-
- match sourceOpt with
- | None ->
- sourceOpt <- Some <| SourceText.From(File.OpenRead("""..\..\..\..\..\src\fsharp\CheckExpressions.fs"""), Encoding.Default, SourceHashAlgorithm.Sha1, true)
- | _ -> ()
-
- match assembliesOpt with
- | None ->
- assembliesOpt <-
- System.AppDomain.CurrentDomain.GetAssemblies()
- |> Array.map (fun x -> (x.Location))
- |> Some
-
- | _ -> ()
-
- match decentlySizedStandAloneFileCheckResultOpt with
- | None ->
- let options, _ =
- checkerOpt.Value.GetProjectOptionsFromScript("decentlySizedStandAloneFile.fsx", SourceText.ofString decentlySizedStandAloneFile)
- |> Async.RunImmediate
- let _, checkResult =
- checkerOpt.Value.ParseAndCheckFileInProject("decentlySizedStandAloneFile.fsx", 0, SourceText.ofString decentlySizedStandAloneFile, options)
- |> Async.RunImmediate
- decentlySizedStandAloneFileCheckResultOpt <- Some checkResult
- | _ -> ()
-
- []
- member __.ParsingTypeCheckerFs() =
- match checkerOpt, sourceOpt with
- | None, _ -> failwith "no checker"
- | _, None -> failwith "no source"
- | Some(checker), Some(source) ->
- let results = checker.ParseFile("CheckExpressions.fs", source.ToFSharpSourceText(), parsingOptions) |> Async.RunImmediate
- if results.ParseHadErrors then failwithf "parse had errors: %A" results.Diagnostics
-
- []
- member __.ParsingTypeCheckerFsSetup() =
- match checkerOpt with
- | None -> failwith "no checker"
- | Some(checker) ->
- checker.InvalidateAll()
- checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
- checker.ParseFile("dummy.fs", SourceText.ofString "dummy", parsingOptions) |> Async.RunImmediate |> ignore
- ClearAllILModuleReaderCache()
-
- []
- member __.ILReading() =
- match assembliesOpt with
- | None -> failwith "no assemblies"
- | Some(assemblies) ->
- // We try to read most of everything in the assembly that matter, mainly types with their properties, methods, and fields.
- // CustomAttrs and SecurityDecls are lazy until you call them, so we call them here for benchmarking.
- assemblies
- |> Array.iter (fun (fileName) ->
- let reader = OpenILModuleReader fileName readerOptions
-
- let ilModuleDef = reader.ILModuleDef
-
- let ilAssemblyManifest = ilModuleDef.Manifest.Value
-
- ilAssemblyManifest.CustomAttrs |> ignore
- ilAssemblyManifest.SecurityDecls |> ignore
- ilAssemblyManifest.ExportedTypes.AsList
- |> List.iter (fun x ->
- x.CustomAttrs |> ignore
- )
-
- ilModuleDef.CustomAttrs |> ignore
- ilModuleDef.TypeDefs.AsArray
- |> Array.iter (fun ilTypeDef ->
- ilTypeDef.CustomAttrs |> ignore
- ilTypeDef.SecurityDecls |> ignore
-
- ilTypeDef.Methods.AsArray
- |> Array.iter (fun ilMethodDef ->
- ilMethodDef.CustomAttrs |> ignore
- ilMethodDef.SecurityDecls |> ignore
- )
-
- ilTypeDef.Fields.AsList
- |> List.iter (fun ilFieldDef ->
- ilFieldDef.CustomAttrs |> ignore
- )
-
- ilTypeDef.Properties.AsList
- |> List.iter (fun ilPropertyDef ->
- ilPropertyDef.CustomAttrs |> ignore
- )
- )
- )
-
- []
- member __.ILReadingSetup() =
- // With caching, performance increases an order of magnitude when re-reading an ILModuleReader.
- // Clear it for benchmarking.
- ClearAllILModuleReaderCache()
-
- member val TypeCheckFileWith100ReferencedProjectsOptions =
- createProject "MainProject"
- [ for i = 1 to 100 do
- yield
- createProject ("ReferencedProject" + string i) []
- ]
-
- member this.TypeCheckFileWith100ReferencedProjectsRun() =
- let options = this.TypeCheckFileWith100ReferencedProjectsOptions
- let file = options.SourceFiles.[0]
-
- match checkerOpt with
- | None -> failwith "no checker"
- | Some checker ->
- let parseResult, checkResult =
- checker.ParseAndCheckFileInProject(file, 0, SourceText.ofString (File.ReadAllText(file)), options)
- |> Async.RunImmediate
-
- if parseResult.Diagnostics.Length > 0 then
- failwithf "%A" parseResult.Diagnostics
-
- match checkResult with
- | FSharpCheckFileAnswer.Aborted -> failwith "aborted"
- | FSharpCheckFileAnswer.Succeeded checkFileResult ->
-
- if checkFileResult.Diagnostics.Length > 0 then
- failwithf "%A" checkFileResult.Diagnostics
-
- []
- member this.TypeCheckFileWith100ReferencedProjectsSetup() =
- this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
- |> Seq.iter (fun file ->
- File.WriteAllText(file, generateSourceCode (Path.GetFileNameWithoutExtension(file)))
- )
-
- this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
- |> Seq.iter (function
- | FSharpReferencedProject.FSharpReference(_, referencedProjectOptions) ->
- referencedProjectOptions.SourceFiles
- |> Seq.iter (fun file ->
- File.WriteAllText(file, generateSourceCode (Path.GetFileNameWithoutExtension(file)))
- )
- | _ -> ()
- )
-
- this.TypeCheckFileWith100ReferencedProjectsRun()
-
- []
- member this.TypeCheckFileWith100ReferencedProjects() =
- // Because the checker's projectcachesize is set to 200, this should be fast.
- // If set to 3, it will be almost as slow as re-evaluating all project and it's projects references on setup; this could be a bug or not what we want.
- this.TypeCheckFileWith100ReferencedProjectsRun()
-
- member val TypeCheckFileWithNoReferencesOptions = createProject "MainProject" []
-
- []
- member this.TypeCheckFileWith100ReferencedProjectsCleanup() =
- this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
- |> Seq.iter (fun file ->
- try File.Delete(file) with | _ -> ()
- )
-
- this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
- |> Seq.iter (function
- | FSharpReferencedProject.FSharpReference(_, referencedProjectOptions) ->
- referencedProjectOptions.SourceFiles
- |> Seq.iter (fun file ->
- try File.Delete(file) with | _ -> ()
- )
- | _ -> ()
- )
-
- match checkerOpt with
- | None -> failwith "no checker"
- | Some(checker) ->
- checker.InvalidateAll()
- checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
- ClearAllILModuleReaderCache()
-
- []
- member this.SimplifyNames() =
- match decentlySizedStandAloneFileCheckResultOpt with
- | Some checkResult ->
- match checkResult with
- | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
- | FSharpCheckFileAnswer.Succeeded results ->
- let sourceLines = decentlySizedStandAloneFile.Split ([|"\r\n"; "\n"; "\r"|], StringSplitOptions.None)
- let ranges = SimplifyNames.getSimplifiableNames(results, fun lineNum -> sourceLines.[Line.toZ lineNum]) |> Async.RunImmediate
- ignore ranges
- ()
- | _ -> failwith "oopsie"
-
- []
- member this.UnusedOpens() =
- match decentlySizedStandAloneFileCheckResultOpt with
- | Some checkResult ->
- match checkResult with
- | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
- | FSharpCheckFileAnswer.Succeeded results ->
- let sourceLines = decentlySizedStandAloneFile.Split ([|"\r\n"; "\n"; "\r"|], StringSplitOptions.None)
- let decls = UnusedOpens.getUnusedOpens(results, fun lineNum -> sourceLines.[Line.toZ lineNum]) |> Async.RunImmediate
- ignore decls
- ()
- | _ -> failwith "oopsie"
-
- []
- member this.UnusedDeclarations() =
- match decentlySizedStandAloneFileCheckResultOpt with
- | Some checkResult ->
- match checkResult with
- | FSharpCheckFileAnswer.Aborted -> failwith "checker aborted"
- | FSharpCheckFileAnswer.Succeeded results ->
- let decls = UnusedDeclarations.getUnusedDeclarations(results, true) |> Async.RunImmediate
- ignore decls // should be 16
- ()
- | _ -> failwith "oopsie"
+open BenchmarkDotNet.Running
+open FSharp.Compiler.Benchmarks
[]
let main _ =
- BenchmarkRunner.Run() |> ignore
+ //BenchmarkRunner.Run() |> ignore
+ BenchmarkRunner.Run() |> ignore
0
diff --git a/tests/benchmarks/CompilerServiceBenchmarks/benchmarks.ipynb b/tests/benchmarks/CompilerServiceBenchmarks/benchmarks.ipynb
new file mode 100644
index 00000000000..cbf5270689c
--- /dev/null
+++ b/tests/benchmarks/CompilerServiceBenchmarks/benchmarks.ipynb
@@ -0,0 +1,154 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "source": [
+ "#!pwsh\r\n",
+ "dotnet build -c release\r\n"
+ ],
+ "outputs": [],
+ "metadata": {
+ "dotnet_interactive": {
+ "language": "pwsh"
+ }
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "source": [
+ "#r \"../../../artifacts/bin/FSharp.Compiler.Benchmarks/Release/net5.0/FSharp.Compiler.Benchmarks.dll\"\r\n",
+ "#r \"../../../artifacts/bin/FSharp.Compiler.Benchmarks/Release/net5.0/BenchmarkDotNet.dll\""
+ ],
+ "outputs": [],
+ "metadata": {
+ "dotnet_interactive": {
+ "language": "fsharp"
+ }
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "source": [
+ "open BenchmarkDotNet.Running\r\n",
+ "open FSharp.Compiler.Benchmarks\r\n",
+ "\r\n",
+ "let summary = BenchmarkRunner.Run()"
+ ],
+ "outputs": [],
+ "metadata": {
+ "dotnet_interactive": {
+ "language": "fsharp"
+ }
+ }
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "source": [
+ "// https://benchmarkdotnet.org/api/BenchmarkDotNet.Reports.BenchmarkReport.html\r\n",
+ "#r \"nuget: XPlot.Plotly.Interactive, 4.0.2\"\r\n",
+ "\r\n",
+ "open XPlot.Plotly\r\n",
+ "\r\n",
+ "let gcStats = summary.Reports |> Seq.map (fun x -> x.GcStats)\r\n",
+ "\r\n",
+ "let gen0Series =\r\n",
+ " Bar(\r\n",
+ " name = \"Gen 0\",\r\n",
+ " y = (gcStats |> Seq.map (fun x -> x.Gen0Collections))\r\n",
+ " )\r\n",
+ "\r\n",
+ "let gen1Series =\r\n",
+ " Bar(\r\n",
+ " name = \"Gen 1\",\r\n",
+ " y = (gcStats |> Seq.map (fun x -> x.Gen1Collections))\r\n",
+ " )\r\n",
+ "\r\n",
+ "let gen2Series =\r\n",
+ " Bar(\r\n",
+ " name = \"Gen 2\",\r\n",
+ " y = (gcStats |> Seq.map (fun x -> x.Gen2Collections))\r\n",
+ " )\r\n",
+ "\r\n",
+ "[gen0Series;gen1Series;gen2Series]\r\n",
+ "|> Chart.Plot\r\n",
+ "|> Chart.WithTitle(\"F# Type-Checking Benchmark 1 - GC Collection Counts\")"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\r\n",
+ "\r\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "metadata": {
+ "dotnet_interactive": {
+ "language": "fsharp"
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "orig_nbformat": 4,
+ "language_info": {
+ "file_extension": ".cs",
+ "mimetype": "text/x-csharp",
+ "name": "C#",
+ "pygments_lexer": "csharp",
+ "version": "9.0"
+ },
+ "kernelspec": {
+ "display_name": ".NET (C#)",
+ "language": "C#",
+ "name": ".net-csharp"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
\ No newline at end of file
diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
index 6ed309bf573..c81957c59b2 100644
--- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
+++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs
@@ -70,4 +70,4 @@ type internal FSharpGoToDefinitionService
// Don't show the dialog box as it's most likely that the user cancelled.
// Don't make them click twice.
- true
\ No newline at end of file
+ true