Skip to main content

Lua ↔ C# interop performance comparison

Formal conclusions cite Il2Cpp Player only (Mono Editor does not represent ship performance).

Benchmark project: focus-creative-games/zlua-benchmark (ZLua / xLua / toLua / SLua four-way alignment). Latest report: comparison_20260728_121554.md

ItemValue
Unity2022.3.62f3 · Il2Cpp · C++ Release · Win64
StatsAfter warmup, 5 rounds mid ns/op; ratio = other / ZLua (>1 means slower than ZLua)
Scopecs2lua 54 + lua2cs 231; no steady-state Delegate; GC Alloc not measured (see GC)

Reproduce: run_benchmark.ps1 in the repo README (or -CompareOnly).


Measured summary

Average ratio vs ZLua:

DirectioncasesxLuatoLuaSLua
Lua→C#2312.57×3.52×7.68×
C#→Lua541.59×3.27×14.9×

Vs xLua: ~98.6% of cases ahead (281/285). Per-case details in the full report.

Representative cases (ns/call mid)

Scenariocase_idZLuaxLuaratio
No-arg methodlua2cs.instance.method.0arg.void21.5758.642.72
prop get intlua2cs.instance.prop.get.int21.6779.033.65
field get intlua2cs.instance.field.get.int21.3275.233.53
field set Vector3lua2cs.instance.field.set.Vector322.8090.903.99
C#→Lua classcs2lua.class.3arg.ret94.00461.004.90
C#→Lua int[]cs2lua.int[].3arg.ret92.00601.006.53

toLua / SLua same-case numbers are in the report.

Reading tip: even if interop is several times faster, if the boundary is only a small share of frame time, whole-frame gains are limited — profile first. When string / heavy objects dominate marshal, gaps shrink.


Why faster

Typical hot paths in xLua / toLua / SLua go through C# Wrap + multiple Lua binding / P/Invoke layers (e.g. LuaDLL) before the target method; each stack read/write crosses the boundary several times.

ZLua Player finishes once in C++ linked with lua: read stack → marshal → methodPointer (or field offset) → write stack — no per-type C# Wrap, and no repeated native↔managed round-trips.

Root causeEffect
No libxlua / multi-layer P/InvokeStack ops no longer cross the boundary every time
No generated C# Wrap (Lua→C#)C++ direct bridge + methodPointer
C#→Lua via cached Delegate, one bridgeNot a C# loop calling LuaDLL
Field / no-arg property fast pathIl2Cpp offset direct read

So lightweight int / field cases lead more clearly; heavy marshal converges — matching measurements. Bridge size and stub reuse: BRIDGE.


DocContent
zlua-benchmarkReproducible benchmarks and latest reports
GC.mdAllocation and “zero GC” boundaries
BRIDGE.mdStub size and indirection
FEATURES.mdFeature differences