Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rust from Unity: the benchmark repository

Companion repository for Using Rust Code from Unity for High Performance. It holds the methodology, every implementation, and the raw results behind the numbers in the post: a utility AI scorer in Rust called over P/Invoke, measured against C# on Unity Mono, IL2CPP, Unity CoreCLR and standalone .NET 10, plus the Burst comparison and the SIMD work.

What is in here

  • utility-ai/ is the benchmark from the post: the Rust crate, the standalone C# console harness, the workload spec (SPEC.md), the full results matrix (MATRIX.txt), and expbench/, a small pair of programs that isolate the cost of exp in each language.
  • unity-benchmark/ and unity-benchmark-coreclr/ are the Unity projects the players are built from. The first is Unity 6000.3 and builds the Mono and IL2CPP players and the Burst jobs; the second is Unity 6000.7 with the experimental CoreCLR backend.
  • src/, benches/, examples/ and csharp-baseline/ are the JPS/A* pathfinder, an earlier phase of the same investigation. The post no longer covers it, but the methodology documents reference it throughout, so it stays.
  • runs/ holds the raw per-run measurement files for the Burst, fearless_simd and SIMD-JPS sweeps. Older raw results sit next to the projects that produced them (unity-benchmark*/study/, unity-benchmark*/*.txt, csharp-baseline/BenchmarkDotNet.Artifacts/).

The reports, in reading order:

  • METHODOLOGY.md states the effort-parity protocol: how to benchmark two languages without measuring which side got more attention.
  • IDIOMATIC.md defines the idiomatic comparison: what each side may and may not use when both are written the way people actually write them.
  • utility-ai/SPEC.md specifies the utility AI workload and its data.
  • utility-ai/SUITE.md is the utility AI sweep across all six runtimes, with build provenance for every player.
  • utility-ai/INTENSITY.md asks whether Rust's lead grows with arithmetic intensity. It does not, and the document shows what the lead actually is.
  • BURST.md measures Burst on both workloads: what the port costs, what it buys, and where safe Rust through fearless_simd lands against it.
  • SIMD-JPS.md is a four-wide JPS built to test the memory-floor explanation. It loses, as predicted, and the document explains why that is the result.
  • FINAL-RESULTS.md is the consolidated pathfinding sweep, one machine state, one code state.
  • csharp-baseline/EFFORT.md records every optimization each pathfinder implementation received, so effort parity can be checked rather than trusted.

How to run

The Rust dylibs are build outputs and are not checked in. Build them first and copy them to where the harnesses and players expect them:

# the utility AI engine
cd utility-ai/rust && cargo build --release && cd ../..
cp utility-ai/rust/target/release/libutility_ai.dylib utility-ai/csharp/
cp utility-ai/rust/target/release/libutility_ai.dylib unity-benchmark/Assets/Plugins/macOS/
cp utility-ai/rust/target/release/libutility_ai.dylib unity-benchmark-coreclr/Assets/Plugins/macOS/

# the pathfinder
cargo build --release
cp target/release/libpathfind.dylib csharp-baseline/
cp target/release/libpathfind.dylib unity-benchmark/Assets/Plugins/macOS/
cp target/release/libpathfind.dylib unity-benchmark-coreclr/Assets/Plugins/macOS/

The console harness is the fastest way to reproduce the core comparison. It times the Rust engine and both C# engines in one process on identical data:

cd utility-ai/csharp
dotnet run -c Release -- core     # rust enum, C# optimized, C# idiomatic
dotnet run -c Release -- check    # times nothing; asserts bit-identical results across engines

Modes are all | core | rust | opt | idio | check. The workload is shaped by environment variables: AI_CHARACTERS, AI_ACTIONS, AI_SCORERS_PER_ACTION, AI_THREADS, AI_REPS, AI_PRUNE, AI_EXP_PCT, AI_CURVES, AI_SIMD. The published configuration is the default 200 x 1000 x 6 with AI_PRUNE=0.

The Unity players build headless through Assets/Editor/BuildScript.cs:

Unity -batchmode -nographics -quit -projectPath unity-benchmark \
  -executeMethod BuildScript.BuildMonoNoIncr    # or BuildMonoIncr, BuildIl2cppNoIncr,
                                                # BuildIl2cppIncr, BuildMonoDev
Unity -batchmode -nographics -quit -projectPath unity-benchmark-coreclr \
  -executeMethod BuildScript.BuildCoreClr

A built player runs the benchmark on launch and writes its results to BENCH_OUT:

BENCH_ENGINE=utility BENCH_THREADS=6 BENCH_SUSTAINED=1000 AI_PRUNE=0 \
  BENCH_OUT="$PWD/out.txt" \
  unity-benchmark/Builds/mono-noincr/bench.app/Contents/MacOS/unity-benchmark -batchmode -nographics

BENCH_ENGINE=jps (the default) runs the pathfinding workload instead, sized by BENCH_REQUESTS. Every run validates before it reports a time: the C# engines must pick the same action as the Rust reference with bit-identical scores for all 200 characters, and the vector engines must match to within 3e-8.

The sweep drivers are the shell scripts at the root. burst-measure.sh is the gated single-player runner; burst-run-all.sh, burst-phase2.sh and simd-jps-run.sh are the sweeps behind BURST.md and SIMD-JPS.md; burst-collect.py reduces run files to medians of run-medians. Variant players build from project clones under build-clones/ (set BUILD_CLONES to move them), because Unity locks a project directory.

Machine notes

Every number in the post comes from one machine: Apple M5 Max, 6 performance cores and 12 efficiency cores, macOS, with all benchmark work capped to the 6 performance cores. The drivers gate each run on a quiet machine (burst-gate.sh: at least 85% idle, no other process above a quarter of a core) and discard runs whose after-gate fails. Your numbers will differ, and on anything other than Apple Silicon the SIMD results will differ structurally, since the vector engines are NEON-shaped.

The effort-parity rules

Both sides must do provably identical work, checked by validation before any time is reported. Every optimization that wins on one side is only a hypothesis for the other until it has been tried there. The stopping rule is the profile going flat, not the number getting satisfying. The full protocol is in METHODOLOGY.md, and the rules for the idiomatic comparison are in IDIOMATIC.md.

About

Rust from Unity over P/Invoke: a utility AI benchmark against C# on Mono, IL2CPP, CoreCLR and .NET 10, plus Burst and SIMD comparisons. Companion repo for the blog post.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages