Skip to content

Performance and Benchmarks

Liu.Yandong.Hanks edited this page Aug 21, 2026 · 4 revisions

Performance and Benchmarks

Run reproducible benchmarks, compare documented baselines, and optimize measured hot paths.

Applies to 4.0.0.

Home · Language Guide

On this page

Running Benchmarks

The unified benchmark project lives in benchmark/ and covers both runtime hot paths and the compiler pipeline.

# Quick smoke check
dotnet run --project benchmark/Benchmark.csproj -c Release -- --smoke

# Write a simple CSV comparison
dotnet run --project benchmark/Benchmark.csproj -c Release -- --compare

# Run the complete BenchmarkDotNet suite
dotnet run --project benchmark/Benchmark.csproj -c Release

Run the smoke check, simple CSV comparison, or the complete BenchmarkDotNet suite with the commands above.

Coverage includes domain creation, empty calls, function calls, module calls, closure calls, objects, arrays, HashMap, strings, JSON, TDoc, Regex, CLR interop, and the lexer, parser, emitter, single-/multi-module compilation, and CompileBlock.

TDoc benchmarks cover primitive reads/writes, ordinary nested-object reads/writes, general Array, Int32Array, and wide-object reads to track parser hot paths, ordinary-object writing, and packed-array allocation regressions.

dotnet run --project benchmark/Benchmark.csproj -c Release -- --filter "*TypedDocument*"

TDoc Hot-path Baseline

The results below are from a Release/net10.0 BenchmarkDotNet ShortRun on 2026-08-20: Windows 11, Intel Xeon W-2235, .NET SDK 10.0.400, and Runtime .NET 10.0.11. ShortRun is for regression comparison, not an absolute cross-machine service-level guarantee.

Benchmark Mean Allocated Notes
SerializePrimitive 226.0 ns 32 B Result-string allocation; no intermediate tree
DeserializePrimitive 246.7 ns 0 B Streaming scan and direct binding
SerializeNestedObject 1.289 µs 296 B Canonical text and result string
DeserializeNestedObject 2.410 µs 640 B Constructs the target script object
SerializePackedInt32Array1024 144.817 µs 20,360 B Text output is the primary allocation
DeserializePackedInt32Array1024 168.155 µs 4,192 B Target int[]/packed array
DeserializeGenericArray1024 178.815 µs 32,992 B General ScriptDatum storage per element
DeserializeWideObject256 371.489 µs 16,608 B 256 properties on the target object

The table records mean time and managed allocation. Primitive parsing stays allocation-free; object and array allocations shown here are the returned data or output text, not an intermediate syntax tree.

Recorded Hot-path Results

The following results are from a Release/net10.0 BenchmarkDotNet ShortRun on 2026-08-15. They are a documentation baseline; regenerate the full suite by running without --filter.

dotnet run --project benchmark/Benchmark.csproj -c Release -- \
  --filter "*FunctionCallLoop*" "*ModuleCallLoop*" "*NumericLoop*"
Benchmark Size Mean Allocated Observation
NumericLoop 1,000 1.477 µs 0 B Numeric locals and arithmetic remain native CIL
NumericLoop 10,000 13.483 µs 0 B Linear time growth with no loop allocation
FunctionCallLoop 1,000 82.668 µs 0 B Same-module numeric calls use native specialization
FunctionCallLoop 10,000 766.803 µs 0 B Reused call frames; no per-call context allocation
ModuleCallLoop 1,000 122.591 µs 0 B Zero-allocation cross-module generic call boundary
ModuleCallLoop 10,000 1.186 ms 0 B Controlled dynamic dispatch cost with no per-call allocation

The table records means and allocations for the documented hot paths. All six cases measured zero managed allocations on the execution path.

The environment was BenchmarkDotNet 0.15.8, Windows 11 10.0.28000.2704, Intel Core i7-13700KF, .NET SDK 10.0.400, Runtime .NET 10.0.11, and the ShortRun job. The benchmark reuses prebuilt host argument arrays, so Allocated reflects the actual execution path only.

Writing Recommendations

  • Cache length, module constants, and repeated property reads inside hot loops.
  • Use packed arrays rather than general Array for known-length numeric work.
  • Use StringBuffer for substantial text construction instead of repeated string concatenation in a loop.
  • Choose Persistence only when durable module output or debugging symbols are truly required; otherwise use the lightest mode that meets the need.
  • Measure before optimizing, then run both functional tests and benchmarks afterward.

Next steps

Clone this wiki locally