Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
path = src/ChibiRuby.Compiler/mruby-compiler2
url = git@github.com:hadashiA/mruby-compiler2.git
branch = master
[submodule "sandbox/ChibiRuby.Benchmark/ruby/optcarrot"]
path = sandbox/ChibiRuby.Benchmark/ruby/optcarrot
url = git@github.com:mame/optcarrot.git
7 changes: 7 additions & 0 deletions sandbox/ChibiRuby.Benchmark/ChibiRuby.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@
<None Include="ruby\**\*.mrb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="ruby\**\*.rb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ruby\**\*.nes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ChibiRuby.Compiler\ChibiRuby.Compiler.csproj" />
<ProjectReference Include="..\..\src\ChibiRuby.NIO\ChibiRuby.NIO.csproj" />
<ProjectReference Include="..\..\src\ChibiRuby\ChibiRuby.csproj" />
</ItemGroup>

Expand Down
82 changes: 82 additions & 0 deletions sandbox/ChibiRuby.Benchmark/OptcarrotMrubyOriginalRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;

namespace ChibiRuby.Benchmark;

sealed class OptcarrotMrubyOriginalRunner(int frames = 180, bool printResult = false)
{
const string MrubyPathEnvironmentVariable = "CHIBIRUBY_BENCH_MRUBY";

public void Run()
{
var mrubyPath = ResolveMrubyPath();
if (!File.Exists(mrubyPath))
{
throw new InvalidOperationException(
"mruby original executable was not found. " +
$"Set {MrubyPathEnvironmentVariable}=/path/to/mruby, or build sandbox/ChibiRuby.Benchmark/mruby with " +
"MRUBY_CONFIG=../mruby_optcarrot_config.rb ./minirake.");
}

var startInfo = new ProcessStartInfo
{
FileName = mrubyPath,
WorkingDirectory = GetBenchmarkPath(Path.Join("ruby", "optcarrot")),
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
};

startInfo.ArgumentList.Add("tools/shim.rb");
startInfo.ArgumentList.Add("--headless");
startInfo.ArgumentList.Add("--quiet");
if (printResult)
{
startInfo.ArgumentList.Add("--print-fps");
startInfo.ArgumentList.Add("--print-video-checksum");
}
startInfo.ArgumentList.Add("--frames");
startInfo.ArgumentList.Add(frames.ToString(CultureInfo.InvariantCulture));
startInfo.ArgumentList.Add("examples/Lan_Master.nes");

using var process = Process.Start(startInfo)
?? throw new InvalidOperationException($"Failed to start mruby original executable: {mrubyPath}");

var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
process.WaitForExit();

var stdout = stdoutTask.GetAwaiter().GetResult();
var stderr = stderrTask.GetAwaiter().GetResult();

if (printResult)
{
Console.Write(stdout);
Console.Error.Write(stderr);
}

if (process.ExitCode != 0)
{
throw new InvalidOperationException(
$"mruby original optcarrot failed with exit code {process.ExitCode}.\n" +
stdout +
stderr);
}
}

static string ResolveMrubyPath()
{
var configuredPath = Environment.GetEnvironmentVariable(MrubyPathEnvironmentVariable);
return !string.IsNullOrWhiteSpace(configuredPath)
? configuredPath
: GetBenchmarkPath(Path.Join("mruby", "bin", "mruby"));
}

static string GetBenchmarkPath(string relativePath, [CallerFilePath] string callerFilePath = "")
{
return Path.Join(Path.GetDirectoryName(callerFilePath)!, relativePath);
}
}
134 changes: 133 additions & 1 deletion sandbox/ChibiRuby.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,109 @@
using System.Reflection;
using System;
using System.Diagnostics;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using ChibiRuby.Benchmark;

// Profiling-friendly mode: warm up the VM, then run the measured optcarrot
// workload many times in a tight loop so a sampling profiler (dotnet-trace)
// captures mostly steady-state execution rather than startup / JIT / warmup.
//
// --profile-optcarrot [frames] [warmupRuns] [iterations]
//
// Recommended capture (run against the built dll so dotnet-trace traces the
// app process directly, not the `dotnet run` launcher):
//
// dotnet build -c Release sandbox/ChibiRuby.Benchmark
// dotnet-trace collect --format speedscope \
// -- dotnet sandbox/ChibiRuby.Benchmark/bin/Release/net10.0/ChibiRuby.Benchmark.dll \
// --profile-optcarrot 180 3 30
//
// Open the resulting .speedscope.json at https://www.speedscope.app/ for a flamegraph.
// if (args is ["--profile-optcarrot", ..])
// {
// var frames = args.Length >= 2 && int.TryParse(args[1], out var parsedFrames)
// ? parsedFrames
// : 180;
// var warmupRuns = args.Length >= 3 && int.TryParse(args[2], out var parsedWarmupRuns)
// ? parsedWarmupRuns
// : 3;
// var iterations = args.Length >= 4 && int.TryParse(args[3], out var parsedIterations)
// ? parsedIterations
// : 30;
//
// using var loader = new RubyScriptLoader();
// loader.PreloadOptcarrotBenchmark(frames, printResult: false);
//
// Console.WriteLine($"[profile] warming up: {warmupRuns} run(s) x {frames} frames");
// for (var i = 0; i < warmupRuns; i++)
// {
// loader.RunChibiRuby();
// }
//
// Console.WriteLine($"[profile] measuring: {iterations} run(s) x {frames} frames");
// loader.ResetDispatchProfile();
// var gc0 = GC.CollectionCount(0);
// var gc1 = GC.CollectionCount(1);
// var gc2 = GC.CollectionCount(2);
// var alloc0 = GC.GetTotalAllocatedBytes(precise: true);
// var pause0 = GC.GetTotalPauseDuration();
// var sw = Stopwatch.StartNew();
// for (var i = 0; i < iterations; i++)
// {
// loader.RunChibiRuby();
// }
// sw.Stop();
// var allocated = GC.GetTotalAllocatedBytes(precise: true) - alloc0;
// var pause = GC.GetTotalPauseDuration() - pause0;
//
// var totalFrames = (long)frames * iterations;
// var fps = totalFrames / sw.Elapsed.TotalSeconds;
// Console.WriteLine(
// $"[profile] done: {totalFrames} frames in {sw.Elapsed.TotalSeconds:F3}s => {fps:F2} fps " +
// $"({sw.Elapsed.TotalMilliseconds / iterations:F2} ms/run)");
// Console.WriteLine(
// $"[profile] GC: gen0={GC.CollectionCount(0) - gc0} gen1={GC.CollectionCount(1) - gc1} " +
// $"gen2={GC.CollectionCount(2) - gc2} pause={pause.TotalMilliseconds:F1}ms " +
// $"({pause.TotalMilliseconds / sw.Elapsed.TotalMilliseconds * 100:F1}% of wall)");
// Console.WriteLine(
// $"[profile] alloc: {allocated / 1024.0 / 1024.0:F1} MB total, " +
// $"{allocated / (double)totalFrames / 1024.0:F1} KB/frame");
// Console.Write(loader.DumpDispatchProfile());
// return;
// }

if (args is ["--quick-optcarrot", ..])
{
var frames = args.Length >= 2 && int.TryParse(args[1], out var parsedFrames)
? parsedFrames
: 180;
var warmupRuns = args.Length >= 3 && int.TryParse(args[2], out var parsedWarmupRuns)
? parsedWarmupRuns
: 0;
using var loader = new RubyScriptLoader();
loader.PreloadOptcarrotBenchmark(frames, printResult: warmupRuns == 0);
for (var i = 0; i < warmupRuns; i++)
{
loader.RunChibiRuby();
}
if (warmupRuns > 0)
{
loader.PreloadOptcarrotRun(frames);
}
loader.RunChibiRuby();
return;
}

if (args is ["--quick-optcarrot-mruby", ..])
{
var frames = args.Length >= 2 && int.TryParse(args[1], out var parsedFrames)
? parsedFrames
: 180;
new OptcarrotMrubyOriginalRunner(frames, printResult: true).Run();
return;
}

BenchmarkSwitcher.FromAssembly(Assembly.GetEntryAssembly()!).Run(args);

[Config(typeof(BenchmarkConfig))]
Expand All @@ -16,3 +117,34 @@ public class MandelbrotBenchmark() : MRubyBenchmarkBase("bm_so_mandelbrot.rb");

[Config(typeof(BenchmarkConfig))]
public class AoRenderBenchmark() : MRubyBenchmarkBase("bm_ao_render.rb");

[Config(typeof(BenchmarkConfig))]
public class OptcarrotBenchmark
{
readonly RubyScriptLoader scriptLoader = new();
readonly OptcarrotMrubyOriginalRunner mrubyOriginalRunner = new();

[GlobalSetup]
public void LoadScript()
{
scriptLoader.PreloadOptcarrotBenchmark(printResult: false);
}

[GlobalCleanup]
public void GlobalCleanup()
{
scriptLoader.Dispose();
}

[Benchmark]
public void ChibiRuby()
{
scriptLoader.RunChibiRuby();
}

[Benchmark]
public void MRubyOriginal()
{
mrubyOriginalRunner.Run();
}
}
Loading
Loading