Benchmarking Rhai

Tip: Nothing beats a JIT

Needless to say V8 (JavaScript), which is a JIT compiler with type specialization, blows any interpreter (AST or bytecodes based) out of the water, as also should LuaJIT.

So if you absolutely must have top performance…

The purpose of Rhai is not to be blazing fast, but to make it as easy and versatile as possible to integrate with native Rust applications.

What you lose from running an AST walker, you gain back from increased flexibility.

The following benchmarks were run on a 2.6GHz Linux VM comparing performance-optimized and full builds of Rhai with Python 3 and V8 (Node.js).

BenchmarkRhai
(Perf)
Rhai
(Full)
Python 3
(bytecodes)
V8
(JIT)
Description
Fibonacci2.25s3.2s0.6s0.07sstresses recursive function calls
1M loop0.13s0.2s0.08s0.05sa simple counting loop (1 million iterations) that must run as fast as possible
Prime numbers0.85s1.2s0.4s0.09sa closer-to-real-life calculation workload

In general, Rhai is roughly 2x slower than Python 3, which is a bytecodes interpreter, for typical real-life workloads.

TL;DR – Rhai is usually fast enough

Small data structures

Essential AST and runtime data structures are packed small and kept together to maximize cache friendliness.

Pre-calculations

Functions are dispatched based on pre-calculated hashes. Variables are mostly accessed through pre-calculated offsets to the variables file (a Scope). It is seldom necessary to look something up by name.

Caching

Function resolutions are cached so they do not incur lookup costs after a couple of calls.

No scope-chain

Maintaining a scope chain is deliberately avoided by design so function scopes do not pay any speed penalty. This allows variables data to be kept together in a contiguous block, avoiding allocations and fragmentation while being cache-friendly.

Immutable strings

Rhai uses immutable strings to bypass cloning issues.

No sharing

In a typical script evaluation run, no data is shared and nothing is locked (other than variables captured by closures).

DO NOT: Write the next 4D VR game entirely in Rhai

Rhai deliberately keeps the language small and lean by omitting advanced language features such as classes, inheritance, interfaces, generics, first-class functions/closures, pattern matching, monads (whatever), concurrency, async etc.

Focus is on flexibility and ease of use instead of a powerful, expressive language.

Avoid the temptation to write full-fledge application logic entirely in Rhai – that use case is best fulfilled by more complete scripting languages such as JavaScript or Lua.

DO THIS: Use Rhai as a thin dynamic wrapper layer over Rust code

In actual practice, it is usually best to expose a Rust API into Rhai for scripts to call.

All the core functionalities should be written in Rust, with Rhai being the dynamic control layer.

This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ standard library (e.g. Python 3).

Another similar scenario is a web front-end driving back-end services written in a systems language. In this case, JavaScript takes the role of Rhai while the back-end language, well… it can actually also be Rust. Except that Rhai integrates with Rust much more tightly, removing the need for interfaces such as XHR calls and payload encoding such as JSON.