Skip to content

Commit e986f64

Browse files
authored
Set up benchmarks project (microsoft#118)
1 parent 850c457 commit e986f64

12 files changed

+214
-68
lines changed

.vscode/tasks.json

+20-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@
3636
"reveal": "always"
3737
},
3838
"problemMatcher": "$msCompile"
39+
},
40+
{
41+
"label": "bench",
42+
"command": "dotnet",
43+
"type": "shell",
44+
"args": [
45+
"run",
46+
"-c",
47+
"Release",
48+
"-f",
49+
"net7.0"
50+
],
51+
"options": {
52+
"cwd": "${workspaceFolder}/bench"
53+
},
54+
"presentation": {
55+
"reveal": "always"
56+
},
57+
"problemMatcher": "$msCompile"
3958
}
4059
]
41-
}
60+
}

Directory.Packages.props

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
55
<ItemGroup>
6+
<PackageVersion Include="BenchmarkDotNet" Version="0.13.5" />
67
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
78
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
89
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />

NodeApi.sln

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Workflows", "GitHub
2929
EndProject
3030
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodeApi.DotNetHost", "src\NodeApi.DotNetHost\NodeApi.DotNetHost.csproj", "{50F53625-D418-4A39-8B3D-842D38229D40}"
3131
EndProject
32+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NodeApi.Bench", "bench\NodeApi.Bench.csproj", "{9EC2AB75-6B03-4E83-8A5D-8660B40B0A9C}"
33+
EndProject
3234
Global
3335
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3436
Debug|Any CPU = Debug|Any CPU
@@ -51,6 +53,10 @@ Global
5153
{50F53625-D418-4A39-8B3D-842D38229D40}.Debug|Any CPU.Build.0 = Debug|Any CPU
5254
{50F53625-D418-4A39-8B3D-842D38229D40}.Release|Any CPU.ActiveCfg = Release|Any CPU
5355
{50F53625-D418-4A39-8B3D-842D38229D40}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{9EC2AB75-6B03-4E83-8A5D-8660B40B0A9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57+
{9EC2AB75-6B03-4E83-8A5D-8660B40B0A9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
58+
{9EC2AB75-6B03-4E83-8A5D-8660B40B0A9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
59+
{9EC2AB75-6B03-4E83-8A5D-8660B40B0A9C}.Release|Any CPU.Build.0 = Release|Any CPU
5460
EndGlobalSection
5561
GlobalSection(SolutionProperties) = preSolution
5662
HideSolutionNode = FALSE

bench/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BenchmarkDotNet.Artifacts

bench/Benchmarks.cs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.IO;
6+
using BenchmarkDotNet.Attributes;
7+
using BenchmarkDotNet.Jobs;
8+
using BenchmarkDotNet.Running;
9+
using Microsoft.JavaScript.NodeApi.Runtimes;
10+
using static Microsoft.JavaScript.NodeApi.JSNativeApi.Interop;
11+
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
12+
13+
namespace Microsoft.JavaScript.NodeApi.Bench;
14+
15+
public abstract class Benchmarks
16+
{
17+
public static void Main(string[] args)
18+
{
19+
// Example: dotnet run -c Release --filter aot
20+
// If no filter is specified, the switcher will prompt.
21+
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
22+
}
23+
24+
public class NonAot : Benchmarks
25+
{
26+
// Non-AOT-only benchmarks may go here
27+
}
28+
29+
[SimpleJob(RuntimeMoniker.NativeAot70)]
30+
public class Aot : Benchmarks
31+
{
32+
// AOT-only benchmarks may go here
33+
}
34+
35+
private static string LibnodePath { get; } = Path.Combine(
36+
GetRepoRootDirectory(),
37+
"bin",
38+
"win-x64", // TODO
39+
"libnode" + GetSharedLibraryExtension());
40+
41+
private napi_env _env;
42+
private JSValue _function;
43+
private JSValue _callback;
44+
45+
[GlobalSetup]
46+
public void Setup()
47+
{
48+
NodejsPlatform platform = new(LibnodePath);
49+
50+
// This setup avoids using NodejsEnvironment so benchmarks can run on the same thread.
51+
// NodejsEnvironment creates a separate thread that would slow down the micro-benchmarks.
52+
_env = JSNativeApi.CreateEnvironment(
53+
(napi_platform)platform, (error) => Console.WriteLine(error), null);
54+
55+
// The new scope instance saves itself as the thread-local JSValueScope.Current.
56+
JSValueScope scope = new(JSValueScopeType.Root, _env);
57+
58+
// Create some JS values that will be used by the benchmarks.
59+
_function = JSNativeApi.RunScript("function callMeBack(cb) { cb(); }; callMeBack");
60+
_callback = JSValue.CreateFunction("callback", (args) => JSValue.Undefined);
61+
}
62+
63+
[Benchmark]
64+
public void CallJS()
65+
{
66+
_function.Call(thisArg: default, _callback);
67+
}
68+
}
69+

bench/Directory.Build.props

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project>
2+
<PropertyGroup>
3+
<LangVersion>10</LangVersion>
4+
<Nullable>enable</Nullable>
5+
</PropertyGroup>
6+
7+
</Project>

bench/NodeApi.Bench.csproj

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and ! $([MSBuild]::IsOsPlatform('Windows')) ">net7.0</TargetFrameworks>
5+
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $([MSBuild]::IsOsPlatform('Windows')) ">net7.0;net472</TargetFrameworks>
6+
<OutputType>exe</OutputType>
7+
<RootNamespace>Microsoft.JavaScript.NodeApi.Bench</RootNamespace>
8+
<IsPublishable>false</IsPublishable>
9+
<NoWarn>MSB3270</NoWarn><!-- Processor architecture mismatch bewteen "MSIL" and ... -->
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Include="../test/TestUtils.cs" Link="TestUtils.cs" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="BenchmarkDotNet" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<ProjectReference Include="..\src\NodeApi\NodeApi.csproj" />
22+
<ProjectReference Include="..\src\NodeApi.DotNetHost\NodeApi.DotNetHost.csproj" />
23+
</ItemGroup>
24+
25+
</Project>

test/HostedClrTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Xunit;
88

99
using static Microsoft.JavaScript.NodeApi.Test.TestBuilder;
10+
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
1011

1112
// Avoid running MSBuild on the same project concurrently.
1213
[assembly: CollectionBehavior(DisableTestParallelization = true)]

test/NativeAotTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Xunit;
1111

1212
using static Microsoft.JavaScript.NodeApi.Test.TestBuilder;
13+
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
1314

1415
namespace Microsoft.JavaScript.NodeApi.Test;
1516

test/NodejsEmbeddingTests.cs

+2-12
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,13 @@
88
using System.Threading;
99
using Microsoft.JavaScript.NodeApi.Runtimes;
1010
using Xunit;
11+
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
1112

1213
namespace Microsoft.JavaScript.NodeApi.Test;
1314

1415
public class NodejsEmbeddingTests
1516
{
16-
private static string LibnodePath { get; } = Path.Combine(
17-
TestBuilder.RepoRootDirectory,
18-
"bin",
19-
TestBuilder.GetCurrentPlatformRuntimeIdentifier(),
20-
"libnode" + GetSharedLibraryExtension());
21-
22-
private static string GetSharedLibraryExtension()
23-
{
24-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return ".dll";
25-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return ".dylib";
26-
else return ".so";
27-
}
17+
private static string LibnodePath { get; } = GetLibnodePath();
2818

2919
// The Node.js platform may only be initialized once per process.
3020
private static NodejsPlatform? NodejsPlatform { get; } =

test/TestBuilder.cs

+3-55
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
using System.Diagnostics;
77
using System.IO;
88
using System.Linq;
9-
using System.Runtime.InteropServices;
109
using System.Text;
1110
using System.Threading;
1211
using Xunit;
12+
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
1313

1414
namespace Microsoft.JavaScript.NodeApi.Test;
1515

@@ -30,39 +30,14 @@ internal static class TestBuilder
3030
"Release";
3131
#endif
3232

33-
public static string RepoRootDirectory { get; } = GetRootDirectory();
33+
public static string RepoRootDirectory { get; } = GetRepoRootDirectory();
3434

3535
public static string TestCasesDirectory { get; } = GetTestCasesDirectory();
3636

37-
private static string GetRootDirectory()
38-
{
39-
string? solutionDir = Path.GetDirectoryName(
40-
#if NETFRAMEWORK
41-
new Uri(typeof(TestBuilder).Assembly.CodeBase).LocalPath)!;
42-
#else
43-
#pragma warning disable IL3000 // Assembly.Location returns an empty string for assemblies embedded in a single-file app
44-
typeof(TestBuilder).Assembly.Location)!;
45-
#pragma warning restore IL3000
46-
#endif
47-
48-
// This assumes there is only a .SLN file at the root of the repo.
49-
while (Directory.GetFiles(solutionDir, "*.sln").Length == 0)
50-
{
51-
solutionDir = Path.GetDirectoryName(solutionDir);
52-
53-
if (string.IsNullOrEmpty(solutionDir))
54-
{
55-
throw new DirectoryNotFoundException("Solution directory not found.");
56-
}
57-
}
58-
59-
return solutionDir;
60-
}
61-
6237
private static string GetTestCasesDirectory()
6338
{
6439
// This assumes tests are organized in this test/TestCases directory structure.
65-
string testCasesDir = Path.Combine(GetRootDirectory(), "test", "TestCases");
40+
string testCasesDir = Path.Combine(GetRepoRootDirectory(), "test", "TestCases");
6641

6742
if (!Directory.Exists(testCasesDir))
6843
{
@@ -141,33 +116,6 @@ public static string GetRunLogFilePath(string prefix, string moduleName, string
141116
return Path.Combine(logDir, $"{prefix}-{Path.GetFileName(testCasePath)}.log");
142117
}
143118

144-
public static string GetCurrentPlatformRuntimeIdentifier()
145-
{
146-
string os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win" :
147-
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" :
148-
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" :
149-
throw new PlatformNotSupportedException(
150-
"Platform not supported: " + Environment.OSVersion.Platform);
151-
152-
string arch = RuntimeInformation.ProcessArchitecture switch
153-
{
154-
Architecture.X86 => "x86",
155-
Architecture.X64 => "x64",
156-
Architecture.Arm64 => "arm64",
157-
_ => throw new PlatformNotSupportedException(
158-
"CPU architecture not supported: " + RuntimeInformation.ProcessArchitecture),
159-
};
160-
161-
return $"{os}-{arch}";
162-
}
163-
164-
public static string GetCurrentFrameworkTarget()
165-
{
166-
Version frameworkVersion = Environment.Version;
167-
return frameworkVersion.Major == 4 ? "net472" :
168-
$"net{frameworkVersion.Major}.{frameworkVersion.Minor}";
169-
}
170-
171119
public static string CreateProjectFile(string moduleName)
172120
{
173121
string projectFilePath = Path.Combine(

test/TestUtils.cs

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
8+
namespace Microsoft.JavaScript.NodeApi.Test;
9+
10+
public static class TestUtils
11+
{
12+
public static string GetRepoRootDirectory()
13+
{
14+
#if NETFRAMEWORK
15+
string assemblyLocation = new Uri(typeof(TestUtils).Assembly.CodeBase).LocalPath;
16+
#else
17+
#pragma warning disable IL3000 // Assembly.Location returns an empty string for assemblies embedded in a single-file app
18+
string assemblyLocation = typeof(TestUtils).Assembly.Location!;
19+
#pragma warning restore IL3000
20+
#endif
21+
22+
string? solutionDir = string.IsNullOrEmpty(assemblyLocation) ?
23+
Environment.CurrentDirectory : Path.GetDirectoryName(assemblyLocation);
24+
25+
// This assumes there is only a .SLN file at the root of the repo.
26+
while (Directory.GetFiles(solutionDir!, "*.sln").Length == 0)
27+
{
28+
solutionDir = Path.GetDirectoryName(solutionDir);
29+
30+
if (string.IsNullOrEmpty(solutionDir))
31+
{
32+
throw new DirectoryNotFoundException("Solution directory not found.");
33+
}
34+
}
35+
36+
return solutionDir!;
37+
}
38+
39+
public static string GetCurrentPlatformRuntimeIdentifier()
40+
{
41+
string os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win" :
42+
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" :
43+
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" :
44+
throw new PlatformNotSupportedException(
45+
"Platform not supported: " + Environment.OSVersion.Platform);
46+
47+
string arch = RuntimeInformation.ProcessArchitecture switch
48+
{
49+
Architecture.X86 => "x86",
50+
Architecture.X64 => "x64",
51+
Architecture.Arm64 => "arm64",
52+
_ => throw new PlatformNotSupportedException(
53+
"CPU architecture not supported: " + RuntimeInformation.ProcessArchitecture),
54+
};
55+
56+
return $"{os}-{arch}";
57+
}
58+
59+
public static string GetCurrentFrameworkTarget()
60+
{
61+
Version frameworkVersion = Environment.Version;
62+
return frameworkVersion.Major == 4 ? "net472" :
63+
$"net{frameworkVersion.Major}.{frameworkVersion.Minor}";
64+
}
65+
66+
public static string GetSharedLibraryExtension()
67+
{
68+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return ".dll";
69+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return ".dylib";
70+
else return ".so";
71+
}
72+
73+
public static string GetLibnodePath() => Path.Combine(
74+
GetRepoRootDirectory(),
75+
"bin",
76+
GetCurrentPlatformRuntimeIdentifier(),
77+
"libnode" + GetSharedLibraryExtension());
78+
}

0 commit comments

Comments
 (0)