Skip to content

Commit 416275c

Browse files
authored
feat: Wasm MemoryDiagnoser (#3040)
* feat: Wasm MemoryDiagnoser * test(MemoryDiagnoserTests): add AllocateTask wasm test
1 parent 002ceeb commit 416275c

5 files changed

Lines changed: 49 additions & 44 deletions

File tree

src/BenchmarkDotNet/Engines/GcStats.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ public static GcStats FromForced(int forcedFullGarbageCollections)
141141
[MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
142142
private static long? GetAllocatedBytes()
143143
{
144-
// we have no tests for WASM and don't want to risk introducing a new bug (https://github.com/dotnet/BenchmarkDotNet/issues/2226)
145-
if (RuntimeInformation.IsWasm)
146-
return null;
147-
148144
// Do NOT call GC.Collect() here, as it causes finalizers to run and possibly allocate. https://github.com/dotnet/runtime/issues/101536#issuecomment-2077533242
149145
// Instead, we call it before we start the measurement in the Engine.
150146
#if NET6_0_OR_GREATER

tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using BenchmarkDotNet.Configs;
1111
using BenchmarkDotNet.Detectors;
1212
using BenchmarkDotNet.Diagnosers;
13+
using BenchmarkDotNet.Environments;
1314
using BenchmarkDotNet.Extensions;
1415
using BenchmarkDotNet.IntegrationTests.Xunit;
1516
using BenchmarkDotNet.Jobs;
@@ -19,11 +20,14 @@
1920
using BenchmarkDotNet.Tests.Loggers;
2021
using BenchmarkDotNet.Tests.XUnit;
2122
using BenchmarkDotNet.Toolchains;
22-
using BenchmarkDotNet.Toolchains.NativeAot;
23+
using BenchmarkDotNet.Toolchains.DotNetCli;
2324
using BenchmarkDotNet.Toolchains.InProcess.Emit;
25+
using BenchmarkDotNet.Toolchains.Mono;
26+
using BenchmarkDotNet.Toolchains.MonoAotLLVM;
27+
using BenchmarkDotNet.Toolchains.MonoWasm;
28+
using BenchmarkDotNet.Toolchains.NativeAot;
2429
using Xunit;
2530
using Xunit.Abstractions;
26-
using BenchmarkDotNet.Toolchains.Mono;
2731

2832
namespace BenchmarkDotNet.IntegrationTests
2933
{
@@ -86,6 +90,31 @@ public void MemoryDiagnoserSupportsModernMono()
8690
MemoryDiagnoserIsAccurate(MonoToolchain.Mono80);
8791
}
8892

93+
[TheoryEnvSpecific("JSVU does not support ARM on Windows or Linux", EnvRequirement.NonWindowsArm, EnvRequirement.NonLinuxArm)]
94+
[InlineData(MonoAotCompilerMode.mini)]
95+
// BUG: https://github.com/dotnet/BenchmarkDotNet/issues/3036
96+
[InlineData(MonoAotCompilerMode.wasm, Skip = "AOT is broken")]
97+
public void MemoryDiagnoserSupportsMonoWasm(MonoAotCompilerMode aotCompilerMode)
98+
{
99+
var ptrSize = sizeof(Int32); // We can't rely on IntPtr.Size, since we run on a different platform. Wasm is currently 32bit.
100+
var objectAllocationOverhead = ptrSize * 2; // pointer to method table + object header word
101+
var arraySizeOverhead = ptrSize * 2; // bounds + max_length
102+
var intTaskSize = 40; // We can't use CalculateRequiredSpace for AllocateTask since it calculates the size with IntPtr.Size.
103+
104+
var netCoreAppSettings = new NetCoreAppSettings("net8.0", runtimeFrameworkVersion: null!, "Wasm", aotCompilerMode: aotCompilerMode);
105+
106+
var runtime = new WasmRuntime(
107+
netCoreAppSettings.TargetFrameworkMoniker, RuntimeMoniker.WasmNet80,
108+
"Wasm", aotCompilerMode == MonoAotCompilerMode.wasm, "v8");
109+
110+
AssertAllocations(WasmToolchain.From(netCoreAppSettings), typeof(AccurateAllocations), new Dictionary<string, long>
111+
{
112+
{ nameof(AccurateAllocations.EightBytesArray), 8 + objectAllocationOverhead + arraySizeOverhead },
113+
{ nameof(AccurateAllocations.SixtyFourBytesArray), 64 + objectAllocationOverhead + arraySizeOverhead },
114+
{ nameof(AccurateAllocations.AllocateTask), intTaskSize },
115+
}, runtime: runtime);
116+
}
117+
89118
public class AllocatingGlobalSetupAndCleanup
90119
{
91120
private List<int> list = default!;
@@ -322,9 +351,10 @@ public void MemoryDiagnoserIsAccurateForMultiThreadedBenchmarks(IToolchain toolc
322351
});
323352
}
324353

325-
private void AssertAllocations(IToolchain toolchain, Type benchmarkType, Dictionary<string, long> benchmarksAllocationsValidators, bool disableTieredJit = true, int iterationCount = 1)
354+
private void AssertAllocations(IToolchain toolchain, Type benchmarkType, Dictionary<string, long> benchmarksAllocationsValidators,
355+
bool disableTieredJit = true, int iterationCount = 1, Runtime? runtime = null)
326356
{
327-
var config = CreateConfig(toolchain, disableTieredJit, iterationCount);
357+
var config = CreateConfig(toolchain, runtime, disableTieredJit, iterationCount);
328358
var benchmarks = BenchmarkConverter.TypeToBenchmarks(benchmarkType, config);
329359

330360
var summary = BenchmarkRunner.Run(benchmarks);
@@ -361,6 +391,7 @@ private void AssertAllocations(IToolchain toolchain, Type benchmarkType, Diction
361391
}
362392

363393
private IConfig CreateConfig(IToolchain toolchain,
394+
Runtime? runtime,
364395
// Tiered JIT can allocate some memory on a background thread, let's disable it by default to make our tests less flaky (#1542).
365396
// This was mostly fixed in net7.0, but tiered jit thread is not guaranteed to not allocate, so we disable it just in case.
366397
bool disableTieredJit = true,
@@ -382,6 +413,12 @@ private IConfig CreateConfig(IToolchain toolchain,
382413
.WithEnvironmentVariable(Engines.Engine.UnitTestBlockFinalizerEnvKey, Engines.Engine.UnitTestBlockFinalizerEnvValue)
383414
.WithToolchain(toolchain);
384415
#pragma warning restore CS0618 // WithEvaluateOverhead is obsolete
416+
417+
if (runtime is not null)
418+
{
419+
job = job.WithRuntime(runtime);
420+
}
421+
385422
return ManualConfig.CreateEmpty()
386423
.AddJob(disableTieredJit
387424
? job.WithEnvironmentVariables(

tests/BenchmarkDotNet.IntegrationTests/WasmTests.cs

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Linq;
44
using BenchmarkDotNet.Attributes;
55
using BenchmarkDotNet.Configs;
6-
using BenchmarkDotNet.Detectors;
76
using BenchmarkDotNet.Environments;
87
using BenchmarkDotNet.IntegrationTests.Diagnosers;
98
using BenchmarkDotNet.Jobs;
@@ -28,31 +27,23 @@ namespace BenchmarkDotNet.IntegrationTests
2827
/// </summary>
2928
public class WasmTests(ITestOutputHelper output) : BenchmarkTestExecutor(output)
3029
{
31-
[Theory]
30+
private const string JsvuSkipReason = "JSVU does not support ARM on Windows or Linux";
31+
32+
[TheoryEnvSpecific(JsvuSkipReason, EnvRequirement.NonWindowsArm, EnvRequirement.NonLinuxArm)]
3233
[InlineData(MonoAotCompilerMode.mini)]
3334
// BUG: https://github.com/dotnet/BenchmarkDotNet/issues/3036
3435
[InlineData(MonoAotCompilerMode.wasm, Skip = "AOT is broken")]
3536
public void WasmIsSupported(MonoAotCompilerMode aotCompilerMode)
3637
{
37-
if (SkipTestRun())
38-
{
39-
return;
40-
}
41-
4238
CanExecute<WasmBenchmark>(GetConfig(aotCompilerMode));
4339
}
4440

45-
[Theory]
41+
[TheoryEnvSpecific(JsvuSkipReason, EnvRequirement.NonWindowsArm, EnvRequirement.NonLinuxArm)]
4642
[InlineData(MonoAotCompilerMode.mini)]
4743
// BUG: https://github.com/dotnet/BenchmarkDotNet/issues/3036
4844
[InlineData(MonoAotCompilerMode.wasm, Skip = "AOT is broken")]
4945
public void WasmSupportsInProcessDiagnosers(MonoAotCompilerMode aotCompilerMode)
5046
{
51-
if (SkipTestRun())
52-
{
53-
return;
54-
}
55-
5647
try
5748
{
5849
var diagnoser = new MockInProcessDiagnoser1(BenchmarkDotNet.Diagnosers.RunMode.NoOverhead);
@@ -69,14 +60,9 @@ public void WasmSupportsInProcessDiagnosers(MonoAotCompilerMode aotCompilerMode)
6960
}
7061
}
7162

72-
[Fact]
63+
[FactEnvSpecific(JsvuSkipReason, EnvRequirement.NonWindowsArm, EnvRequirement.NonLinuxArm)]
7364
public void WasmSupportsCustomMainJs()
7465
{
75-
if (SkipTestRun())
76-
{
77-
return;
78-
}
79-
8066
var summary = CanExecute<WasmBenchmark>(GetConfig(MonoAotCompilerMode.mini, true, true));
8167

8268
var artefactsPaths = summary.Reports.Single().GenerateResult.ArtifactsPaths;
@@ -85,14 +71,9 @@ public void WasmSupportsCustomMainJs()
8571
Directory.Delete(Path.GetDirectoryName(artefactsPaths.ProjectFilePath)!, true);
8672
}
8773

88-
[Fact]
74+
[FactEnvSpecific(JsvuSkipReason, EnvRequirement.NonWindowsArm, EnvRequirement.NonLinuxArm)]
8975
public void WasmSupportsNode()
9076
{
91-
if (SkipTestRun())
92-
{
93-
return;
94-
}
95-
9677
CanExecute<WasmBenchmark>(GetConfig(MonoAotCompilerMode.mini, javaScriptEngine: "node"));
9778
}
9879

@@ -115,17 +96,6 @@ private ManualConfig GetConfig(MonoAotCompilerMode aotCompilerMode, bool useMain
11596
.WithOption(ConfigOptions.GenerateMSBuildBinLog, false);
11697
}
11798

118-
private static bool SkipTestRun()
119-
{
120-
// jsvu only supports arm for mac.
121-
if (RuntimeInformation.GetCurrentPlatform() != Platform.X64 && !OsDetector.IsMacOS())
122-
{
123-
return true;
124-
}
125-
126-
return false;
127-
}
128-
12999
public class WasmBenchmark
130100
{
131101
[Benchmark]

tests/BenchmarkDotNet.Tests/XUnit/EnvRequirement.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public enum EnvRequirement
66
NonWindows,
77
NonWindowsArm,
88
NonLinux,
9+
NonLinuxArm,
910
FullFrameworkOnly,
1011
NonFullFramework,
1112
DotNetCoreOnly,

tests/BenchmarkDotNet.Tests/XUnit/EnvRequirementChecker.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static class EnvRequirementChecker
1717
EnvRequirement.NonWindows => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? null : "Non-Windows test",
1818
EnvRequirement.NonWindowsArm => !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsArm() ? null : "Non-Windows+Arm test",
1919
EnvRequirement.NonLinux => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? null : "Non-Linux test",
20+
EnvRequirement.NonLinuxArm => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || !IsArm() ? null : "Non-Linux+Arm test",
2021
EnvRequirement.FullFrameworkOnly => BdnRuntimeInformation.IsFullFramework ? null : "Full .NET Framework-only test",
2122
EnvRequirement.NonFullFramework => !BdnRuntimeInformation.IsFullFramework ? null : "Non-Full .NET Framework test",
2223
EnvRequirement.DotNetCoreOnly => BdnRuntimeInformation.IsNetCore ? null : ".NET/.NET Core-only test",

0 commit comments

Comments
 (0)