@@ -7,7 +7,7 @@ open FSharp.Compiler.Text
77open FSharp.Compiler .AbstractIL
88open FSharp.Compiler .AbstractIL .IL
99open FSharp.Compiler .AbstractIL .ILBinaryReader
10- open CodeAnalysis.Text
10+ open Microsoft. CodeAnalysis .Text
1111open BenchmarkDotNet.Attributes
1212open BenchmarkDotNet.Running
1313
@@ -80,6 +80,49 @@ type SourceText with
8080 member this.ToFSharpSourceText () =
8181 SourceText.weakTable.GetValue( this, Runtime.CompilerServices.ConditionalWeakTable<_,_>. CreateValueCallback( SourceText.create))
8282
83+ [<AutoOpen>]
84+ module Helpers =
85+
86+ let createProject name referencedProjects =
87+ let tmpPath = Path.GetTempPath()
88+ let file = Path.Combine( tmpPath, Path.ChangeExtension( name, " .fs" ))
89+ {
90+ ProjectFileName = Path.Combine( tmpPath, Path.ChangeExtension( name, " .dll" ))
91+ ProjectId = None
92+ SourceFiles = [| file|]
93+ OtherOptions =
94+ Array.append [| " --optimize+" ; " --target:library" |] ( referencedProjects |> Array.ofList |> Array.map ( fun x -> " -r:" + x.ProjectFileName))
95+ ReferencedProjects =
96+ referencedProjects
97+ |> List.map ( fun x -> ( x.ProjectFileName, x))
98+ |> Array.ofList
99+ IsIncompleteTypeCheckEnvironment = false
100+ UseScriptResolutionRules = false
101+ LoadTime = DateTime()
102+ UnresolvedReferences = None
103+ OriginalLoadReferences = []
104+ ExtraProjectInfo = None
105+ Stamp = Some 0 L (* set the stamp to 0L on each run so we don't evaluate the whole project again *)
106+ }
107+
108+ let generateSourceCode moduleName =
109+ sprintf """
110+ module Benchmark.%s
111+
112+ type %s =
113+
114+ val X : int
115+
116+ val Y : int
117+
118+ val Z : int
119+
120+ let function%s (x: %s ) =
121+ let x = 1
122+ let y = 2
123+ let z = x + y
124+ z""" moduleName moduleName moduleName moduleName
125+
83126[<MemoryDiagnoser>]
84127type CompilerService () =
85128
@@ -112,7 +155,7 @@ type CompilerService() =
112155 [<GlobalSetup>]
113156 member __.Setup () =
114157 match checkerOpt with
115- | None -> checkerOpt <- Some( FSharpChecker.Create())
158+ | None -> checkerOpt <- Some( FSharpChecker.Create( projectCacheSize = 200 ))
116159 | _ -> ()
117160
118161 match sourceOpt with
@@ -127,30 +170,25 @@ type CompilerService() =
127170 |> Array.map ( fun x -> ( x.Location))
128171 |> Some
129172 | _ -> ()
130-
131- [<IterationSetup( Target = " Parsing" ) >]
132- member __.ParsingSetup () =
133- match checkerOpt with
134- | None -> failwith " no checker"
135- | Some( checker) ->
136- checker.InvalidateAll()
137- checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
138- checker.ParseFile( " dummy.fs" , SourceText.ofString " dummy" , parsingOptions) |> Async.RunSynchronously |> ignore
139173
140174 [<Benchmark>]
141- member __.Parsing () =
175+ member __.ParsingTypeCheckerFs () =
142176 match checkerOpt, sourceOpt with
143177 | None, _ -> failwith " no checker"
144178 | _, None -> failwith " no source"
145179 | Some( checker), Some( source) ->
146180 let results = checker.ParseFile( " TypeChecker.fs" , source.ToFSharpSourceText(), parsingOptions) |> Async.RunSynchronously
147181 if results.ParseHadErrors then failwithf " parse had errors: %A " results.Errors
148182
149- [<IterationSetup( Target = " ILReading" ) >]
150- member __.ILReadingSetup () =
151- // With caching, performance increases an order of magnitude when re-reading an ILModuleReader.
152- // Clear it for benchmarking.
153- ClearAllILModuleReaderCache()
183+ [<IterationCleanup( Target = " ParsingTypeCheckerFs" ) >]
184+ member __.ParsingTypeCheckerFsSetup () =
185+ match checkerOpt with
186+ | None -> failwith " no checker"
187+ | Some( checker) ->
188+ checker.InvalidateAll()
189+ checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
190+ checker.ParseFile( " dummy.fs" , SourceText.ofString " dummy" , parsingOptions) |> Async.RunSynchronously |> ignore
191+ ClearAllILModuleReaderCache()
154192
155193 [<Benchmark>]
156194 member __.ILReading () =
@@ -198,6 +236,85 @@ type CompilerService() =
198236 )
199237 )
200238
239+ [<IterationCleanup( Target = " ILReading" ) >]
240+ member __.ILReadingSetup () =
241+ // With caching, performance increases an order of magnitude when re-reading an ILModuleReader.
242+ // Clear it for benchmarking.
243+ ClearAllILModuleReaderCache()
244+
245+ member val TypeCheckFileWith100ReferencedProjectsOptions =
246+ createProject " MainProject"
247+ [ for i = 1 to 100 do
248+ yield
249+ createProject ( " ReferencedProject" + string i) []
250+ ]
251+
252+ member this.TypeCheckFileWith100ReferencedProjectsRun () =
253+ let options = this.TypeCheckFileWith100ReferencedProjectsOptions
254+ let file = options.SourceFiles.[ 0 ]
255+
256+ match checkerOpt with
257+ | None -> failwith " no checker"
258+ | Some checker ->
259+ let parseResult , checkResult =
260+ checker.ParseAndCheckFileInProject( file, 0 , SourceText.ofString ( File.ReadAllText( file)), options)
261+ |> Async.RunSynchronously
262+
263+ if parseResult.Errors.Length > 0 then
264+ failwithf " %A " parseResult.Errors
265+
266+ match checkResult with
267+ | FSharpCheckFileAnswer.Aborted -> failwith " aborted"
268+ | FSharpCheckFileAnswer.Succeeded checkFileResult ->
269+
270+ if checkFileResult.Errors.Length > 0 then
271+ failwithf " %A " checkFileResult.Errors
272+
273+ [<IterationSetup( Target = " TypeCheckFileWith100ReferencedProjects" ) >]
274+ member this.TypeCheckFileWith100ReferencedProjectsSetup () =
275+ this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
276+ |> Seq.iter ( fun file ->
277+ File.WriteAllText( file, generateSourceCode ( Path.GetFileNameWithoutExtension( file)))
278+ )
279+
280+ this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
281+ |> Seq.iter ( fun ( _ , referencedProjectOptions ) ->
282+ referencedProjectOptions.SourceFiles
283+ |> Seq.iter ( fun file ->
284+ File.WriteAllText( file, generateSourceCode ( Path.GetFileNameWithoutExtension( file)))
285+ )
286+ )
287+
288+ this.TypeCheckFileWith100ReferencedProjectsRun()
289+
290+ [<Benchmark>]
291+ member this.TypeCheckFileWith100ReferencedProjects () =
292+ // Because the checker's projectcachesize is set to 200, this should be fast.
293+ // 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.
294+ this.TypeCheckFileWith100ReferencedProjectsRun()
295+
296+ [<IterationCleanup( Target = " TypeCheckFileWith100ReferencedProjects" ) >]
297+ member this.TypeCheckFileWith100ReferencedProjectsCleanup () =
298+ this.TypeCheckFileWith100ReferencedProjectsOptions.SourceFiles
299+ |> Seq.iter ( fun file ->
300+ try File.Delete( file) with | _ -> ()
301+ )
302+
303+ this.TypeCheckFileWith100ReferencedProjectsOptions.ReferencedProjects
304+ |> Seq.iter ( fun ( _ , referencedProjectOptions ) ->
305+ referencedProjectOptions.SourceFiles
306+ |> Seq.iter ( fun file ->
307+ try File.Delete( file) with | _ -> ()
308+ )
309+ )
310+
311+ match checkerOpt with
312+ | None -> failwith " no checker"
313+ | Some( checker) ->
314+ checker.InvalidateAll()
315+ checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
316+ ClearAllILModuleReaderCache()
317+
201318[<EntryPoint>]
202319let main argv =
203320 let _ = BenchmarkRunner.Run< CompilerService>()
0 commit comments