From dbe479d16334f080995ccb91bdd984de62f8bfc3 Mon Sep 17 00:00:00 2001 From: akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:01:36 +0900 Subject: [PATCH 1/2] Organize benchmarks and add optional .NET 11 runtime support --- .gitignore | 2 + global.json | 7 + .../Benchmark.NativeAot.csproj | 12 +- sandbox/Benchmark.NativeAot/Program.cs | 29 - sandbox/Benchmark.NativeAot/README.md | 11 +- .../Benchmark.NativeAot/RuntimeComparison.cs | 2 - sandbox/Benchmark/AddBenchmark.cs | 86 -- sandbox/Benchmark/Benchmark.csproj | 54 +- sandbox/Benchmark/Benchmark.sln | 10 - sandbox/Benchmark/BenchmarkConfig.cs | 69 ++ sandbox/Benchmark/BenchmarkCore.cs | 39 - sandbox/Benchmark/ByteStringIoBenchmark.cs | 50 -- .../Benchmark/BytecodeInteropVerification.cs | 120 --- sandbox/Benchmark/ComparisonWorkloads.cs | 163 ---- .../{ => Comparisons}/ComparisonEngines.cs | 4 - .../CompilationComparison.cs | 47 -- .../Comparisons/InterpreterComparison.cs | 49 ++ .../{ => Comparisons}/LuaCSharpComparison.cs | 0 .../Comparisons/StartupComparison.cs | 26 + sandbox/Benchmark/CoroutineBenchmark.cs | 57 -- .../Benchmark/CoroutineTransferBenchmark.cs | 61 -- sandbox/Benchmark/DebuggerBenchmark.cs | 66 -- .../Benchmark/DebuggerBreakpointBenchmark.cs | 52 -- sandbox/Benchmark/DebuggerControlBenchmark.cs | 49 -- .../Benchmark/DebuggerFirstUseBenchmark.cs | 59 -- sandbox/Benchmark/FileHelper.cs | 12 - sandbox/Benchmark/GSubBenchmark.cs | 98 --- .../Benchmark/GeneratedUserDataBenchmark.cs | 71 -- sandbox/Benchmark/HookedBenchmark.cs | 39 - sandbox/Benchmark/InterpreterComparison.cs | 101 --- sandbox/Benchmark/InterpreterSteps.cs | 50 -- sandbox/Benchmark/NBodyBenchmark.cs | 65 -- sandbox/Benchmark/Net11RuntimeToolchain.cs | 39 + sandbox/Benchmark/Program.cs | 49 +- sandbox/Benchmark/README.md | 90 ++- .../Scripts/Comparison/calls-varargs.lua | 9 + .../Scripts/Comparison/coroutines.lua | 12 + sandbox/Benchmark/Scripts/Comparison/fib.lua | 8 + .../Scripts/Comparison/host-calls.lua | 6 + .../Scripts/Comparison/mandelbrot.lua | 20 + sandbox/Benchmark/Scripts/Comparison/map.lua | 30 + sandbox/Benchmark/Scripts/Comparison/math.lua | 6 + .../Scripts/Comparison/metamethods.lua | 10 + .../Comparison/nbody.lua} | 8 +- .../Scripts/Comparison/numeric-loop.lua | 5 + .../Benchmark/Scripts/Comparison/patterns.lua | 7 + .../Benchmark/Scripts/Comparison/strings.lua | 8 + .../Scripts/Comparison/table-access.lua | 10 + .../Scripts/Comparison/table-sort.lua | 6 + .../Scripts/Compilation/control-flow.lua | 762 ++++++++++++++++++ .../Scripts/Compilation/escaped-strings.lua | 82 ++ .../Scripts/Compilation/identifiers.lua | 82 ++ .../Scripts/Compilation/plain-strings.lua | 82 ++ .../Benchmark/Utf8CharConversionBenchmark.cs | 37 - .../Benchmark/Workloads/BenchmarkScripts.cs | 13 + .../Workloads/ComparisonWorkloads.cs | 46 ++ sandbox/Benchmark/add.lua | 7 - sandbox/Benchmark/coroutine.lua | 17 - .../DifferentialProbe.csproj | 2 +- tools/DifferentialProbe/README.md | 2 +- .../PerformanceProbe/PerformanceProbe.csproj | 2 +- .../PerformanceProbe/Scripts}/n-body.lua | 0 62 files changed, 1511 insertions(+), 1506 deletions(-) create mode 100644 global.json delete mode 100644 sandbox/Benchmark/AddBenchmark.cs create mode 100644 sandbox/Benchmark/BenchmarkConfig.cs delete mode 100644 sandbox/Benchmark/BenchmarkCore.cs delete mode 100644 sandbox/Benchmark/ByteStringIoBenchmark.cs delete mode 100644 sandbox/Benchmark/BytecodeInteropVerification.cs delete mode 100644 sandbox/Benchmark/ComparisonWorkloads.cs rename sandbox/Benchmark/{ => Comparisons}/ComparisonEngines.cs (89%) rename sandbox/Benchmark/{ => Comparisons}/CompilationComparison.cs (71%) create mode 100644 sandbox/Benchmark/Comparisons/InterpreterComparison.cs rename sandbox/Benchmark/{ => Comparisons}/LuaCSharpComparison.cs (100%) create mode 100644 sandbox/Benchmark/Comparisons/StartupComparison.cs delete mode 100644 sandbox/Benchmark/CoroutineBenchmark.cs delete mode 100644 sandbox/Benchmark/CoroutineTransferBenchmark.cs delete mode 100644 sandbox/Benchmark/DebuggerBenchmark.cs delete mode 100644 sandbox/Benchmark/DebuggerBreakpointBenchmark.cs delete mode 100644 sandbox/Benchmark/DebuggerControlBenchmark.cs delete mode 100644 sandbox/Benchmark/DebuggerFirstUseBenchmark.cs delete mode 100644 sandbox/Benchmark/FileHelper.cs delete mode 100644 sandbox/Benchmark/GSubBenchmark.cs delete mode 100644 sandbox/Benchmark/GeneratedUserDataBenchmark.cs delete mode 100644 sandbox/Benchmark/HookedBenchmark.cs delete mode 100644 sandbox/Benchmark/InterpreterComparison.cs delete mode 100644 sandbox/Benchmark/InterpreterSteps.cs delete mode 100644 sandbox/Benchmark/NBodyBenchmark.cs create mode 100644 sandbox/Benchmark/Net11RuntimeToolchain.cs create mode 100644 sandbox/Benchmark/Scripts/Comparison/calls-varargs.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/coroutines.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/fib.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/host-calls.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/mandelbrot.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/map.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/math.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/metamethods.lua rename sandbox/Benchmark/{hooked.lua => Scripts/Comparison/nbody.lua} (96%) create mode 100644 sandbox/Benchmark/Scripts/Comparison/numeric-loop.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/patterns.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/strings.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/table-access.lua create mode 100644 sandbox/Benchmark/Scripts/Comparison/table-sort.lua create mode 100644 sandbox/Benchmark/Scripts/Compilation/control-flow.lua create mode 100644 sandbox/Benchmark/Scripts/Compilation/escaped-strings.lua create mode 100644 sandbox/Benchmark/Scripts/Compilation/identifiers.lua create mode 100644 sandbox/Benchmark/Scripts/Compilation/plain-strings.lua delete mode 100644 sandbox/Benchmark/Utf8CharConversionBenchmark.cs create mode 100644 sandbox/Benchmark/Workloads/BenchmarkScripts.cs create mode 100644 sandbox/Benchmark/Workloads/ComparisonWorkloads.cs delete mode 100644 sandbox/Benchmark/add.lua delete mode 100644 sandbox/Benchmark/coroutine.lua rename {sandbox/Benchmark => tools/PerformanceProbe/Scripts}/n-body.lua (100%) 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..2991f6e8 100644 --- a/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj +++ b/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj @@ -8,8 +8,14 @@ - - - + + + + + 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 continuation, - object? state, - short token, - ValueTaskSourceOnCompletedFlags flags - ) => core.OnCompleted(continuation, state, token, flags); - } -} diff --git a/sandbox/Benchmark/GeneratedUserDataBenchmark.cs b/sandbox/Benchmark/GeneratedUserDataBenchmark.cs deleted file mode 100644 index 27fccf23..00000000 --- a/sandbox/Benchmark/GeneratedUserDataBenchmark.cs +++ /dev/null @@ -1,71 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Lua; -using Lua.Runtime; - -[Config(typeof(ComparisonConfig))] -public class GeneratedUserDataBenchmark -{ - [Params("field", "method", "comparison", "write")] - public string Case { get; set; } = "field"; - - [Params(false, true)] - public bool OverrideMetatable { get; set; } - - LuaState state = default!; - LuaClosure closure = default!; - - [GlobalSetup] - public void Setup() - { - state = LuaState.Create(); - var first = new GeneratedBenchmarkObject { Value = 7 }; - var second = new GeneratedBenchmarkObject { Value = 8 }; - if (OverrideMetatable) - { - ILuaUserData left = first; - ILuaUserData right = second; - left.Metatable = left.Metatable; - right.Metatable = right.Metatable; - } - state.Environment["first"] = first; - state.Environment["second"] = second; - var (source, expected) = Case switch - { - "field" => ("local v=first;local n=0;for i=1,1000 do n=n+v.Value end;return n", 7000), - "method" => ("local v=first;local n=0;for i=1,1000 do n=n+v:Add(1) end;return n", 8000), - "comparison" => ( - "local a,b=first,second;local n=0;for i=1,1000 do if a ("local v=first;for i=1,1000 do v.Value=i end;return v.Value", 1000), - _ => throw new ArgumentOutOfRangeException(nameof(Case)), - }; - closure = state.Load(source, Case); - if (Run() != expected) - throw new InvalidOperationException($"Unexpected result for {Case}"); - } - - [Benchmark(OperationsPerInvoke = 1000)] - public int Run() - { - state.RunAsync(closure).GetAwaiter().GetResult(); - return state.Stack.Pop().Read(); - } - - [GlobalCleanup] - public void Cleanup() => state.Dispose(); -} - -[LuaObject] -public partial class GeneratedBenchmarkObject -{ - [LuaMember] - public int Value { get; set; } - - [LuaMember] - public int Add(int value) => Value + value; - - [LuaMetamethod(LuaObjectMetamethod.Lt)] - public static bool Less(GeneratedBenchmarkObject left, GeneratedBenchmarkObject right) => - left.Value < right.Value; -} diff --git a/sandbox/Benchmark/HookedBenchmark.cs b/sandbox/Benchmark/HookedBenchmark.cs deleted file mode 100644 index 4aa705d5..00000000 --- a/sandbox/Benchmark/HookedBenchmark.cs +++ /dev/null @@ -1,39 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Lua; -using Lua.Standard; - -[Config(typeof(BenchmarkConfig))] -public class HookedBenchmark -{ - BenchmarkCore core = default!; - LuaValue[] buffer = new LuaValue[1]; - - [IterationSetup] - public void Setup() - { - core = new(); - core.Setup("hooked.lua"); - core.LuaCSharpState.OpenStandardLibraries(); - } - - [IterationCleanup] - public void Cleanup() - { - core.Dispose(); - core = default!; - GC.Collect(); - } - - [Benchmark(Description = "NLua (DoString)", Baseline = true)] - public object[] Benchmark_NLua_String() - { - return core.NLuaState.DoString(core.SourceText); - } - - [Benchmark(Description = "Lua-CSharp (DoString)")] - public async Task Benchmark_LuaCSharp_String() - { - await core.LuaCSharpState.DoStringAsync(core.SourceText, buffer); - return buffer[0]; - } -} diff --git a/sandbox/Benchmark/InterpreterComparison.cs b/sandbox/Benchmark/InterpreterComparison.cs deleted file mode 100644 index 1560c356..00000000 --- a/sandbox/Benchmark/InterpreterComparison.cs +++ /dev/null @@ -1,101 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Order; -using Perfolizer.Horology; - -public sealed class ComparisonConfig : ManualConfig -{ - public ComparisonConfig() - { - AddDiagnoser(MemoryDiagnoser.Default); - AddJob( - Job.ShortRun.WithWarmupCount(3) - .WithIterationCount(5) - .WithIterationTime(TimeInterval.FromMilliseconds(250)) - ); - WithOrderer(new DefaultOrderer(SummaryOrderPolicy.Declared)); - } -} - -[Config(typeof(ComparisonConfig))] -[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); - ComparisonWorkloads.Check(workload, "Lua-CSharp", lua.Run()); - } - - [GlobalSetup(Target = nameof(MoonSharp))] - public void SetupMoonSharp() - { - var workload = ComparisonWorkloads.Find(Case); - moonSharp = new(workload.Source); - ComparisonWorkloads.Check(workload, "MoonSharp", moonSharp.Run()); - } - - [GlobalSetup(Target = nameof(NuLua52))] - public void SetupNuLua() - { - var workload = ComparisonWorkloads.Find(Case); - nuLua = new(workload.Source); - ComparisonWorkloads.Check(workload, "NuLua Lua 5.2", nuLua.Run()); - } - - [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(); - - public static void Verify() - { - ScriptWarmUp(); - foreach ( - var workload in ComparisonWorkloads - .Cases.Concat(ComparisonWorkloads.CompilationCases) - .DistinctBy(c => c.Name) - ) - { - using var lua = new LuaCSharpComparison(workload.Source); - using var bytes = new LuaCSharpComparison(workload.Source, bytes: true); - var moon = new MoonSharpComparison(workload.Source); - using var native = new NuLua52Comparison(workload.Source); - // A second invocation detects accidental reuse of mutated workload data. - for (var i = 0; i < 2; i++) - { - ComparisonWorkloads.Check(workload, "Lua-CSharp", lua.Run()); - ComparisonWorkloads.Check(workload, "Lua-CSharp bytes", bytes.Run()); - ComparisonWorkloads.Check(workload, "MoonSharp", moon.Run()); - ComparisonWorkloads.Check(workload, "NuLua Lua 5.2", native.Run()); - } - Console.WriteLine( - $"PASS {workload.Category, -12} {workload.Name, -16} {workload.Expected:R}" - ); - } - } - - static void ScriptWarmUp() => global::MoonSharp.Interpreter.Script.WarmUp(); -} diff --git a/sandbox/Benchmark/InterpreterSteps.cs b/sandbox/Benchmark/InterpreterSteps.cs deleted file mode 100644 index c62f2625..00000000 --- a/sandbox/Benchmark/InterpreterSteps.cs +++ /dev/null @@ -1,50 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Lua; -using Lua.Runtime; -using Lua.Standard; - -[Config(typeof(BenchmarkConfig))] -public class InterpreterSteps -{ - string sourceText = default!; - LuaState state = default!; - LuaClosure closure = default!; - - [GlobalSetup] - public void GlobalSetup() - { - var filePath = FileHelper.GetAbsolutePath("n-body.lua"); - sourceText = File.ReadAllText(filePath); - state = LuaState.Create(); - state.OpenStandardLibraries(); - closure = state.Load(sourceText, sourceText); - } - - [IterationSetup] - public void Setup() - { - state = default!; - GC.Collect(); - - state = LuaState.Create(); - state.OpenStandardLibraries(); - } - - [Benchmark] - public void CreateState() - { - LuaState.Create(); - } - - [Benchmark] - public LuaClosure Compile() - { - return state.Load(sourceText, sourceText); - } - - [Benchmark] - public async ValueTask RunAsync() - { - await state.CallAsync(closure, []); - } -} diff --git a/sandbox/Benchmark/NBodyBenchmark.cs b/sandbox/Benchmark/NBodyBenchmark.cs deleted file mode 100644 index 1a5b0d07..00000000 --- a/sandbox/Benchmark/NBodyBenchmark.cs +++ /dev/null @@ -1,65 +0,0 @@ -using BenchmarkDotNet.Attributes; -using Lua; -using Lua.Standard; -using MoonSharp.Interpreter; - -[Config(typeof(BenchmarkConfig))] -public class NBodyBenchmark -{ - BenchmarkCore core = default!; - LuaValue[] buffer = new LuaValue[1]; - - [IterationSetup] - public void Setup() - { - core = new(); - core.Setup("n-body.lua"); - core.LuaCSharpState.OpenStandardLibraries(); - } - - [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)", Baseline = true)] - 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/Net11RuntimeToolchain.cs b/sandbox/Benchmark/Net11RuntimeToolchain.cs new file mode 100644 index 00000000..bd77fc6d --- /dev/null +++ b/sandbox/Benchmark/Net11RuntimeToolchain.cs @@ -0,0 +1,39 @@ +using System.Xml.Linq; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Toolchains; +using BenchmarkDotNet.Toolchains.CsProj; +using BenchmarkDotNet.Toolchains.DotNetCli; +using BenchmarkDotNet.Toolchains.NetCoreApp; + +// Build net10.0 with the existing SDK; execute it on the installed .NET 11 runtime. +sealed class Net11RuntimeToolchain(NetCoreAppSettings settings) + : Toolchain( + "Runtime11", + CoreRuntime.Core11_0, + new RuntimeBuilder(settings), + new DotNetCliExecutor(settings.CliPath) + ) +{ + sealed class RuntimeBuilder(NetCoreAppSettings settings) + : CsProjBuilder(settings with { TargetFrameworkMoniker = "net10.0" }) + { + protected override void AddLateProperties( + XElement project, + BuildPartition partition, + ArtifactsPaths paths, + FileInfo projectFile + ) + { + base.AddLateProperties(project, partition, paths, projectFile); + project.Add( + new XElement( + "PropertyGroup", + new XElement("UseAppHost", false), + new XElement("RuntimeFrameworkVersion", "11.0.0-preview.1"), + new XElement("RollForward", "LatestPatch") + ) + ); + } + } +} diff --git a/sandbox/Benchmark/Program.cs b/sandbox/Benchmark/Program.cs index bc0d1e04..4edcbe51 100644 --- a/sandbox/Benchmark/Program.cs +++ b/sandbox/Benchmark/Program.cs @@ -1,49 +1,4 @@ -using System.Reflection; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; -if (args.Contains("--verify")) -{ - Console.WriteLine( - $"Runtime: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}" - ); - Console.WriteLine( - $"MoonSharp: {typeof(MoonSharp.Interpreter.Script).Assembly.GetName().Version}" - ); - Console.WriteLine( - $"NuLua Lua52 assembly: {typeof(NuLua.Lua52.Lua52State).Assembly.GetName().Version}" - ); - InterpreterComparison.Verify(); - await BytecodeInteropVerification.VerifyAsync(); - return; -} - -if (args.Contains("--legacy")) - BenchmarkSwitcher - .FromAssembly(Assembly.GetEntryAssembly()!) - .Run(args.Where(a => a != "--legacy").ToArray()); -else - BenchmarkSwitcher - .FromTypes([ - typeof(InterpreterComparison), - typeof(CompilationComparison), - typeof(StartupComparison), - typeof(GeneratedUserDataBenchmark), - typeof(CoroutineTransferBenchmark), - typeof(DebuggerBenchmark), - typeof(DebuggerBreakpointBenchmark), - typeof(DebuggerFirstUseBenchmark), - typeof(DebuggerControlBenchmark), - ]) - .Run(args); - -class BenchmarkConfig : ManualConfig -{ - public BenchmarkConfig() - { - AddDiagnoser(MemoryDiagnoser.Default); - AddJob(Job.ShortRun.WithWarmupCount(10).WithIterationCount(10)); - } -} +var config = BenchmarkConfig.Create(ref args); +BenchmarkSwitcher.FromAssembly(typeof(InterpreterComparison).Assembly).Run(args, config); diff --git a/sandbox/Benchmark/README.md b/sandbox/Benchmark/README.md index 9f441ce1..8752119c 100644 --- a/sandbox/Benchmark/README.md +++ b/sandbox/Benchmark/README.md @@ -1,36 +1,79 @@ # Interpreter benchmarks The default .NET 10 suite compares Lua-CSharp, MoonSharp 2.0.0 and -[NuLua.Lua52 0.1.0](https://github.com/nuskey8/NuLua). NuLua runs native Lua 5.2; -setup checks `_VERSION`. NLua remains available in the legacy benchmarks. +[NuLua.Lua52 0.2.0](https://github.com/nuskey8/NuLua). NuLua runs native Lua 5.2. -Run from this directory so `global.json` selects the .NET 10 SDK: +Run from the repository root: + +```powershell +dotnet run -c Release --project sandbox/Benchmark +dotnet run -c Release --project sandbox/Benchmark -- --filter '*InterpreterComparison*' +``` + +The repository's `global.json` selects a stable .NET 10 SDK for root-level +commands and BenchmarkDotNet's generated builds. SDK selection starts in the +current directory, so a pin only inside the benchmark directory does not cover +root-level `dotnet run --project` commands. + +To build and run the executable directly, use this directory: ```powershell dotnet restore Benchmark.csproj dotnet build Benchmark.csproj -c Release --no-restore -dotnet bin/Release/net10.0/Benchmark.dll --verify dotnet bin/Release/net10.0/Benchmark.dll --filter '*InterpreterComparison*' --exporters json ``` -`--verify` checks independent numeric results twice on all three engines and -both Lua-CSharp string modes, including the compiler workloads. Timed runs use -Lua-CSharp's default UTF-16 mode. Shared sources are ASCII; these cases do not -measure Unicode encoding or arbitrary binary strings. +## Choosing .NET runtimes + +.NET 10 is the default. Select execution runtimes using BenchmarkDotNet's +native option: + +```powershell +# .NET 11 runtime only +dotnet run -c Release --project sandbox/Benchmark -- --runtimes net11.0 --filter '*InterpreterComparison*map*' + +# Compare .NET 10 and .NET 11 runtimes +dotnet run -c Release --project sandbox/Benchmark -- --runtimes net10.0 net11.0 --filter '*InterpreterComparison*map*' +``` + +The build stays on .NET 10. The .NET 11 job executes the same `net10.0` code +under the installed .NET 11 runtime, including RC versions. No .NET 11 SDK or +SDK switching is needed. Runtime roll-forward stays within .NET 11.0. + +BenchmarkDotNet handles selection, listing, filters, jobs and reports directly. +Use `--job Dry` for a smoke run or `--job Short` for a short measurement run. +The harness uses BenchmarkDotNet 0.16.0-preview.2 for .NET 11 runtime support. + +## Layout + +- `Comparisons/`: interpreter, compilation and startup comparisons and engine adapters. +- `Workloads/`: case names, script paths and embedded script loading. +- `Scripts/`: Lua sources grouped by suite, embedded in the executable. + +Scripts load during setup, outside timed operations, and work from any current +directory. Compiler corpora are stored as complete Lua files. + +Cases load their Lua source on demand during setup. Listing benchmarks does +not read Lua scripts. ## Measurement boundaries | Suite | Timed operation | Cases | | --- | --- | --- | -| `InterpreterComparison` | Execute a retained compiled chunk and read one numeric result | Numeric loops, Lua calls/varargs, table access/sort, metamethods, math, strings, patterns, coroutines, host callbacks, n-body | +| `InterpreterComparison` | Execute a retained compiled chunk and read one numeric result | Numeric loops, Lua calls/varargs, recursive Fibonacci, table access/sort, map, metamethods, math, strings, patterns, coroutines, host callbacks, Mandelbrot, n-body | | `CompilationComparison` | Compile source in an existing state; no execution | Small loop, n-body, control flow, identifiers, plain strings, escaped strings | | `StartupComparison` | Create a state, open libraries, dispose where supported | One lifecycle per engine | -| `GeneratedUserDataBenchmark` | Execute 1,000 generated C# member accesses or comparisons; report time per operation | Property reads/writes, method calls, comparisons, with default and explicitly replaced metatables | -| `CoroutineTransferBenchmark` | Transfer 64 yielded packets; report time per transfer | `resume` and `wrap`, 1–4,096 results per packet, with and without cancellation | + +The `map` case inserts 1,000 string keys, performs four lookup/update passes, +deletes even keys, checks misses and traverses the remaining entries with +`pairs`. Its order-independent integer checksum is 6,771,000. `mandelbrot` +sums escape iteration counts on a 128 × 96 grid with a 64-iteration limit; +its checksum is 258,320. +Neither workload prints output. Only the engine being measured is initialized for a benchmark. Execution excludes source compilation and state setup. Scripts rebuild their mutable -data on each invocation; each has an independently calculated checksum. +data on each invocation and return a numeric checksum. Lua-CSharp uses a caller-owned result buffer, NuLua a retained registry reference and its public stack API, and MoonSharp a retained function value. The host-callback case uses each engine's direct callback API without reflection @@ -39,8 +82,8 @@ binding. It measures that public embedding path as well as the interpreter. Compilation uses batches of 64 loads. Each batch starts with a fresh state outside timing, because MoonSharp retains loaded sources and bytecode in its state. The native stack is cleared after each load. Each compiler gets two -seconds of bounded warmup, then three launches with 20 warmup and 20 measured -batches. Source-to-UTF-8 conversion in NuLua's string API is included. Small +seconds of bounded warmup. BenchmarkDotNet controls launches and measured +iterations using its normal job options. Source-to-UTF-8 conversion in NuLua's string API is included. Small batches can be shorter than BenchmarkDotNet's recommended iteration duration; inspect per-launch variation and confidence intervals. @@ -52,18 +95,15 @@ equivalent deterministic disposal API. ```powershell dotnet bin/Release/net10.0/Benchmark.dll --filter '*InterpreterComparison*host-calls*' +dotnet bin/Release/net10.0/Benchmark.dll --filter '*InterpreterComparison*map*' +dotnet bin/Release/net10.0/Benchmark.dll --filter '*InterpreterComparison*mandelbrot*' dotnet bin/Release/net10.0/Benchmark.dll --filter '*CompilationComparison*' dotnet bin/Release/net10.0/Benchmark.dll --filter '*StartupComparison*' -dotnet bin/Release/net10.0/Benchmark.dll --filter '*GeneratedUserDataBenchmark*' -dotnet bin/Release/net10.0/Benchmark.dll --filter '*CoroutineTransferBenchmark*' -dotnet bin/Release/net10.0/Benchmark.dll --legacy --filter '*GSubBenchmark*' ``` `--list flat` lists benchmarks. `--artifacts ` keeps a run's outputs separate. With no arguments, BenchmarkDotNet opens its benchmark selector. -Execution/startup defaults use three warmups and five measurements -with a 250 ms target iteration time. Use more launches/iterations for small -differences. Run timing processes serially on an otherwise idle machine. +Use more launches/iterations for small differences. Run timing processes serially on an otherwise idle machine. GC remains enabled. MemoryDiagnoser reports managed .NET allocation only; NuLua also allocates on Lua's native heap. Zero managed bytes does not mean @@ -71,8 +111,14 @@ zero total allocation. Keep engine/runtime versions, CPU, tiering/PGO settings, source revision and workload selection with any published results. Avoid a single overall ranking without application-specific workload weights. -The legacy suites include file I/O, source execution and focused API tests with -different boundaries. Their results should not be mixed with this suite. +## Adding workloads + +Add an ASCII Lua file under `Scripts/Comparison/` that rebuilds its mutable +data and returns one numeric checksum. Register its name in +`Workloads/ComparisonWorkloads.cs`. The case is +automatically available to all three engines and the NativeAOT execution suite. +Compiler-only sources belong under `Scripts/Compilation/` and are registered +in `CompilationCases`. The project embeds new files automatically. For Lua-CSharp JIT versus NativeAOT compilation and execution, use the [NativeAOT benchmarks](../Benchmark.NativeAot/README.md), which share these sources. diff --git a/sandbox/Benchmark/Scripts/Comparison/calls-varargs.lua b/sandbox/Benchmark/Scripts/Comparison/calls-varargs.lua new file mode 100644 index 00000000..8f44d8cf --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/calls-varargs.lua @@ -0,0 +1,9 @@ +local function add(a, ...) + local b, c = ... + return a + b + c +end +local sum = 0 +for i = 1, 2000 do + sum = sum + add(i, 2, 3) +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/coroutines.lua b/sandbox/Benchmark/Scripts/Comparison/coroutines.lua new file mode 100644 index 00000000..a2f7c1b8 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/coroutines.lua @@ -0,0 +1,12 @@ +local co = coroutine.create(function() + for i = 1, 500 do + coroutine.yield(i) + end +end) +local sum = 0 +for i = 1, 500 do + local ok, value = coroutine.resume(co) + assert(ok) + sum = sum + value +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/fib.lua b/sandbox/Benchmark/Scripts/Comparison/fib.lua new file mode 100644 index 00000000..608b0b09 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/fib.lua @@ -0,0 +1,8 @@ +local function fib(n) + if n < 2 then + return n + end + return fib(n - 1) + fib(n - 2) +end + +return fib(20) diff --git a/sandbox/Benchmark/Scripts/Comparison/host-calls.lua b/sandbox/Benchmark/Scripts/Comparison/host-calls.lua new file mode 100644 index 00000000..e3a96348 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/host-calls.lua @@ -0,0 +1,6 @@ +local add = host_add +local sum = 0 +for i = 1, 2000 do + sum = add(sum, i) +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/mandelbrot.lua b/sandbox/Benchmark/Scripts/Comparison/mandelbrot.lua new file mode 100644 index 00000000..ee62e5d8 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/mandelbrot.lua @@ -0,0 +1,20 @@ +-- Sum escape iteration counts over a 128 x 96 grid, capped at 64. +-- No image output or I/O is included in the workload. +local width, height, limit = 128, 96, 64 +local total = 0 +for y = 0, height - 1 do + local ci = y / height * 2.0 - 1.0 + for x = 0, width - 1 do + local cr = x / width * 3.0 - 2.0 + local zr, zi = 0.0, 0.0 + local iterations = 0 + while iterations < limit and zr * zr + zi * zi <= 4.0 do + local next_r = zr * zr - zi * zi + cr + zi = 2.0 * zr * zi + ci + zr = next_r + iterations = iterations + 1 + end + total = total + iterations + end +end +return total diff --git a/sandbox/Benchmark/Scripts/Comparison/map.lua b/sandbox/Benchmark/Scripts/Comparison/map.lua new file mode 100644 index 00000000..f2722f7c --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/map.lua @@ -0,0 +1,30 @@ +-- String-keyed hash table: insert, read/update, delete, miss, then traverse. +local map, keys = {}, {} +for i = 1, 1000 do + local key = 'key-' .. i + keys[i] = key + map[key] = i * 3 +end + +local sum = 0 +for round = 1, 4 do + for i = 1, 1000 do + local key = keys[i] + local value = map[key] + sum = sum + value + map[key] = value + round + end +end + +for i = 2, 1000, 2 do + map[keys[i]] = nil +end +for i = 2, 1000, 2 do + assert(map[keys[i]] == nil) +end +for key, value in pairs(map) do + sum = sum + value +end +-- Read/update sum: 12 * sum(1..1000) + 10 * 1000. +-- Remaining odd keys: 3 * sum(1,3,..999) + 10 * 500. +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/math.lua b/sandbox/Benchmark/Scripts/Comparison/math.lua new file mode 100644 index 00000000..82839877 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/math.lua @@ -0,0 +1,6 @@ +local abs, sqrt, min, max = math.abs, math.sqrt, math.min, math.max +local sum = 0 +for i = 1, 2000 do + sum = sum + min(abs(-i), sqrt(i)) + max(i, 3, 7) +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/metamethods.lua b/sandbox/Benchmark/Scripts/Comparison/metamethods.lua new file mode 100644 index 00000000..d190a4a1 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/metamethods.lua @@ -0,0 +1,10 @@ +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 sum = 0 +for i = 1, 2000 do + sum = sum + (a + b) + a.missing +end +return sum diff --git a/sandbox/Benchmark/hooked.lua b/sandbox/Benchmark/Scripts/Comparison/nbody.lua similarity index 96% rename from sandbox/Benchmark/hooked.lua rename to sandbox/Benchmark/Scripts/Comparison/nbody.lua index 1055c5b7..ea844990 100644 --- a/sandbox/Benchmark/hooked.lua +++ b/sandbox/Benchmark/Scripts/Comparison/nbody.lua @@ -1,5 +1,3 @@ -debug.sethook(function () -end,"",1000000) sun = {} jupiter = {} saturn = {} @@ -108,12 +106,12 @@ local function offsetMomentum(b, nbody) b[1].vz = -pz / SOLAR_MASS end -local N = tonumber(arg and arg[1]) or 10000 +local N = tonumber(arg and arg[1]) or 100 local nbody = #bodies offsetMomentum(bodies, nbody) energy(bodies, nbody) for i = 1, N do advance(bodies, nbody, 0.01) end energy(bodies, nbody) -debug.sethook(function () -end,"",1000000) \ No newline at end of file + +return energy(bodies, nbody) diff --git a/sandbox/Benchmark/Scripts/Comparison/numeric-loop.lua b/sandbox/Benchmark/Scripts/Comparison/numeric-loop.lua new file mode 100644 index 00000000..f8707c4b --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/numeric-loop.lua @@ -0,0 +1,5 @@ +local sum = 0 +for i = 1, 20000 do + sum = sum + i +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/patterns.lua b/sandbox/Benchmark/Scripts/Comparison/patterns.lua new file mode 100644 index 00000000..87ea891c --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/patterns.lua @@ -0,0 +1,7 @@ +local gsub = string.gsub +local count = 0 +for i = 1, 400 do + local text = gsub('hello 123 world 456', '(%a+)', '[%1]') + count = count + #text +end +return count diff --git a/sandbox/Benchmark/Scripts/Comparison/strings.lua b/sandbox/Benchmark/Scripts/Comparison/strings.lua new file mode 100644 index 00000000..8fe9ae97 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/strings.lua @@ -0,0 +1,8 @@ +local rep, reverse, sub, byte, concat = string.rep, string.reverse, string.sub, string.byte, table.concat +local count = 0 +local values = { 'abc', 12, 'def', 34 } +for i = 1, 400 do + local text = sub(reverse(rep('abcd', 8, ':')), 2, -2) + count = count + byte(text) + #text + #concat(values, ',') +end +return count diff --git a/sandbox/Benchmark/Scripts/Comparison/table-access.lua b/sandbox/Benchmark/Scripts/Comparison/table-access.lua new file mode 100644 index 00000000..cfb2518f --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/table-access.lua @@ -0,0 +1,10 @@ +local values = {} +for i = 1, 1000 do + values[i] = { x = i, y = i % 7 } +end +local sum = 0 +for i = 1, 1000 do + local value = values[i] + sum = sum + value.x + value.y +end +return sum diff --git a/sandbox/Benchmark/Scripts/Comparison/table-sort.lua b/sandbox/Benchmark/Scripts/Comparison/table-sort.lua new file mode 100644 index 00000000..1aa1465c --- /dev/null +++ b/sandbox/Benchmark/Scripts/Comparison/table-sort.lua @@ -0,0 +1,6 @@ +local values = {} +for i = 1, 300 do + values[i] = 301 - i +end +table.sort(values) +return values[150] diff --git a/sandbox/Benchmark/Scripts/Compilation/control-flow.lua b/sandbox/Benchmark/Scripts/Compilation/control-flow.lua new file mode 100644 index 00000000..76ad5e1c --- /dev/null +++ b/sandbox/Benchmark/Scripts/Compilation/control-flow.lua @@ -0,0 +1,762 @@ +local total = 0 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +return total diff --git a/sandbox/Benchmark/Scripts/Compilation/escaped-strings.lua b/sandbox/Benchmark/Scripts/Compilation/escaped-strings.lua new file mode 100644 index 00000000..88432109 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Compilation/escaped-strings.lua @@ -0,0 +1,82 @@ +local total = 0 +do local value = 'escaped value 0:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 1:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 2:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 3:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 4:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 5:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 6:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 7:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 8:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 9:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 10:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 11:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 12:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 13:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 14:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 15:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 16:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 17:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 18:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 19:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 20:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 21:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 22:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 23:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 24:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 25:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 26:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 27:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 28:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 29:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 30:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 31:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 32:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 33:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 34:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 35:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 36:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 37:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 38:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 39:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 40:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 41:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 42:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 43:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 44:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 45:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 46:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 47:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 48:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 49:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 50:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 51:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 52:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 53:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 54:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 55:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 56:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 57:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 58:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 59:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 60:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 61:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 62:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 63:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 64:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 65:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 66:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 67:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 68:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 69:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 70:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 71:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 72:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 73:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 74:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 75:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 76:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 77:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 78:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +do local value = 'escaped value 79:\tcolumn\nline\r\a\b\f\v\\\"\'\x41\065\z suffix'; total = total + #value end +return total diff --git a/sandbox/Benchmark/Scripts/Compilation/identifiers.lua b/sandbox/Benchmark/Scripts/Compilation/identifiers.lua new file mode 100644 index 00000000..48d62125 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Compilation/identifiers.lua @@ -0,0 +1,82 @@ +local total = 0 +do local returnValue0, functionName0, ordinaryIdentifier0 = 1, 2, 3; total = total + returnValue0 + functionName0 + ordinaryIdentifier0 end +do local returnValue1, functionName1, ordinaryIdentifier1 = 1, 2, 3; total = total + returnValue1 + functionName1 + ordinaryIdentifier1 end +do local returnValue2, functionName2, ordinaryIdentifier2 = 1, 2, 3; total = total + returnValue2 + functionName2 + ordinaryIdentifier2 end +do local returnValue3, functionName3, ordinaryIdentifier3 = 1, 2, 3; total = total + returnValue3 + functionName3 + ordinaryIdentifier3 end +do local returnValue4, functionName4, ordinaryIdentifier4 = 1, 2, 3; total = total + returnValue4 + functionName4 + ordinaryIdentifier4 end +do local returnValue5, functionName5, ordinaryIdentifier5 = 1, 2, 3; total = total + returnValue5 + functionName5 + ordinaryIdentifier5 end +do local returnValue6, functionName6, ordinaryIdentifier6 = 1, 2, 3; total = total + returnValue6 + functionName6 + ordinaryIdentifier6 end +do local returnValue7, functionName7, ordinaryIdentifier7 = 1, 2, 3; total = total + returnValue7 + functionName7 + ordinaryIdentifier7 end +do local returnValue8, functionName8, ordinaryIdentifier8 = 1, 2, 3; total = total + returnValue8 + functionName8 + ordinaryIdentifier8 end +do local returnValue9, functionName9, ordinaryIdentifier9 = 1, 2, 3; total = total + returnValue9 + functionName9 + ordinaryIdentifier9 end +do local returnValue10, functionName10, ordinaryIdentifier10 = 1, 2, 3; total = total + returnValue10 + functionName10 + ordinaryIdentifier10 end +do local returnValue11, functionName11, ordinaryIdentifier11 = 1, 2, 3; total = total + returnValue11 + functionName11 + ordinaryIdentifier11 end +do local returnValue12, functionName12, ordinaryIdentifier12 = 1, 2, 3; total = total + returnValue12 + functionName12 + ordinaryIdentifier12 end +do local returnValue13, functionName13, ordinaryIdentifier13 = 1, 2, 3; total = total + returnValue13 + functionName13 + ordinaryIdentifier13 end +do local returnValue14, functionName14, ordinaryIdentifier14 = 1, 2, 3; total = total + returnValue14 + functionName14 + ordinaryIdentifier14 end +do local returnValue15, functionName15, ordinaryIdentifier15 = 1, 2, 3; total = total + returnValue15 + functionName15 + ordinaryIdentifier15 end +do local returnValue16, functionName16, ordinaryIdentifier16 = 1, 2, 3; total = total + returnValue16 + functionName16 + ordinaryIdentifier16 end +do local returnValue17, functionName17, ordinaryIdentifier17 = 1, 2, 3; total = total + returnValue17 + functionName17 + ordinaryIdentifier17 end +do local returnValue18, functionName18, ordinaryIdentifier18 = 1, 2, 3; total = total + returnValue18 + functionName18 + ordinaryIdentifier18 end +do local returnValue19, functionName19, ordinaryIdentifier19 = 1, 2, 3; total = total + returnValue19 + functionName19 + ordinaryIdentifier19 end +do local returnValue20, functionName20, ordinaryIdentifier20 = 1, 2, 3; total = total + returnValue20 + functionName20 + ordinaryIdentifier20 end +do local returnValue21, functionName21, ordinaryIdentifier21 = 1, 2, 3; total = total + returnValue21 + functionName21 + ordinaryIdentifier21 end +do local returnValue22, functionName22, ordinaryIdentifier22 = 1, 2, 3; total = total + returnValue22 + functionName22 + ordinaryIdentifier22 end +do local returnValue23, functionName23, ordinaryIdentifier23 = 1, 2, 3; total = total + returnValue23 + functionName23 + ordinaryIdentifier23 end +do local returnValue24, functionName24, ordinaryIdentifier24 = 1, 2, 3; total = total + returnValue24 + functionName24 + ordinaryIdentifier24 end +do local returnValue25, functionName25, ordinaryIdentifier25 = 1, 2, 3; total = total + returnValue25 + functionName25 + ordinaryIdentifier25 end +do local returnValue26, functionName26, ordinaryIdentifier26 = 1, 2, 3; total = total + returnValue26 + functionName26 + ordinaryIdentifier26 end +do local returnValue27, functionName27, ordinaryIdentifier27 = 1, 2, 3; total = total + returnValue27 + functionName27 + ordinaryIdentifier27 end +do local returnValue28, functionName28, ordinaryIdentifier28 = 1, 2, 3; total = total + returnValue28 + functionName28 + ordinaryIdentifier28 end +do local returnValue29, functionName29, ordinaryIdentifier29 = 1, 2, 3; total = total + returnValue29 + functionName29 + ordinaryIdentifier29 end +do local returnValue30, functionName30, ordinaryIdentifier30 = 1, 2, 3; total = total + returnValue30 + functionName30 + ordinaryIdentifier30 end +do local returnValue31, functionName31, ordinaryIdentifier31 = 1, 2, 3; total = total + returnValue31 + functionName31 + ordinaryIdentifier31 end +do local returnValue32, functionName32, ordinaryIdentifier32 = 1, 2, 3; total = total + returnValue32 + functionName32 + ordinaryIdentifier32 end +do local returnValue33, functionName33, ordinaryIdentifier33 = 1, 2, 3; total = total + returnValue33 + functionName33 + ordinaryIdentifier33 end +do local returnValue34, functionName34, ordinaryIdentifier34 = 1, 2, 3; total = total + returnValue34 + functionName34 + ordinaryIdentifier34 end +do local returnValue35, functionName35, ordinaryIdentifier35 = 1, 2, 3; total = total + returnValue35 + functionName35 + ordinaryIdentifier35 end +do local returnValue36, functionName36, ordinaryIdentifier36 = 1, 2, 3; total = total + returnValue36 + functionName36 + ordinaryIdentifier36 end +do local returnValue37, functionName37, ordinaryIdentifier37 = 1, 2, 3; total = total + returnValue37 + functionName37 + ordinaryIdentifier37 end +do local returnValue38, functionName38, ordinaryIdentifier38 = 1, 2, 3; total = total + returnValue38 + functionName38 + ordinaryIdentifier38 end +do local returnValue39, functionName39, ordinaryIdentifier39 = 1, 2, 3; total = total + returnValue39 + functionName39 + ordinaryIdentifier39 end +do local returnValue40, functionName40, ordinaryIdentifier40 = 1, 2, 3; total = total + returnValue40 + functionName40 + ordinaryIdentifier40 end +do local returnValue41, functionName41, ordinaryIdentifier41 = 1, 2, 3; total = total + returnValue41 + functionName41 + ordinaryIdentifier41 end +do local returnValue42, functionName42, ordinaryIdentifier42 = 1, 2, 3; total = total + returnValue42 + functionName42 + ordinaryIdentifier42 end +do local returnValue43, functionName43, ordinaryIdentifier43 = 1, 2, 3; total = total + returnValue43 + functionName43 + ordinaryIdentifier43 end +do local returnValue44, functionName44, ordinaryIdentifier44 = 1, 2, 3; total = total + returnValue44 + functionName44 + ordinaryIdentifier44 end +do local returnValue45, functionName45, ordinaryIdentifier45 = 1, 2, 3; total = total + returnValue45 + functionName45 + ordinaryIdentifier45 end +do local returnValue46, functionName46, ordinaryIdentifier46 = 1, 2, 3; total = total + returnValue46 + functionName46 + ordinaryIdentifier46 end +do local returnValue47, functionName47, ordinaryIdentifier47 = 1, 2, 3; total = total + returnValue47 + functionName47 + ordinaryIdentifier47 end +do local returnValue48, functionName48, ordinaryIdentifier48 = 1, 2, 3; total = total + returnValue48 + functionName48 + ordinaryIdentifier48 end +do local returnValue49, functionName49, ordinaryIdentifier49 = 1, 2, 3; total = total + returnValue49 + functionName49 + ordinaryIdentifier49 end +do local returnValue50, functionName50, ordinaryIdentifier50 = 1, 2, 3; total = total + returnValue50 + functionName50 + ordinaryIdentifier50 end +do local returnValue51, functionName51, ordinaryIdentifier51 = 1, 2, 3; total = total + returnValue51 + functionName51 + ordinaryIdentifier51 end +do local returnValue52, functionName52, ordinaryIdentifier52 = 1, 2, 3; total = total + returnValue52 + functionName52 + ordinaryIdentifier52 end +do local returnValue53, functionName53, ordinaryIdentifier53 = 1, 2, 3; total = total + returnValue53 + functionName53 + ordinaryIdentifier53 end +do local returnValue54, functionName54, ordinaryIdentifier54 = 1, 2, 3; total = total + returnValue54 + functionName54 + ordinaryIdentifier54 end +do local returnValue55, functionName55, ordinaryIdentifier55 = 1, 2, 3; total = total + returnValue55 + functionName55 + ordinaryIdentifier55 end +do local returnValue56, functionName56, ordinaryIdentifier56 = 1, 2, 3; total = total + returnValue56 + functionName56 + ordinaryIdentifier56 end +do local returnValue57, functionName57, ordinaryIdentifier57 = 1, 2, 3; total = total + returnValue57 + functionName57 + ordinaryIdentifier57 end +do local returnValue58, functionName58, ordinaryIdentifier58 = 1, 2, 3; total = total + returnValue58 + functionName58 + ordinaryIdentifier58 end +do local returnValue59, functionName59, ordinaryIdentifier59 = 1, 2, 3; total = total + returnValue59 + functionName59 + ordinaryIdentifier59 end +do local returnValue60, functionName60, ordinaryIdentifier60 = 1, 2, 3; total = total + returnValue60 + functionName60 + ordinaryIdentifier60 end +do local returnValue61, functionName61, ordinaryIdentifier61 = 1, 2, 3; total = total + returnValue61 + functionName61 + ordinaryIdentifier61 end +do local returnValue62, functionName62, ordinaryIdentifier62 = 1, 2, 3; total = total + returnValue62 + functionName62 + ordinaryIdentifier62 end +do local returnValue63, functionName63, ordinaryIdentifier63 = 1, 2, 3; total = total + returnValue63 + functionName63 + ordinaryIdentifier63 end +do local returnValue64, functionName64, ordinaryIdentifier64 = 1, 2, 3; total = total + returnValue64 + functionName64 + ordinaryIdentifier64 end +do local returnValue65, functionName65, ordinaryIdentifier65 = 1, 2, 3; total = total + returnValue65 + functionName65 + ordinaryIdentifier65 end +do local returnValue66, functionName66, ordinaryIdentifier66 = 1, 2, 3; total = total + returnValue66 + functionName66 + ordinaryIdentifier66 end +do local returnValue67, functionName67, ordinaryIdentifier67 = 1, 2, 3; total = total + returnValue67 + functionName67 + ordinaryIdentifier67 end +do local returnValue68, functionName68, ordinaryIdentifier68 = 1, 2, 3; total = total + returnValue68 + functionName68 + ordinaryIdentifier68 end +do local returnValue69, functionName69, ordinaryIdentifier69 = 1, 2, 3; total = total + returnValue69 + functionName69 + ordinaryIdentifier69 end +do local returnValue70, functionName70, ordinaryIdentifier70 = 1, 2, 3; total = total + returnValue70 + functionName70 + ordinaryIdentifier70 end +do local returnValue71, functionName71, ordinaryIdentifier71 = 1, 2, 3; total = total + returnValue71 + functionName71 + ordinaryIdentifier71 end +do local returnValue72, functionName72, ordinaryIdentifier72 = 1, 2, 3; total = total + returnValue72 + functionName72 + ordinaryIdentifier72 end +do local returnValue73, functionName73, ordinaryIdentifier73 = 1, 2, 3; total = total + returnValue73 + functionName73 + ordinaryIdentifier73 end +do local returnValue74, functionName74, ordinaryIdentifier74 = 1, 2, 3; total = total + returnValue74 + functionName74 + ordinaryIdentifier74 end +do local returnValue75, functionName75, ordinaryIdentifier75 = 1, 2, 3; total = total + returnValue75 + functionName75 + ordinaryIdentifier75 end +do local returnValue76, functionName76, ordinaryIdentifier76 = 1, 2, 3; total = total + returnValue76 + functionName76 + ordinaryIdentifier76 end +do local returnValue77, functionName77, ordinaryIdentifier77 = 1, 2, 3; total = total + returnValue77 + functionName77 + ordinaryIdentifier77 end +do local returnValue78, functionName78, ordinaryIdentifier78 = 1, 2, 3; total = total + returnValue78 + functionName78 + ordinaryIdentifier78 end +do local returnValue79, functionName79, ordinaryIdentifier79 = 1, 2, 3; total = total + returnValue79 + functionName79 + ordinaryIdentifier79 end +return total diff --git a/sandbox/Benchmark/Scripts/Compilation/plain-strings.lua b/sandbox/Benchmark/Scripts/Compilation/plain-strings.lua new file mode 100644 index 00000000..1a17af30 --- /dev/null +++ b/sandbox/Benchmark/Scripts/Compilation/plain-strings.lua @@ -0,0 +1,82 @@ +local total = 0 +do local value = 'ordinary string value 0: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 1: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 2: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 3: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 4: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 5: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 6: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 7: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 8: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 9: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 10: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 11: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 12: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 13: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 14: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 15: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 16: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 17: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 18: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 19: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 20: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 21: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 22: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 23: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 24: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 25: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 26: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 27: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 28: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 29: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 30: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 31: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 32: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 33: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 34: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 35: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 36: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 37: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 38: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 39: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 40: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 41: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 42: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 43: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 44: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 45: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 46: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 47: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 48: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 49: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 50: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 51: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 52: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 53: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 54: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 55: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 56: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 57: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 58: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 59: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 60: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 61: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 62: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 63: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 64: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 65: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 66: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 67: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 68: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 69: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 70: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 71: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 72: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 73: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 74: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 75: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 76: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 77: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 78: the quick brown fox jumps over the lazy dog'; total = total + #value end +do local value = 'ordinary string value 79: the quick brown fox jumps over the lazy dog'; total = total + #value end +return total diff --git a/sandbox/Benchmark/Utf8CharConversionBenchmark.cs b/sandbox/Benchmark/Utf8CharConversionBenchmark.cs deleted file mode 100644 index f289aa16..00000000 --- a/sandbox/Benchmark/Utf8CharConversionBenchmark.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text; -using BenchmarkDotNet.Attributes; -using Lua; - -[MemoryDiagnoser] -[ShortRunJob] -public class Utf8CharConversionBenchmark -{ - [Params("Ascii", "SparseUnicode", "Japanese", "Mixed", "InvalidUtf16")] - public string Corpus { get; set; } = "Ascii"; - - [Params(4096)] - public int Length { get; set; } - - string source = default!; - - [GlobalSetup] - public void Setup() - { - var unit = Corpus switch - { - "Ascii" => "hello world 1234 ", - "SparseUnicode" => new string('a', Length - 1) + "ế", - "Japanese" => "こんにちは世界", - "Mixed" => "aế😀漢字 ", - "InvalidUtf16" => "a\uD800b\uDC00c", - _ => throw new ArgumentOutOfRangeException(nameof(Corpus)), - }; - source = string.Concat(Enumerable.Repeat(unit, Length / unit.Length + 1))[..Length]; - } - - [Benchmark(Baseline = true)] - public string ViaBytes() => LuaByteString.FromBytes(Encoding.UTF8.GetBytes(source)); - - [Benchmark] - public string DirectChars() => LuaByteString.EncodeUtf8(source); -} diff --git a/sandbox/Benchmark/Workloads/BenchmarkScripts.cs b/sandbox/Benchmark/Workloads/BenchmarkScripts.cs new file mode 100644 index 00000000..5db64f68 --- /dev/null +++ b/sandbox/Benchmark/Workloads/BenchmarkScripts.cs @@ -0,0 +1,13 @@ +public static class BenchmarkScripts +{ + // Stable names also work when these sources are linked into the NativeAOT host. + public static string Read(string path) + { + var resourceName = "Benchmark.Scripts." + path.Replace('/', '.'); + using var stream = + typeof(BenchmarkScripts).Assembly.GetManifestResourceStream(resourceName) + ?? throw new InvalidOperationException($"Missing benchmark script: {path}"); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/sandbox/Benchmark/Workloads/ComparisonWorkloads.cs b/sandbox/Benchmark/Workloads/ComparisonWorkloads.cs new file mode 100644 index 00000000..6f3e969c --- /dev/null +++ b/sandbox/Benchmark/Workloads/ComparisonWorkloads.cs @@ -0,0 +1,46 @@ +public sealed record ComparisonCase(string Name, string ScriptPath) +{ + public string Source => BenchmarkScripts.Read(ScriptPath); +} + +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 = + [ + Execution("numeric-loop"), + Execution("calls-varargs"), + Execution("fib"), + Execution("metamethods"), + Execution("table-access"), + Execution("table-sort"), + Execution("map"), + Execution("math"), + Execution("strings"), + Execution("patterns"), + Execution("coroutines"), + Execution("host-calls"), + Execution("mandelbrot"), + Execution("nbody"), + ]; + + public static readonly ComparisonCase[] CompilationCases = + [ + Find("numeric-loop"), + Find("nbody"), + Compilation("control-flow"), + Compilation("identifiers"), + Compilation("plain-strings"), + Compilation("escaped-strings"), + ]; + + static ComparisonCase Execution(string name) => new(name, $"Comparison/{name}.lua"); + + static ComparisonCase Compilation(string name) => new(name, $"Compilation/{name}.lua"); + + public static ComparisonCase Find(string name) => Cases.Single(c => c.Name == name); + + public static ComparisonCase FindCompilation(string name) => + CompilationCases.Single(c => c.Name == name); +} diff --git a/sandbox/Benchmark/add.lua b/sandbox/Benchmark/add.lua deleted file mode 100644 index 91544d2f..00000000 --- a/sandbox/Benchmark/add.lua +++ /dev/null @@ -1,7 +0,0 @@ -local x = 0 - -for _ = 0, 25000 do - x = add(x, 1) -end - -return x \ No newline at end of file diff --git a/sandbox/Benchmark/coroutine.lua b/sandbox/Benchmark/coroutine.lua deleted file mode 100644 index d93892b1..00000000 --- a/sandbox/Benchmark/coroutine.lua +++ /dev/null @@ -1,17 +0,0 @@ -local function c() - for i = 1, 100 do - coroutine.yield(i) - end - - return 1000 -end - -local co = coroutine.create(c) - -local x = 0 -while coroutine.status(co) ~= "dead" do - local _, i = coroutine.resume(co) - x = x + i -end - -return x diff --git a/tools/DifferentialProbe/DifferentialProbe.csproj b/tools/DifferentialProbe/DifferentialProbe.csproj index efe0d6ad..3dede570 100644 --- a/tools/DifferentialProbe/DifferentialProbe.csproj +++ b/tools/DifferentialProbe/DifferentialProbe.csproj @@ -10,6 +10,6 @@ $(LuaAssemblyPath) - + diff --git a/tools/DifferentialProbe/README.md b/tools/DifferentialProbe/README.md index 0abfa78e..cae56f95 100644 --- a/tools/DifferentialProbe/README.md +++ b/tools/DifferentialProbe/README.md @@ -1,7 +1,7 @@ # Lua 5.2 differential checks Compare deterministic edge cases in fresh Lua-CSharp and native Lua 5.2 states -through NuLua.Lua52 0.1.0. Run from this directory to select the .NET 10 SDK: +through NuLua.Lua52 0.2.0. Run from this directory to select the .NET 10 SDK: ```powershell dotnet restore diff --git a/tools/PerformanceProbe/PerformanceProbe.csproj b/tools/PerformanceProbe/PerformanceProbe.csproj index 61b238db..ed93750e 100644 --- a/tools/PerformanceProbe/PerformanceProbe.csproj +++ b/tools/PerformanceProbe/PerformanceProbe.csproj @@ -13,6 +13,6 @@ - + diff --git a/sandbox/Benchmark/n-body.lua b/tools/PerformanceProbe/Scripts/n-body.lua similarity index 100% rename from sandbox/Benchmark/n-body.lua rename to tools/PerformanceProbe/Scripts/n-body.lua From 58d70769b6ab1984a8bea5f4afaadcefa85784b5 Mon Sep 17 00:00:00 2001 From: akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:45:38 +0900 Subject: [PATCH 2/2] Fix benchmark project formatting and remove obsolete CI verification --- .github/workflows/ci.yml | 3 --- sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj | 10 ++++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) 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/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj b/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj index 2991f6e8..d670ff90 100644 --- a/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj +++ b/sandbox/Benchmark.NativeAot/Benchmark.NativeAot.csproj @@ -8,9 +8,15 @@ - + - + Benchmark.Scripts.Comparison.%(Filename)%(Extension)