Benchmarking Rhai
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).
Benchmark | Rhai (Perf) | Rhai(Full) | Python 3 (bytecodes) | V8 (JIT) | Description |
---|---|---|---|---|---|
Fibonacci | 2.25s | 3.2s | 0.6s | 0.07s | stresses recursive function calls |
1M loop | 0.13s | 0.2s | 0.08s | 0.05s | a simple counting loop (1 million iterations) that must run as fast as possible |
Prime numbers | 0.85s | 1.2s | 0.4s | 0.09s | a 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.
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).
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.
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.