diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 937af059..c7884d8f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,9 +36,6 @@ jobs:
run: dotnet restore Lua.slnx
- name: Test
run: dotnet test tests/Lua.Tests/Lua.Tests.csproj -c Release --filter "TestCategory!=ExpectedFailure"
- - name: Verify interpreter benchmark workloads
- working-directory: sandbox/Benchmark
- run: dotnet run --project Benchmark.csproj -c Release --no-restore -- --verify
- name: Test debugger protocol
run: dotnet test tests/Lua.Debugger.Dap.Tests/Lua.Debugger.Dap.Tests.csproj -c Release --no-restore
- name: Test debugger controls and hosting
diff --git a/.gitignore b/.gitignore
index 58f81391..1fdf077a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
.DS_Store
.claude/settings.local.json
artifacts/
+BenchmarkDotNet.Artifacts/
+.bdn/
[Bb]in/
[Oo]bj/
diff --git a/global.json b/global.json
new file mode 100644
index 00000000..d46d21e5
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "10.0.100",
+ "rollForward": "latestFeature",
+ "allowPrerelease": false
+ }
+}
diff --git a/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj b/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj
index 80c766fb..d670ff90 100644
--- a/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj
+++ b/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj
@@ -8,8 +8,20 @@
-
-
-
+
+
+
+
+ Benchmark.Scripts.Comparison.%(Filename)%(Extension)
+
+
+ Benchmark.Scripts.Compilation.%(Filename)%(Extension)
+
diff --git a/sandbox/Benchmark.NativeAot/Program.cs b/sandbox/Benchmark.NativeAot/Program.cs
index 48dbb969..87a6a8fd 100644
--- a/sandbox/Benchmark.NativeAot/Program.cs
+++ b/sandbox/Benchmark.NativeAot/Program.cs
@@ -1,34 +1,5 @@
using BenchmarkDotNet.Running;
-if (args.Contains("--verify"))
-{
- foreach (
- var workload in ComparisonWorkloads
- .Cases.Concat(ComparisonWorkloads.CompilationCases)
- .DistinctBy(c => c.Name)
- )
- RuntimeVerification.Verify(workload);
- return;
-}
-
BenchmarkSwitcher
.FromTypes([typeof(RuntimeExecutionComparison), typeof(RuntimeCompilationComparison)])
.Run(args);
-
-static class RuntimeVerification
-{
- public static void Verify(ComparisonCase workload)
- {
- foreach (var bytes in new[] { false, true })
- {
- using var runner = new LuaCSharpComparison(workload.Source, bytes);
- for (var i = 0; i < 2; i++)
- ComparisonWorkloads.Check(
- workload,
- bytes ? "Lua-CSharp bytes" : "Lua-CSharp",
- runner.Run()
- );
- }
- Console.WriteLine($"PASS {workload.Name}: {workload.Expected:R}");
- }
-}
diff --git a/sandbox/Benchmark.NativeAot/README.md b/sandbox/Benchmark.NativeAot/README.md
index f24e3393..9dd693b2 100644
--- a/sandbox/Benchmark.NativeAot/README.md
+++ b/sandbox/Benchmark.NativeAot/README.md
@@ -1,7 +1,7 @@
# .NET 10 JIT and NativeAOT benchmarks
Compare Lua-CSharp source compilation and bytecode execution under .NET 10 JIT
-and NativeAOT, using the sources and checksum checks from `../Benchmark`.
+and NativeAOT, using the Lua sources from `../Benchmark`.
NativeAOT compiles the C# interpreter; Lua source is still compiled to Lua
bytecode and interpreted.
@@ -10,7 +10,6 @@ Run from this directory:
```powershell
dotnet restore Benchmark.NativeAot.csproj
dotnet build Benchmark.NativeAot.csproj -c Release --no-restore
-dotnet bin/Release/net10.0/Benchmark.NativeAot.dll --verify
dotnet bin/Release/net10.0/Benchmark.NativeAot.dll --filter '*RuntimeExecutionComparison*numeric-loop*' --exporters json
dotnet bin/Release/net10.0/Benchmark.NativeAot.dll --filter '*RuntimeCompilationComparison*numeric-loop*' --exporters json
```
@@ -26,9 +25,11 @@ uses speed optimization and the current CPU's native instruction set. Record
the resolved JIT runtime version too. These native executables target the build
machine's CPU capabilities.
-`--verify` checks all 15 distinct workloads in the managed host, twice in each
-string mode. Each benchmark's setup also runs its checksum checks inside the
-actual JIT or NativeAOT benchmark process before timing. Timed execution uses
+The shared `Scripts/Comparison/` sources include string-keyed map operations
+and Mandelbrot escape iterations. Filter with `*map*` or `*mandelbrot*` to run
+either case.
+
+Timed execution uses
UTF-16 mode with ASCII inputs, retained state and a compiled chunk. Compilation
has six source cases, a fresh state outside each batch of 64 loads, and no Lua
execution during measurement. Both jobs use three launches. Compilation warms
diff --git a/sandbox/Benchmark.NativeAot/RuntimeComparison.cs b/sandbox/Benchmark.NativeAot/RuntimeComparison.cs
index 8c7d1d1d..44bd01b9 100644
--- a/sandbox/Benchmark.NativeAot/RuntimeComparison.cs
+++ b/sandbox/Benchmark.NativeAot/RuntimeComparison.cs
@@ -75,7 +75,6 @@ public class RuntimeExecutionComparison
public void Setup()
{
var workload = ComparisonWorkloads.Find(Case);
- RuntimeVerification.Verify(workload);
runner = new(workload.Source);
}
@@ -102,7 +101,6 @@ public class RuntimeCompilationComparison
public void Setup()
{
var workload = ComparisonWorkloads.FindCompilation(Case);
- RuntimeVerification.Verify(workload);
source = workload.Source;
var timer = Stopwatch.StartNew();
do
diff --git a/sandbox/Benchmark/AddBenchmark.cs b/sandbox/Benchmark/AddBenchmark.cs
deleted file mode 100644
index a3df673c..00000000
--- a/sandbox/Benchmark/AddBenchmark.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System.Reflection;
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Standard;
-using MoonSharp.Interpreter;
-
-[Config(typeof(BenchmarkConfig))]
-public class AddBenchmark
-{
- BenchmarkCore core = new();
- LuaValue[] buffer = new LuaValue[1];
-
- public static double Add(double x, double y)
- {
- return x + y;
- }
-
- [IterationSetup]
- public void Setup()
- {
- core = new();
- core.Setup("add.lua");
- core.LuaCSharpState.OpenStandardLibraries();
-
- core.LuaCSharpState.Environment["add"] = new LuaFunction(
- "add",
- (context, ct) =>
- {
- var a = context.GetArgument(0);
- var b = context.GetArgument(1);
- return new(context.Return(a + b));
- }
- );
- core.MoonSharpState.Globals["add"] = (Func)Add;
- core.NLuaState.RegisterFunction(
- "add",
- typeof(AddBenchmark).GetMethod(nameof(Add), BindingFlags.Static | BindingFlags.Public)
- );
- }
-
- [IterationCleanup]
- public void Cleanup()
- {
- core.Dispose();
- core = default!;
- GC.Collect();
- }
-
- [Benchmark(Description = "MoonSharp (RunString)")]
- public DynValue Benchmark_MoonSharp_String()
- {
- return core.MoonSharpState.DoString(core.SourceText);
- }
-
- [Benchmark(Description = "MoonSharp (RunFile)")]
- public DynValue Benchmark_MoonSharp_File()
- {
- return core.MoonSharpState.DoFile(core.FilePath);
- }
-
- [Benchmark(Description = "NLua (DoString)")]
- public object[] Benchmark_NLua_String()
- {
- return core.NLuaState.DoString(core.SourceText);
- }
-
- [Benchmark(Description = "NLua (DoFile)")]
- public object[] Benchmark_NLua_File()
- {
- return core.NLuaState.DoFile(core.FilePath);
- }
-
- [Benchmark(Description = "Lua-CSharp (DoString)")]
- public async Task Benchmark_LuaCSharp_String()
- {
- await core.LuaCSharpState.DoStringAsync(core.SourceText, buffer);
- return buffer[0];
- }
-
- [Benchmark(Description = "Lua-CSharp (DoFileAsync)")]
- public async Task Benchmark_LuaCSharp_File()
- {
- await core.LuaCSharpState.DoFileAsync(core.FilePath, buffer);
- return buffer[0];
- }
-}
diff --git a/sandbox/Benchmark/Benchmark.csproj b/sandbox/Benchmark/Benchmark.csproj
index 1fc13074..3445e82a 100644
--- a/sandbox/Benchmark/Benchmark.csproj
+++ b/sandbox/Benchmark/Benchmark.csproj
@@ -1,30 +1,24 @@
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Benchmark.Scripts.$([System.String]::Copy('%(RecursiveDir)').Replace('\', '.').Replace('/', '.'))%(Filename)%(Extension)
+
+
+
diff --git a/sandbox/Benchmark/Benchmark.sln b/sandbox/Benchmark/Benchmark.sln
index 2b03bb29..8f890c5a 100644
--- a/sandbox/Benchmark/Benchmark.sln
+++ b/sandbox/Benchmark/Benchmark.sln
@@ -5,12 +5,6 @@ VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "Benchmark.csproj", "{ECA61AB3-09C9-4990-8201-2E83BB9FD48B}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bin", "bin", "{B33416D1-661B-461E-95CD-C3BCC7DDC23F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Release", "Release", "{0A15FAB8-2749-4DF1-B1B3-925E6CD29AB1}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net8.0", "net8.0", "{2308BEED-4D71-4415-B9AC-269272953282}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -25,10 +19,6 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {0A15FAB8-2749-4DF1-B1B3-925E6CD29AB1} = {B33416D1-661B-461E-95CD-C3BCC7DDC23F}
- {2308BEED-4D71-4415-B9AC-269272953282} = {0A15FAB8-2749-4DF1-B1B3-925E6CD29AB1}
- EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F4ABD181-CD81-474C-93FF-CD4E6F93197A}
EndGlobalSection
diff --git a/sandbox/Benchmark/BenchmarkConfig.cs b/sandbox/Benchmark/BenchmarkConfig.cs
new file mode 100644
index 00000000..652ff8b6
--- /dev/null
+++ b/sandbox/Benchmark/BenchmarkConfig.cs
@@ -0,0 +1,69 @@
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.ConsoleArguments;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Environments;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Loggers;
+using BenchmarkDotNet.Toolchains.NetCoreApp;
+using CommandLine;
+
+static class BenchmarkConfig
+{
+ public static IConfig Create(ref string[] args)
+ {
+ var defaults = ManualConfig
+ .Create(DefaultConfig.Instance)
+ .AddDiagnoser(MemoryDiagnoser.Default);
+ var (success, parsed, options) = ConfigParser.Parse(args, NullLogger.Instance, defaults);
+ if (
+ !success
+ || !options!.Runtimes.Any(runtime => CoreRuntime.Parse(runtime) == CoreRuntime.Core11_0)
+ )
+ return defaults;
+
+ var effective = ManualConfig.Union(defaults, parsed!).CreateImmutableConfig();
+ var net11 = new Net11RuntimeToolchain(
+ new NetCoreAppSettings
+ {
+ CliPath = options.CliPath,
+ PackagesPath = options.RestorePath,
+ UseFreshPackages = options.UseFreshPackages,
+ }
+ );
+ var jobs = effective
+ .GetJobs()
+ .Select(job =>
+ job.Infrastructure.Runtime == CoreRuntime.Core11_0 ? job.WithToolchain(net11) : job
+ )
+ .ToArray();
+ var first = jobs[0];
+ // Copy settings without job metadata, so the template cannot overwrite baselines.
+ var template = new Job([
+ first.Environment,
+ first.Run,
+ first.Infrastructure,
+ first.Accuracy,
+ ]);
+ template.Infrastructure.Runtime = null;
+
+ // Runtime jobs are already configured; let BenchmarkDotNet handle the remaining CLI normally.
+ options.Runtimes = [];
+ args = Parser
+ .Default.FormatCommandLineArgs(options, settings => settings.SkipDefault = true)
+ .ToArray();
+ return new RuntimeJobs(effective, [.. jobs, template.AsDefault()]);
+ }
+
+ sealed class RuntimeJobs : ManualConfig, IConfig
+ {
+ readonly Job[] jobs;
+
+ public RuntimeJobs(IConfig source, Job[] jobs)
+ {
+ Add(source);
+ this.jobs = jobs;
+ }
+
+ IEnumerable IConfig.GetJobs() => jobs;
+ }
+}
diff --git a/sandbox/Benchmark/BenchmarkCore.cs b/sandbox/Benchmark/BenchmarkCore.cs
deleted file mode 100644
index c07a5beb..00000000
--- a/sandbox/Benchmark/BenchmarkCore.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using Lua;
-using MoonSharp.Interpreter;
-
-public class BenchmarkCore : IDisposable
-{
- public NLua.Lua NLuaState => nLuaState;
- public Script MoonSharpState => moonSharpState;
- public LuaState LuaCSharpState => luaCSharpState;
- public string FilePath => filePath;
- public string SourceText => sourceText;
-
- NLua.Lua nLuaState = default!;
- Script moonSharpState = default!;
- LuaState luaCSharpState = default!;
- string filePath = default!;
- string sourceText = default!;
-
- public void Setup(string fileName)
- {
- // moonsharp
- moonSharpState = new();
- Script.WarmUp();
-
- // NLua
- nLuaState = new();
-
- // Lua-CSharp
- luaCSharpState = LuaState.Create();
-
- filePath = FileHelper.GetAbsolutePath(fileName);
- sourceText = File.ReadAllText(filePath);
- }
-
- public void Dispose()
- {
- luaCSharpState.Dispose();
- nLuaState.Dispose();
- }
-}
diff --git a/sandbox/Benchmark/ByteStringIoBenchmark.cs b/sandbox/Benchmark/ByteStringIoBenchmark.cs
deleted file mode 100644
index d961231e..00000000
--- a/sandbox/Benchmark/ByteStringIoBenchmark.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.IO;
-
-[Config(typeof(BenchmarkConfig))]
-public class ByteStringIoBenchmark
-{
- [Params(LuaStringMode.Utf16, LuaStringMode.Bytes)]
- public LuaStringMode StringMode { get; set; }
-
- [Params(1, 4096)]
- public int Size { get; set; }
-
- MemoryStream input = default!;
- MemoryStream output = default!;
- LuaStream reader = default!;
- LuaStream writer = default!;
- string text = default!;
-
- [GlobalSetup]
- public void Setup()
- {
- text = new string('a', Size);
- input = new MemoryStream(Enumerable.Repeat((byte)'a', Size).ToArray());
- output = new MemoryStream(Size);
- reader = new LuaStream(LuaFileOpenMode.Read, input, StringMode);
- writer = new LuaStream(LuaFileOpenMode.Write, output, StringMode);
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- reader.Dispose();
- writer.Dispose();
- }
-
- [Benchmark]
- public string? Read()
- {
- reader.Seek(SeekOrigin.Begin, 0);
- return reader.ReadAsync(Size, default).GetAwaiter().GetResult();
- }
-
- [Benchmark]
- public void Write()
- {
- output.Position = 0;
- writer.WriteAsync(text.AsMemory(), default).GetAwaiter().GetResult();
- }
-}
diff --git a/sandbox/Benchmark/BytecodeInteropVerification.cs b/sandbox/Benchmark/BytecodeInteropVerification.cs
deleted file mode 100644
index 24186244..00000000
--- a/sandbox/Benchmark/BytecodeInteropVerification.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using Lua;
-using Lua.Platforms;
-using Lua.Standard;
-using NuLua;
-using NuLua.Lua52;
-using LuaFunction = Lua.LuaFunction;
-using LuaTable = Lua.LuaTable;
-
-static class BytecodeInteropVerification
-{
- static readonly ComparisonCase Closures = new(
- "dump-closures",
- "Bytecode",
- """
- local value=10
- local function make(step)
- return function(...)
- value=value+step
- local first=...
- return value+select('#',...)+first
- end
- end
- local a,b=make(3),make(5)
- assert(a(4)==18 and b(7,8)==27)
- return a(20)
- """,
- 42
- );
-
- static readonly ComparisonCase BinaryStrings = new(
- "dump-binary-strings",
- "Bytecode",
- """
- local bytes='\0\128\255'
- local utf8='\225\186\191'
- local function check()
- assert(#bytes==3 and bytes:byte(1)==0 and bytes:byte(2)==128 and bytes:byte(3)==255)
- assert(#utf8==3 and utf8:byte(1)==225 and utf8:byte(2)==186 and utf8:byte(3)==191)
- assert(''==string.sub(bytes,1,0))
- assert(true and not false and nil==nil)
- return 42
- end
- return check()
- """,
- 42
- );
-
- public static async Task VerifyAsync()
- {
- var buffer = new byte[1024 * 1024];
- foreach (
- var workload in ComparisonWorkloads
- .Cases.Concat(ComparisonWorkloads.CompilationCases)
- .Append(Closures)
- .Append(BinaryStrings)
- .DistinctBy(c => c.Name)
- )
- {
- using var native = Lua52State.Create();
- native.OpenLibraries();
- native.RegisterFunction(
- "host_add",
- (vm, args) =>
- {
- vm.PushNumber(args[0].Read() + args[1].Read());
- return 1;
- }
- );
- native.LoadString(workload.Source, "@bytecode");
- if (!native.TryDump(1, false, buffer, out var written))
- throw new InvalidOperationException(
- $"Native dump exceeds verification buffer: {workload.Name}"
- );
- native.SetTop(0);
-
- foreach (var mode in new[] { LuaStringMode.Utf16, LuaStringMode.Bytes })
- {
- // Arbitrary bytes have the same semantics as C Lua in byte mode.
- if (workload == BinaryStrings && mode != LuaStringMode.Bytes)
- continue;
- using var state = LuaState.Create(
- mode == LuaStringMode.Bytes
- ? LuaPlatform.DefaultByteStrings
- : LuaPlatform.Default
- );
- state.OpenStandardLibraries();
- state.Environment["host_add"] = new LuaFunction(
- (context, _) =>
- new(
- context.Return(
- context.GetArgument(0) + context.GetArgument(1)
- )
- )
- );
-
- var loaded = state.Load(buffer.AsSpan(0, written), "native", "b");
- var results = await state.ExecuteAsync(loaded);
- ComparisonWorkloads.Check(
- workload,
- $"NuLua dump -> Lua-CSharp {mode}",
- results.Single().Read()
- );
-
- var original = state.Load(workload.Source, "@bytecode");
- var dump = state.Environment["string"].Read()["dump"].Read();
- var dumped = (await state.CallAsync(dump, [original])).Single().Read();
- native.LoadString("return assert(load(...,'managed','b'))()", "@bytecode");
- native.PushString(LuaByteString.ToBytes(dumped));
- native.Call(1, 1);
- ComparisonWorkloads.Check(
- workload,
- $"Lua-CSharp {mode} dump -> NuLua",
- native.ToNumber(-1)
- );
- native.SetTop(0);
- }
- Console.WriteLine($"PASS Bytecode {workload.Name, -20} NuLua <-> Lua-CSharp");
- }
- }
-}
diff --git a/sandbox/Benchmark/ComparisonWorkloads.cs b/sandbox/Benchmark/ComparisonWorkloads.cs
deleted file mode 100644
index 74abd2db..00000000
--- a/sandbox/Benchmark/ComparisonWorkloads.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-using System.Reflection;
-
-public sealed record ComparisonCase(string Name, string Category, string Source, double Expected)
-{
- public override string ToString() => Name;
-}
-
-public static class ComparisonWorkloads
-{
- // ASCII input keeps the UTF-16, byte-string and native UTF-8 engines comparable.
- // Every invocation rebuilds its mutable Lua data and returns one numeric checksum.
- public static readonly ComparisonCase[] Cases =
- [
- new("numeric-loop", "VM", "local s=0; for i=1,20000 do s=s+i end; return s", 200010000),
- new(
- "calls-varargs",
- "VM",
- "local function f(a,...) local b,c=...; return a+b+c end; local s=0; for i=1,2000 do s=s+f(i,2,3) end; return s",
- 2011000
- ),
- new(
- "table-access",
- "Tables",
- "local t={}; for i=1,1000 do t[i]={x=i,y=i%7} end; local s=0; for i=1,1000 do local v=t[i]; s=s+v.x+v.y end; return s",
- 503503
- ),
- new(
- "metamethods",
- "VM",
- "local mt={__add=function(a,b) return a.x+b.x end,__index=function(t,k) return 5 end}; local a,b=setmetatable({x=3},mt),setmetatable({x=4},mt); local s=0; for i=1,2000 do s=s+(a+b)+a.missing end; return s",
- 24000
- ),
- new(
- "math",
- "Standard",
- "local abs,sqrt,min,max=math.abs,math.sqrt,math.min,math.max; local s=0; for i=1,2000 do s=s+min(abs(-i),sqrt(i))+max(i,3,7) end; return s",
- MathExpected()
- ),
- new(
- "strings",
- "Standard",
- "local rep,reverse,sub,byte,concat=string.rep,string.reverse,string.sub,string.byte,table.concat; local n=0; local t={'abc',12,'def',34}; for i=1,400 do local s=sub(reverse(rep('abcd',8,':')),2,-2); n=n+byte(s)+#s+#concat(t,',') end; return n",
- 59600
- ),
- new(
- "patterns",
- "Standard",
- "local gsub=string.gsub; local n=0; for i=1,400 do local s=gsub('hello 123 world 456','(%a+)', '[%1]'); n=n+#s end; return n",
- 9200
- ),
- new(
- "table-sort",
- "Tables",
- "local t={}; for i=1,300 do t[i]=301-i end; table.sort(t); return t[150]",
- 150
- ),
- new(
- "coroutines",
- "Coroutines",
- "local co=coroutine.create(function() for i=1,500 do coroutine.yield(i) end end); local s=0; for i=1,500 do local ok,x=coroutine.resume(co); assert(ok); s=s+x end; return s",
- 125250
- ),
- new(
- "host-calls",
- "Interop",
- "local add=host_add; local s=0; for i=1,2000 do s=add(s,i) end; return s",
- 2001000
- ),
- new("nbody", "Mixed", LoadNBody(), -0.1690507623824094),
- ];
-
- public static readonly ComparisonCase[] CompilationCases =
- [
- Find("numeric-loop"),
- Find("nbody"),
- new(
- "control-flow",
- "Compiler",
- "local total=0;"
- + string.Concat(
- Enumerable.Repeat(
- "do local function choose(value) if not value or value==false then return nil elseif value==true then return 1 else return value end end; "
- + "local value=choose(2); repeat value=value-1 until value==0; while value<2 do value=value+1; if value==1 then goto skip end; total=total+value; ::skip:: end; "
- + "for index=1,2 do if index==2 then break end; total=total+index end end;",
- 40
- )
- )
- + "return total",
- 120
- ),
- new(
- "identifiers",
- "Compiler",
- "local total=0;"
- + string.Concat(
- Enumerable
- .Range(0, 80)
- .Select(i =>
- $"do local returnValue{i},functionName{i},ordinaryIdentifier{i}=1,2,3; total=total+returnValue{i}+functionName{i}+ordinaryIdentifier{i} end;"
- )
- )
- + "return total",
- 480
- ),
- StringConstants(false),
- StringConstants(true),
- ];
-
- static ComparisonCase StringConstants(bool escaped)
- {
- var source = new System.Text.StringBuilder("local total=0;");
- var expected = 0;
- for (var i = 0; i < 80; i++)
- {
- var decoded = escaped
- ? $"escaped value {i}:\tcolumn\nline\r\a\b\f\v\\\"'AAsuffix"
- : $"ordinary string value {i}: the quick brown fox jumps over the lazy dog";
- var literal = escaped
- ? $"escaped value {i}:\\tcolumn\\nline\\r\\a\\b\\f\\v\\\\\\\"\\'\\x41\\065\\z suffix"
- : decoded;
- source.Append("do local value='").Append(literal).Append("'; total=total+#value end;");
- expected += decoded.Length;
- }
- source.Append("return total");
- return new(
- escaped ? "escaped-strings" : "plain-strings",
- "Compiler",
- source.ToString(),
- expected
- );
- }
-
- public static ComparisonCase FindCompilation(string name) =>
- CompilationCases.Single(c => c.Name == name);
-
- static double MathExpected()
- {
- double sum = 0;
- for (var i = 1; i <= 2000; i++)
- sum += Math.Sqrt(i) + Math.Max(i, 7);
- return sum;
- }
-
- static string LoadNBody()
- {
- using var stream = Assembly
- .GetExecutingAssembly()
- .GetManifestResourceStream("Comparison.n-body.lua")!;
- using var reader = new StreamReader(stream);
- return reader.ReadToEnd().Replace("or 1000", "or 100") + "\nreturn energy(bodies,nbody)";
- }
-
- public static ComparisonCase Find(string name) => Cases.Single(c => c.Name == name);
-
- public static void Check(ComparisonCase workload, string engine, double actual)
- {
- var tolerance = Math.Max(1, Math.Abs(workload.Expected)) * 1e-10;
- if (!double.IsFinite(actual) || Math.Abs(actual - workload.Expected) > tolerance)
- throw new InvalidOperationException(
- $"{engine}/{workload.Name}: expected {workload.Expected:R}, got {actual:R}"
- );
- }
-}
diff --git a/sandbox/Benchmark/ComparisonEngines.cs b/sandbox/Benchmark/Comparisons/ComparisonEngines.cs
similarity index 89%
rename from sandbox/Benchmark/ComparisonEngines.cs
rename to sandbox/Benchmark/Comparisons/ComparisonEngines.cs
index 8d535bff..b3b299d4 100644
--- a/sandbox/Benchmark/ComparisonEngines.cs
+++ b/sandbox/Benchmark/Comparisons/ComparisonEngines.cs
@@ -27,10 +27,6 @@ public NuLua52Comparison(string source)
{
state = Lua52State.Create();
state.OpenLibraries();
- state.GetGlobal("_VERSION");
- if (state.ToString(-1) != "Lua 5.2")
- throw new InvalidOperationException("Expected native Lua 5.2");
- state.SetTop(0);
state.RegisterFunction(
"host_add",
(vm, args) =>
diff --git a/sandbox/Benchmark/CompilationComparison.cs b/sandbox/Benchmark/Comparisons/CompilationComparison.cs
similarity index 71%
rename from sandbox/Benchmark/CompilationComparison.cs
rename to sandbox/Benchmark/Comparisons/CompilationComparison.cs
index a7ab1a81..f487ec22 100644
--- a/sandbox/Benchmark/CompilationComparison.cs
+++ b/sandbox/Benchmark/Comparisons/CompilationComparison.cs
@@ -1,30 +1,11 @@
using System.Diagnostics;
using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Jobs;
using Lua;
using Lua.Runtime;
using Lua.Standard;
using MoonSharp.Interpreter;
using NuLua.Lua52;
-public sealed class CompilationConfig : ManualConfig
-{
- public CompilationConfig()
- {
- AddDiagnoser(MemoryDiagnoser.Default);
- AddJob(
- Job.ShortRun.WithLaunchCount(3)
- .WithWarmupCount(20)
- .WithIterationCount(20)
- .WithInvocationCount(1)
- .WithUnrollFactor(1)
- );
- }
-}
-
-[Config(typeof(CompilationConfig))]
[BenchmarkCategory("Compilation")]
public class CompilationComparison
{
@@ -44,8 +25,6 @@ public void SetupLua()
{
var workload = ComparisonWorkloads.FindCompilation(Case);
source = workload.Source;
- using (var runner = new LuaCSharpComparison(source))
- ComparisonWorkloads.Check(workload, "Lua-CSharp", runner.Run());
WarmCompiler(ResetLua, () => LuaCSharp(), CleanupLua);
}
@@ -54,8 +33,6 @@ public void SetupMoonSharp()
{
var workload = ComparisonWorkloads.FindCompilation(Case);
source = workload.Source;
- var runner = new MoonSharpComparison(source);
- ComparisonWorkloads.Check(workload, "MoonSharp", runner.Run());
WarmCompiler(ResetMoonSharp, () => MoonSharp(), CleanupMoonSharp);
}
@@ -64,8 +41,6 @@ public void SetupNuLua()
{
var workload = ComparisonWorkloads.FindCompilation(Case);
source = workload.Source;
- using (var runner = new NuLua52Comparison(source))
- ComparisonWorkloads.Check(workload, "NuLua Lua 5.2", runner.Run());
WarmCompiler(ResetNuLua, NuLua52, CleanupNuLua);
}
@@ -146,25 +121,3 @@ public void NuLua52()
[IterationCleanup(Target = nameof(MoonSharp))]
public void CleanupMoonSharp() => moon = default!;
}
-
-[Config(typeof(ComparisonConfig))]
-[BenchmarkCategory("Startup")]
-public class StartupComparison
-{
- [Benchmark(Baseline = true)]
- public void LuaCSharp()
- {
- using var state = LuaState.Create();
- state.OpenStandardLibraries();
- }
-
- [Benchmark]
- public Script MoonSharp() => new(CoreModules.Preset_Complete);
-
- [Benchmark]
- public void NuLua52()
- {
- using var state = Lua52State.Create();
- state.OpenLibraries();
- }
-}
diff --git a/sandbox/Benchmark/Comparisons/InterpreterComparison.cs b/sandbox/Benchmark/Comparisons/InterpreterComparison.cs
new file mode 100644
index 00000000..1e16c8a9
--- /dev/null
+++ b/sandbox/Benchmark/Comparisons/InterpreterComparison.cs
@@ -0,0 +1,49 @@
+using BenchmarkDotNet.Attributes;
+
+[BenchmarkCategory("Execution")]
+public class InterpreterComparison
+{
+ [ParamsSource(nameof(Cases))]
+ public string Case { get; set; } = "numeric-loop";
+ public static IEnumerable Cases => ComparisonWorkloads.Cases.Select(c => c.Name);
+
+ LuaCSharpComparison lua = default!;
+ MoonSharpComparison moonSharp = default!;
+ NuLua52Comparison nuLua = default!;
+
+ [GlobalSetup(Target = nameof(LuaCSharp))]
+ public void SetupLua()
+ {
+ var workload = ComparisonWorkloads.Find(Case);
+ lua = new(workload.Source);
+ }
+
+ [GlobalSetup(Target = nameof(MoonSharp))]
+ public void SetupMoonSharp()
+ {
+ var workload = ComparisonWorkloads.Find(Case);
+ moonSharp = new(workload.Source);
+ }
+
+ [GlobalSetup(Target = nameof(NuLua52))]
+ public void SetupNuLua()
+ {
+ var workload = ComparisonWorkloads.Find(Case);
+ nuLua = new(workload.Source);
+ }
+
+ [Benchmark(Baseline = true)]
+ public double LuaCSharp() => lua.Run();
+
+ [Benchmark]
+ public double MoonSharp() => moonSharp.Run();
+
+ [Benchmark]
+ public double NuLua52() => nuLua.Run();
+
+ [GlobalCleanup(Target = nameof(LuaCSharp))]
+ public void CleanupLua() => lua.Dispose();
+
+ [GlobalCleanup(Target = nameof(NuLua52))]
+ public void CleanupNuLua() => nuLua.Dispose();
+}
diff --git a/sandbox/Benchmark/LuaCSharpComparison.cs b/sandbox/Benchmark/Comparisons/LuaCSharpComparison.cs
similarity index 100%
rename from sandbox/Benchmark/LuaCSharpComparison.cs
rename to sandbox/Benchmark/Comparisons/LuaCSharpComparison.cs
diff --git a/sandbox/Benchmark/Comparisons/StartupComparison.cs b/sandbox/Benchmark/Comparisons/StartupComparison.cs
new file mode 100644
index 00000000..3eb2cc52
--- /dev/null
+++ b/sandbox/Benchmark/Comparisons/StartupComparison.cs
@@ -0,0 +1,26 @@
+using BenchmarkDotNet.Attributes;
+using Lua;
+using Lua.Standard;
+using MoonSharp.Interpreter;
+using NuLua.Lua52;
+
+[BenchmarkCategory("Startup")]
+public class StartupComparison
+{
+ [Benchmark(Baseline = true)]
+ public void LuaCSharp()
+ {
+ using var state = LuaState.Create();
+ state.OpenStandardLibraries();
+ }
+
+ [Benchmark]
+ public Script MoonSharp() => new(CoreModules.Preset_Complete);
+
+ [Benchmark]
+ public void NuLua52()
+ {
+ using var state = Lua52State.Create();
+ state.OpenLibraries();
+ }
+}
diff --git a/sandbox/Benchmark/CoroutineBenchmark.cs b/sandbox/Benchmark/CoroutineBenchmark.cs
deleted file mode 100644
index cbd66be6..00000000
--- a/sandbox/Benchmark/CoroutineBenchmark.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Standard;
-using MoonSharp.Interpreter;
-
-[Config(typeof(BenchmarkConfig))]
-public class CoroutineBenchmark
-{
- BenchmarkCore core = new();
- LuaValue[] buffer = new LuaValue[1];
-
- [GlobalSetup]
- public void GlobalSetup()
- {
- core.Setup("coroutine.lua");
- core.LuaCSharpState.OpenBasicLibrary();
- core.LuaCSharpState.OpenCoroutineLibrary();
- }
-
- [Benchmark(Description = "MoonSharp (RunString)")]
- public DynValue Benchmark_MoonSharp_String()
- {
- return core.MoonSharpState.DoString(core.SourceText);
- }
-
- [Benchmark(Description = "MoonSharp (RunFile)")]
- public DynValue Benchmark_MoonSharp_File()
- {
- return core.MoonSharpState.DoFile(core.FilePath);
- }
-
- [Benchmark(Description = "NLua (DoString)")]
- public object[] Benchmark_NLua_String()
- {
- return core.NLuaState.DoString(core.SourceText);
- }
-
- [Benchmark(Description = "NLua (DoFile)")]
- public object[] Benchmark_NLua_File()
- {
- return core.NLuaState.DoFile(core.FilePath);
- }
-
- [Benchmark(Description = "Lua-CSharp (DoString)")]
- public async Task Benchmark_LuaCSharp_String()
- {
- await core.LuaCSharpState.DoStringAsync(core.SourceText, buffer);
- return buffer[0];
- }
-
- [Benchmark(Description = "Lua-CSharp (DoFileAsync)")]
- public async Task Benchmark_LuaCSharp_File()
- {
- await core.LuaCSharpState.DoFileAsync(core.FilePath, buffer);
- return buffer[0];
- }
-}
diff --git a/sandbox/Benchmark/CoroutineTransferBenchmark.cs b/sandbox/Benchmark/CoroutineTransferBenchmark.cs
deleted file mode 100644
index f0bc2acb..00000000
--- a/sandbox/Benchmark/CoroutineTransferBenchmark.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Runtime;
-using Lua.Standard;
-
-[Config(typeof(ComparisonConfig))]
-public class CoroutineTransferBenchmark
-{
- [Params("resume", "wrap")]
- public string Operation { get; set; } = "resume";
-
- [Params(1, 64, 4096)]
- public int ResultCount { get; set; }
-
- [Params(false, true)]
- public bool Cancellable { get; set; }
-
- LuaState state = default!;
- LuaClosure closure = default!;
- CancellationTokenSource cancellation = default!;
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- state.OpenStandardLibraries();
- cancellation = new();
- state.Environment["packet"] = new LuaFunction(
- (context, _) =>
- {
- context.GetReturnBuffer(ResultCount).Fill(42);
- return new(ResultCount);
- }
- );
- closure = state.Load(
- Operation == "wrap"
- ? "local run=coroutine.wrap(function() for i=1,64 do coroutine.yield(packet()) end end); for i=1,64 do run() end; run(); return 42"
- : "local co=coroutine.create(function() for i=1,64 do coroutine.yield(packet()) end end); for i=1,64 do coroutine.resume(co) end; coroutine.resume(co); return 42",
- "coroutine-transfer"
- );
- if (Run() != 42)
- throw new InvalidOperationException("Unexpected coroutine transfer result");
- }
-
- [Benchmark(OperationsPerInvoke = 64)]
- public int Run()
- {
- state
- .RunAsync(closure, 0, Cancellable ? cancellation.Token : default)
- .GetAwaiter()
- .GetResult();
- return state.Stack.Pop().Read();
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- cancellation.Dispose();
- state.Dispose();
- }
-}
diff --git a/sandbox/Benchmark/DebuggerBenchmark.cs b/sandbox/Benchmark/DebuggerBenchmark.cs
deleted file mode 100644
index 1f9ce29f..00000000
--- a/sandbox/Benchmark/DebuggerBenchmark.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Debugging;
-using Lua.Runtime;
-using Lua.Standard;
-
-[Config(typeof(ComparisonConfig))]
-public class DebuggerBenchmark
-{
- [Params("numeric-loop", "calls", "tables", "metamethods", "coroutines", "n-body")]
- public string Workload { get; set; } = "numeric-loop";
-
- [Params(false, true)]
- public bool Attached { get; set; }
-
- LuaState state = default!;
- LuaClosure closure = default!;
- LuaDebugger? debugger;
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- state.OpenStandardLibraries();
- var source = Workload switch
- {
- "numeric-loop" => "local x=0;for i=1,10000 do x=x+i end;assert(x==50005000)",
- "calls" =>
- "local function f(x)return x+1 end;local x=0;for i=1,1000 do x=f(x)end;assert(x==1000)",
- "tables" => "local t={x=1};for i=1,1000 do t.x=t.x+1 end;assert(t.x==1001)",
- "metamethods" =>
- "local t=setmetatable({},{__add=function(a,b)return b+1 end});local x;for i=1,1000 do x=t+i end;assert(x==1001)",
- "coroutines" =>
- "local co=coroutine.create(function()for i=1,100 do coroutine.yield(i)end end);local x=0;for i=1,100 do local ok,v=coroutine.resume(co);assert(ok);x=x+v end;assert(x==5050)",
- "n-body" => ReadNBody(),
- _ => throw new InvalidOperationException(),
- };
- closure = state.Load(source, "=debugger-benchmark");
- if (Attached)
- {
- debugger = new LuaDebugger(state);
- debugger.Attach();
- }
- Run();
- }
-
- [Benchmark]
- public void Run() =>
- state.ExecuteAsync(closure, Memory.Empty).GetAwaiter().GetResult();
-
- static string ReadNBody()
- {
- using var source = typeof(DebuggerBenchmark).Assembly.GetManifestResourceStream(
- "Comparison.n-body.lua"
- )!;
- using var reader = new StreamReader(source);
- return reader.ReadToEnd();
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- debugger?.Dispose();
- state.Dispose();
- }
-}
diff --git a/sandbox/Benchmark/DebuggerBreakpointBenchmark.cs b/sandbox/Benchmark/DebuggerBreakpointBenchmark.cs
deleted file mode 100644
index 91ec4049..00000000
--- a/sandbox/Benchmark/DebuggerBreakpointBenchmark.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Debugging;
-using Lua.Runtime;
-using Lua.Standard;
-
-[Config(typeof(ComparisonConfig))]
-public class DebuggerBreakpointBenchmark
-{
- [Params("other-function", "cold-branch")]
- public string Location { get; set; } = "other-function";
-
- [Params(false, true)]
- public bool BreakpointEnabled { get; set; }
-
- LuaState state = default!;
- LuaClosure closure = default!;
- LuaDebugger debugger = default!;
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- state.OpenStandardLibraries();
- debugger = new LuaDebugger(state);
- debugger.Attach();
- closure = state.Load(
- Location == "other-function"
- ? "local function unused()\n return 0\nend\nlocal x=0;for i=1,10000 do x=x+i end;assert(x==50005000)"
- : "local x=0\nfor i=1,10000 do x=x+i end\nif x<0 then\n x=0\nend\nassert(x==50005000)",
- "=breakpoint-benchmark"
- );
- debugger.RegisterSource(closure);
- if (BreakpointEnabled)
- debugger.SetBreakpoints(
- "=breakpoint-benchmark",
- [Location == "other-function" ? 2 : 4]
- );
- Run();
- }
-
- [Benchmark]
- public void Run() =>
- state.ExecuteAsync(closure, Memory.Empty).GetAwaiter().GetResult();
-
- [GlobalCleanup]
- public void Cleanup()
- {
- debugger.Dispose();
- state.Dispose();
- }
-}
diff --git a/sandbox/Benchmark/DebuggerControlBenchmark.cs b/sandbox/Benchmark/DebuggerControlBenchmark.cs
deleted file mode 100644
index dae3b1cf..00000000
--- a/sandbox/Benchmark/DebuggerControlBenchmark.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Debugging;
-using Lua.Runtime;
-
-[Config(typeof(ComparisonConfig))]
-public class DebuggerControlBenchmark
-{
- [Params(1, 2000)]
- public int FunctionCount { get; set; }
-
- LuaState state = default!;
- LuaClosure source = default!;
- LuaDebugger debugger = default!;
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- debugger = new LuaDebugger(state);
- debugger.Attach();
- source = state.Load(
- "local t={}\n"
- + string.Join(
- "\n",
- Enumerable
- .Range(1, FunctionCount)
- .Select(i => $"t[{i}]=function() return {i} end")
- )
- + $"\nfor i=1,{FunctionCount} do t[i]() end",
- "=controls"
- );
- state.ExecuteAsync(source, Memory.Empty).GetAwaiter().GetResult();
- }
-
- [Benchmark]
- public void PauseAndReset()
- {
- debugger.Pause();
- debugger.Reset();
- }
-
- [GlobalCleanup]
- public void Cleanup()
- {
- debugger.Dispose();
- state.Dispose();
- }
-}
diff --git a/sandbox/Benchmark/DebuggerFirstUseBenchmark.cs b/sandbox/Benchmark/DebuggerFirstUseBenchmark.cs
deleted file mode 100644
index 5ed6db3b..00000000
--- a/sandbox/Benchmark/DebuggerFirstUseBenchmark.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Debugging;
-using Lua.Runtime;
-
-[Config(typeof(ComparisonConfig))]
-public class DebuggerFirstUseBenchmark
-{
- [Params(250, 1000, 2000)]
- public int FunctionCount { get; set; }
-
- [Params(false, true)]
- public bool BreakpointEnabled { get; set; }
-
- [Params(false, true)]
- public bool PreRegistered { get; set; }
-
- LuaState state = default!;
- LuaClosure source = default!;
- LuaClosure driver = default!;
- LuaDebugger debugger = default!;
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- var functions = string.Join(
- "\n",
- Enumerable.Range(1, FunctionCount).Select(i => $"t[{i}]=function() return {i} end")
- );
- source = state.Load(
- "local t={}\n" + functions + "\nlocal function unused()\n return 0\nend\nreturn t",
- "=first-use"
- );
- state.Environment["functions"] = state.ExecuteAsync(source).GetAwaiter().GetResult()[0];
- driver = state.Load($"for i=1,{FunctionCount} do functions[i]() end", "=driver");
- }
-
- [IterationSetup]
- public void Attach()
- {
- debugger = new LuaDebugger(state);
- debugger.Attach();
- if (PreRegistered)
- debugger.RegisterSource(source);
- if (BreakpointEnabled)
- debugger.SetBreakpoints("=first-use", [FunctionCount + 4]);
- }
-
- [Benchmark]
- public void FirstCalls() =>
- state.ExecuteAsync(driver, Memory.Empty).GetAwaiter().GetResult();
-
- [IterationCleanup]
- public void Detach() => debugger.Dispose();
-
- [GlobalCleanup]
- public void Cleanup() => state.Dispose();
-}
diff --git a/sandbox/Benchmark/FileHelper.cs b/sandbox/Benchmark/FileHelper.cs
deleted file mode 100644
index aa998f1d..00000000
--- a/sandbox/Benchmark/FileHelper.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System.Runtime.CompilerServices;
-
-public static class FileHelper
-{
- public static string GetAbsolutePath(
- string relativePath,
- [CallerFilePath] string callerFilePath = ""
- )
- {
- return Path.Combine(Path.GetDirectoryName(callerFilePath)!, relativePath);
- }
-}
diff --git a/sandbox/Benchmark/GSubBenchmark.cs b/sandbox/Benchmark/GSubBenchmark.cs
deleted file mode 100644
index dd74db5b..00000000
--- a/sandbox/Benchmark/GSubBenchmark.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System.Threading.Tasks.Sources;
-using BenchmarkDotNet.Attributes;
-using Lua;
-using Lua.Runtime;
-using Lua.Standard;
-
-[Config(typeof(BenchmarkConfig))]
-public class GSubBenchmark
-{
- [Params(
- "literal",
- "capture",
- "position",
- "frontier",
- "function",
- "async-function",
- "async-index"
- )]
- public string Replacement { get; set; } = "literal";
-
- LuaState state = default!;
- LuaClosure closure = default!;
- readonly LuaValue[] buffer = new LuaValue[2];
- readonly SuspendedReplacement suspension = new();
-
- [GlobalSetup]
- public void Setup()
- {
- state = LuaState.Create();
- state.OpenStandardLibraries();
- state.Environment["source"] = new string('a', 64);
- state.Environment["suspend"] = new LuaFunction(
- "suspend",
- (context, _) => suspension.Start(context.Return("R"))
- );
- state
- .DoStringAsync(
- "repl = setmetatable({}, {__index = suspend}); sync = function() return 'R' end"
- )
- .GetAwaiter()
- .GetResult();
- var script = Replacement switch
- {
- "literal" => "return string.gsub(source, 'a', 'R')",
- "capture" => "return string.gsub(source, '(a)', '[%1]%0%%')",
- "position" => "return string.gsub(source, '()a', '%1')",
- "frontier" => "return string.gsub(source, '%f[%z]', '.')",
- "function" => "return string.gsub('a', '.', sync)",
- "async-function" => "return string.gsub('a', '.', suspend)",
- "async-index" => "return string.gsub('a', '.', repl)",
- _ => throw new ArgumentOutOfRangeException(nameof(Replacement)),
- };
- closure = state.Load(script, "gsub-benchmark");
- }
-
- [GlobalCleanup]
- public void Cleanup() => state.Dispose();
-
- [Benchmark]
- public int GSub()
- {
- var task = state.ExecuteAsync(closure, buffer);
- // Suspend and resume on this thread so allocation measurements include all
- // of gsub, without measuring Task.Yield or thread-pool scheduling costs.
- while (!task.IsCompleted)
- {
- suspension.Complete();
- }
-
- return task.GetAwaiter().GetResult();
- }
-
- sealed class SuspendedReplacement : IValueTaskSource
- {
- ManualResetValueTaskSourceCore core;
- int result;
-
- public ValueTask Start(int value)
- {
- core.Reset();
- result = value;
- return new(this, core.Version);
- }
-
- public void Complete() => core.SetResult(result);
-
- public int GetResult(short token) => core.GetResult(token);
-
- public ValueTaskSourceStatus GetStatus(short token) => core.GetStatus(token);
-
- public void OnCompleted(
- Action