Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added basic FSharp benchmarks #1546

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,23 @@ jobs:
- master
- release/5.0.1xx
#- release/3.1.3xx

# Windows x64 FSharp benchmarks
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: windows
osVersion: RS5
kind: fsharp
architecture: x64
pool: Hosted VS2017
machinePool: Open
queue: Windows.10.Amd64.ClientRS5.Open
csproj: src\benchmarks\real-world\FSharp\FSharpBenchmarks.fsproj
runCategories: 'fsharp'
channels: # for FSharp jobs we want to check .NET Core 3.1 and 5.0 only
- master
- release/5.0.1xx
#- release/3.1.3xx

# Ubuntu 1804 x64 micro benchmarks
- template: /eng/performance/benchmark_jobs.yml
Expand Down Expand Up @@ -270,6 +287,24 @@ jobs:
- release/5.0.1xx
#- release/3.1.3xx

# Ubuntu 1804 x64 FSharp benchmarks
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: ubuntu
osVersion: 1804
kind: fsharp
architecture: x64
pool: Hosted Ubuntu 1604
machinePool: Open
queue: Ubuntu.1804.Amd64.Open
container: ubuntu_x64_build_container
runCategories: 'fsharp'
csproj: src/benchmarks/real-world/FSharp/FSharpBenchmarks.fsproj
channels: # for FSharp jobs we want to check .NET Core 3.1 and 5.0 only
- master
- release/5.0.1xx
#- release/3.1.3xx

###########################################
# Private Jobs
###########################################
Expand Down Expand Up @@ -390,6 +425,23 @@ jobs:
- release/5.0.1xx
#- release/3.1.2xx

# Windows x64 FSharp benchmarks
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: windows
osVersion: 19H1
kind: fsharp
architecture: x64
pool: Hosted VS2017
machinePool: Tiger
queue: Windows.10.Amd64.19H1.Tiger.Perf # using a dedicated private Helix queue (perfsnakes)
csproj: src\benchmarks\real-world\FSharp\FSharpBenchmarks.fsproj
runCategories: 'fsharp'
channels: # for private jobs we want to benchmark .NET Core 3.1 and 5.0 only
- master
- release/5.0.1xx
#- release/3.1.2xx

# Ubuntu 1804 x64 micro benchmarks
- template: /eng/performance/benchmark_jobs.yml
parameters:
Expand Down Expand Up @@ -462,6 +514,24 @@ jobs:
- release/5.0.1xx
#- release/3.1.2xx

# Ubuntu 1804 x64 FSharp benchmarks
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: ubuntu
osVersion: 1804
kind: fsharp
architecture: x64
pool: Hosted Ubuntu 1604
machinePool: Tiger
queue: Ubuntu.1804.Amd64.Tiger.Perf # using a dedicated private Helix queue (perftigers)
container: ubuntu_x64_build_container
csproj: src/benchmarks/real-world/FSharp/FSharpBenchmarks.fsproj
runCategories: 'fsharp'
channels: # for private jobs we want to benchmark .NET Core 3.1 and 5.0 only
- master
- release/5.0.1xx
#- release/3.1.2xx

################################################
# Scheduled Private jobs
################################################
Expand Down
33 changes: 32 additions & 1 deletion eng/performance/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,35 @@ jobs:
csproj: src/benchmarks/real-world/Roslyn/CompilerBenchmarks.csproj
frameworks: # for Roslyn jobs we want to check .NET Core 3.0 only
- netcoreapp3.0


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this file is actually used - @DrewScoggins can you confirm?


# Windows x64 FSharp benchmarks, public correctness job
- ${{ if eq(variables['System.TeamProject'], 'public') }}:
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: windows
osVersion: RS4
kind: fsharp
architecture: x64
pool: Hosted VS2017
queue: Windows.10.Amd64.ClientRS4.Open
csproj: src\benchmarks\real-world\FSharp\FSharpBenchmarks.fsproj
runCategories: 'fsharp'
frameworks: # for FSharp jobs we want to check .NET Core 3.0 only
- netcoreapp3.0

# Ubuntu 1604 x64 FSharp benchmarks, public correctness job
- ${{ if eq(variables['System.TeamProject'], 'public') }}:
- template: /eng/performance/benchmark_jobs.yml
parameters:
osName: ubuntu
osVersion: 1604
kind: fsharp
architecture: x64
pool: Hosted Ubuntu 1604
queue: Ubuntu.1604.Amd64.Open
container: ubuntu_x64_build_container
runCategories: 'fsharp'
csproj: src/benchmarks/real-world/FSharp/FSharpBenchmarks.fsproj
frameworks: # for FSharp jobs we want to check .NET Core 3.0 only
- netcoreapp3.0
31 changes: 31 additions & 0 deletions src/benchmarks/real-world/FSharp/FSharpBenchmarks.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>$(PERFLAB_TARGET_FRAMEWORKS)</TargetFrameworks>
<TargetFrameworks Condition="'$(TargetFrameworks)' == ''">netcoreapp3.1;netcoreapp5.0</TargetFrameworks>
<IsShipping>false</IsShipping>
<!-- this app is using internal Roslyn API and needs to be signed -->
<SignAssembly>true</SignAssembly>
<StrongNameKeyId>MicrosoftShared</StrongNameKeyId>
<!-- ignore warning about BenchmarkDotNet.Extensions being not signed -->
<NoWarn>$(NoWarn);8002</NoWarn>
</PropertyGroup>

<ItemGroup>
<Compile Include="Helpers.fs" />
<Compile Include="FSharpCompilerServiceBenchmarks.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Compiler.Service" Version="37.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\harness\BenchmarkDotNet.Extensions\BenchmarkDotNet.Extensions.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace FSharpBenchmarks

open System
open System.Collections.Immutable
open System.Diagnostics
open System.IO
open System.Threading.Tasks
open BenchmarkDotNet.Attributes
open FSharp.Compiler.SourceCodeServices
open FSharp.Compiler.AbstractIL

[<RequireQualifiedAccess>]
module Sources =

let helloWorld =
"""
[<EntryPoint>]
let main _argv =
printfn "Hello World!"
0
"""

/// FSharp compiler service benchmarks
[<BenchmarkCategory("FSharp");MemoryDiagnoser>]
type FSharpCompilerServiceBenchmarks () =

let sourceDir = Path.Combine(Environment.CurrentDirectory, "fsharpSource");
let tempFsFile = Path.Combine(sourceDir, "temp.fs")
let tempOutputFile = Path.Combine(sourceDir, "temp.dll")

let defaultReferenceArgs =
Helpers.getFrameworkReferences ()
|> Array.map (fun r -> "-r:" + r)

let compileArgs =
Array.append
[|
"--preferreduilang:en-US"
"--optimize+"
"--langversion:preview"
"-o:" + tempOutputFile
"--nowin32manifest"
"--noframework"
"--simpleresolution"
"--targetprofile:netcore"
"--warn:5"
tempFsFile
|]
defaultReferenceArgs

let mutable checker = Unchecked.defaultof<FSharpChecker>

[<GlobalSetup>]
member _.Setup() =
Directory.CreateDirectory(sourceDir) |> ignore

// Benchmark.NET creates a new process to run the benchmark, so the easiest way
// to communicate information is pass by environment variable
Environment.SetEnvironmentVariable(Helpers.testProjectEnvVarName, sourceDir)

match box checker with
| null ->
checker <- FSharpChecker.Create(projectCacheSize = 200)
| _ ->
()

[<IterationSetup(Target = "CompileHelloWorld")>]
member _.CompileHelloWorldSetup() =
File.WriteAllText(tempFsFile, Sources.helloWorld)

[<Benchmark>]
member _.CompileHelloWorld() =
let errors, _ = checker.Compile(compileArgs) |> Async.RunSynchronously
if errors.Length > 0 then
failwithf "%A" errors

[<IterationCleanup(Target = "CompileHelloWorld")>]
member _.CompileHelloWorldCleanup() =
checker.InvalidateAll()
checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
try File.Delete(tempOutputFile) with | _ -> ()
try File.Delete(tempFsFile) with | _ -> ()
75 changes: 75 additions & 0 deletions src/benchmarks/real-world/FSharp/Helpers.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module FSharpBenchmarks.Helpers

open System
open System.IO
open System.Diagnostics

let testProjectEnvVarName = "FSHARP_TEST_PROJECT_DIR"

let getFrameworkReferences () =

let programFs = """
open System

[<EntryPoint>]
let main argv = 0"""

let projectFile = """
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5</TargetFramework>
<UseFSharpPreview>true</UseFSharpPreview>
</PropertyGroup>

<ItemGroup><Compile Include="Program.fs" /></ItemGroup>

<Target Name="WriteFrameworkReferences" AfterTargets="AfterBuild">
<WriteLinesToFile File="FrameworkReferences.txt" Lines="@(ReferencePath)" Overwrite="true" WriteOnlyWhenDifferent="true" />
</Target>

</Project>"""

let mutable output = ""
let mutable errors = ""
let mutable cleanUp = true
let projectDirectory = Path.Combine(Path.GetTempPath(), "FSharpBenchmarks", Path.GetRandomFileName())
try
try
Directory.CreateDirectory(projectDirectory) |> ignore
let projectFileName = Path.Combine(projectDirectory, "ProjectFile.fsproj")
let programFsFileName = Path.Combine(projectDirectory, "Program.fs")
let frameworkReferencesFileName = Path.Combine(projectDirectory, "FrameworkReferences.txt")
File.WriteAllText(projectFileName, projectFile)
File.WriteAllText(programFsFileName, programFs)

let pInfo = ProcessStartInfo ()
pInfo.FileName <- "dotnet"
pInfo.Arguments <- "build"
pInfo.WorkingDirectory <- projectDirectory
pInfo.RedirectStandardOutput <- true
pInfo.RedirectStandardError <- true
pInfo.UseShellExecute <- false

let p = Process.Start(pInfo)
let timeout = 30000
let succeeded = p.WaitForExit(timeout)

output <- p.StandardOutput.ReadToEnd ()
errors <- p.StandardError.ReadToEnd ()

if not (String.IsNullOrWhiteSpace errors) then failwithf "%A" errors
if p.ExitCode <> 0 then failwithf "Program exited with exit code %d" p.ExitCode
if not succeeded then failwithf "Program timed out after %d ms" timeout

File.ReadLines(frameworkReferencesFileName) |> Seq.toArray
with | e ->
cleanUp <- false
printfn "Project directory: %s" projectDirectory
printfn "STDOUT: %s" output
printfn "STDERR: %s" errors
raise (new Exception (sprintf "An error occurred getting framework references: %A" e))
finally
if cleanUp then
try Directory.Delete(projectDirectory) with | _ -> ()
28 changes: 28 additions & 0 deletions src/benchmarks/real-world/FSharp/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
open System
open System.Collections.Immutable
open System.Reflection
open System.IO
open System.Threading.Tasks
open BenchmarkDotNet.Running
open BenchmarkDotNet.Jobs
open BenchmarkDotNet.Configs
open BenchmarkDotNet.Extensions

open FSharpBenchmarks

let init argv =
async {
let asm = Assembly.GetExecutingAssembly()
return
BenchmarkSwitcher.FromAssembly(asm)
.Run(argv, RecommendedConfig.Create(
artifactsPath = DirectoryInfo(Path.Combine(Path.GetDirectoryName(asm.Location), "BenchmarkDotNet.Artifacts")),
mandatoryCategories = ImmutableHashSet.Create("FSharp"),
job = Job.Default.WithMaxRelativeError(0.01)))
.ToExitCode()
}

[<EntryPoint>]
let main argv =
init argv
|> Async.RunSynchronously