Skip to content

Commit e74fb46

Browse files
committed
Move benchmarks to src/Benchmarks and use Sample record for params
- Relocate Go.Benchmarks project under src/Benchmarks/Benchmarks.csproj - Update go.slnx reference - Change sample parameter from string path to record { Name, App } - Name is the directory name (for display in reports) - App is the app.cs path used for execution - ToString() returns Name so benchmark columns show short names
1 parent bc422c5 commit e74fb46

3 files changed

Lines changed: 182 additions & 0 deletions

File tree

go.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<Solution>
22
<Project Path="src/go/go.csproj" />
33
<Project Path="src/Tests/Tests.csproj" />
4+
<Project Path="src/Benchmarks/Benchmarks.csproj" />
45
</Solution>

src/Benchmarks/Benchmarks.csproj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
13+
</ItemGroup>
14+
15+
</Project>

src/Benchmarks/Program.cs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Jobs;
6+
using BenchmarkDotNet.Running;
7+
8+
var artifactsDir = Path.GetFullPath(Path.Combine(
9+
AppContext.BaseDirectory, "..", "..", "..", "BenchmarkDotNet.Artifacts"));
10+
11+
var config = DefaultConfig.Instance
12+
.WithArtifactsPath(artifactsDir);
13+
14+
BenchmarkRunner.Run<GoBenchmarks>(config);
15+
16+
public record Sample(string Name, string App)
17+
{
18+
public override string ToString() => Name;
19+
}
20+
21+
[Config(typeof(BenchmarkConfig))]
22+
public class GoBenchmarks
23+
{
24+
class BenchmarkConfig : ManualConfig
25+
{
26+
public BenchmarkConfig()
27+
{
28+
// Use fewer iterations because these are high-level process invocations (hundreds of ms to seconds)
29+
AddJob(Job.Default
30+
.WithLaunchCount(1)
31+
.WithWarmupCount(1)
32+
.WithIterationCount(3)
33+
.WithInvocationCount(1)
34+
.WithUnrollFactor(1));
35+
}
36+
}
37+
38+
string _repoRoot = null!;
39+
const string _dotnet = "dotnet";
40+
41+
[GlobalSetup]
42+
public void GlobalSetup()
43+
{
44+
_repoRoot = FindRepoRoot();
45+
46+
// Require that `dotnet go` works (the tool must be installed in a way that `dotnet go --version` succeeds).
47+
// This avoids any dnx overhead in the measurements.
48+
var (exit, stdout, stderr) = RunProcessCapture(_dotnet, ["go", "--version"]);
49+
if (exit != 0)
50+
{
51+
throw new InvalidOperationException(
52+
$"`dotnet go --version` must succeed (exit {exit}).\n" +
53+
$"stdout: {stdout}\nstderr: {stderr}\n" +
54+
"Install the 'go' tool (e.g. from local package) so that `dotnet go` is available.");
55+
}
56+
57+
Console.WriteLine($"[setup] dotnet go --version: {stdout.Trim()}");
58+
Console.WriteLine($"[setup] samples root: {_repoRoot}");
59+
}
60+
61+
static string FindRepoRoot()
62+
{
63+
var dir = AppContext.BaseDirectory;
64+
while (!string.IsNullOrEmpty(dir) && dir.Length > 3)
65+
{
66+
if (File.Exists(Path.Combine(dir, "go.slnx")))
67+
return dir;
68+
// Also accept if we see the samples folder as we walk up
69+
if (Directory.Exists(Path.Combine(dir, "samples")) && File.Exists(Path.Combine(dir, "readme.md")))
70+
return dir;
71+
dir = Path.GetDirectoryName(dir);
72+
}
73+
74+
// Fallback: assume running from inside the repo tree
75+
var fallback = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
76+
return fallback;
77+
}
78+
79+
[ParamsSource(nameof(GetSamples))]
80+
public Sample Sample { get; set; } = default!;
81+
82+
public IEnumerable<Sample> GetSamples()
83+
{
84+
var root = _repoRoot ?? FindRepoRoot();
85+
var samplesDir = Path.Combine(root, "samples");
86+
if (!Directory.Exists(samplesDir))
87+
yield break;
88+
89+
foreach (var full in Directory.GetFiles(samplesDir, "app.cs", SearchOption.AllDirectories).OrderBy(p => p))
90+
{
91+
var name = Path.GetFileName(Path.GetDirectoryName(full))!;
92+
yield return new Sample(name, full);
93+
}
94+
}
95+
96+
string GetFullSamplePath() => Sample.App;
97+
98+
string GetSampleDirectory() => Path.GetDirectoryName(GetFullSamplePath())!;
99+
100+
string GetPublishedExePath()
101+
{
102+
var dir = GetSampleDirectory();
103+
var baseName = Path.Combine(dir, "artifacts", "app", "app");
104+
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? baseName + ".exe" : baseName;
105+
}
106+
107+
[Benchmark(Description = "dnx go")]
108+
public void DnxGo()
109+
{
110+
var (exit, _, err) = RunProcessCapture(_dotnet, ["go", GetFullSamplePath()]);
111+
if (exit != 0) throw new Exception($"dnx go failed: {err}");
112+
}
113+
114+
[Benchmark(Description = "dotnet publish")]
115+
public void DotnetPublish()
116+
{
117+
// Run publish (this measures the full publish cost + run of resulting exe)
118+
var sample = GetFullSamplePath();
119+
var (pubExit, _, pubErr) = RunProcessCapture(_dotnet, ["publish", sample, "--nologo", "-v", "q"]);
120+
if (pubExit != 0) throw new Exception($"dotnet publish failed: {pubErr}");
121+
122+
var exe = GetPublishedExePath();
123+
if (!File.Exists(exe))
124+
throw new FileNotFoundException($"Expected published executable not found at {exe}");
125+
126+
var (runExit, _, runErr) = RunProcessCapture(exe, []);
127+
if (runExit != 0) throw new Exception($"published exe failed: {runErr}");
128+
}
129+
130+
[Benchmark(Description = "dnx go dev")]
131+
public void DnxGoDev()
132+
{
133+
var (exit, _, err) = RunProcessCapture(_dotnet, ["go", "dev", GetFullSamplePath()]);
134+
if (exit != 0) throw new Exception($"dnx go dev failed: {err}");
135+
}
136+
137+
[Benchmark(Description = "dotnet run")]
138+
public void DotnetRun()
139+
{
140+
var (exit, _, err) = RunProcessCapture(_dotnet, ["run", GetFullSamplePath()]);
141+
if (exit != 0) throw new Exception($"dotnet run failed: {err}");
142+
}
143+
144+
static (int ExitCode, string StdOut, string StdErr) RunProcessCapture(string fileName, string[] args)
145+
{
146+
var psi = new ProcessStartInfo
147+
{
148+
FileName = fileName,
149+
RedirectStandardOutput = true,
150+
RedirectStandardError = true,
151+
UseShellExecute = false,
152+
CreateNoWindow = true,
153+
};
154+
155+
foreach (var a in args)
156+
psi.ArgumentList.Add(a);
157+
158+
using var process = Process.Start(psi) ?? throw new InvalidOperationException($"Could not start process: {fileName}");
159+
160+
var stdout = process.StandardOutput.ReadToEnd();
161+
var stderr = process.StandardError.ReadToEnd();
162+
process.WaitForExit();
163+
164+
return (process.ExitCode, stdout, stderr);
165+
}
166+
}

0 commit comments

Comments
 (0)