This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_engine/rhaibook/start/builds/performance.md
2025-04-03 09:18:05 +02:00

205 lines
7.3 KiB
Markdown

Performance Build
=================
{{#include ../../links.md}}
Some features are for performance. In order to squeeze out the maximum performance from Rhai, the
following features should be considered:
| Feature | Description | Rationale |
| -------------------- | ------------------------------------------------------ | ------------------------ |
| [`only_i32`] | support only a single `i32` integer type | reduce data size |
| [`no_float`] | remove support for floating-point numbers | reduce code size |
| [`f32_float`] | set floating-point numbers (if not disabled) to 32-bit | reduce data size |
| [`no_closure`] | remove support for [variables] sharing | no need for data locking |
| [`unchecked`] | disable all safety [checks][checked] | remove checking code |
| [`no_module`] | disable loading external [modules] | reduce code size |
| [`no_position`] | disable position tracking during parsing | reduce data size |
| [`no_custom_syntax`] | disable [custom syntax] | reduce code size |
When the above feature flags are used, performance may increase by around 15-20%.
~~~admonish info.small "See also: Benchmarks"
See Rhai performance [benchmarks].
~~~
Unchecked Build
---------------
By default, Rhai provides a [_Don't Panic_](https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy#Don't_Panic)
guarantee and prevents malicious scripts from bringing down the host. Any panic can be considered a bug.
For maximum performance, however, these [safety] checks can be turned off via the [`unchecked`] feature.
Fast Operators Mode
-------------------
Make sure that [_Fast Operators_ Mode][options], which is enabled by default, is on. It ignores any
user [overloading][operator overloading] of [built-in operators].
For operator-heavy scripts, this may provide a substantial speed-up.
Use Only One Integer Type
-------------------------
If only a single integer type is needed in scripts – most of the time this is the case –
it is best to avoid registering lots of functions related to other integer types that will never be used.
As a result, [`Engine`] creation will be faster because fewer functions need to be loaded.
The [`only_i32`] and [`only_i64`] features disable all integer types except `i32` or `i64` respectively.
Use Only 32-Bit Numbers
-----------------------
If only 32-bit integers are needed – again, most of the time this is the case – turn on [`only_i32`].
Under this feature, only `i32` is supported as a built-in integer type and no others.
On 64-bit targets this may not gain much, but on certain 32-bit targets this improves performance
due to 64-bit arithmetic requiring more CPU cycles to complete.
Minimize Size of `Dynamic`
--------------------------
Turning on [`f32_float`] (or [`no_float`]) and [`only_i32`] on 32-bit targets makes the critical
[`Dynamic`] data type only 8 bytes long for 32-bit targets.
Normally [`Dynamic`] needs to be up 12-16 bytes long in order to hold an `i64` or `f64`.
A smaller [`Dynamic`] helps performance due to better cache efficiency.
Use `ImmutableString`
---------------------
Internally, Rhai uses _immutable_ [strings] instead of the Rust `String` type.
This is mainly to avoid excessive cloning when passing function arguments.
Rhai's internal string type is [`ImmutableString`] (basically `Rc<SmartString>` or
`Arc<SmartString>` depending on the [`sync`] feature). It is cheap to clone, but expensive to modify
(a new copy of the string must be made in order to change it).
Therefore, functions taking `String` parameters should use [`ImmutableString`] or `&str`
(maps to [`ImmutableString`]) for the best performance with Rhai.
Disable Capturing in Closures
-----------------------------
```admonish info.side "Anonymous functions still work"
[Anonymous functions] continue to work even under [`no_closure`].
Only capturing of external shared [variables] is disabled.
```
Support for [closures] that capture _shared_ [variables] adds material overhead to script evaluation.
This is because every data access must be checked whether it is a shared value and, if so,
take a read lock before reading it.
As the vast majority of [variables] are _not_ shared, needless to say this is a non-trivial
performance overhead.
Use [`no_closure`] to disable support for [closures] to optimize the hot path because it no longer
needs to take locks for shared data.
Disable Position
----------------
For embedded scripts that are not expected to cause errors, the [`no_position`] feature can be used
to disable position tracking during parsing.
No line number/character position information is kept for error reporting purposes.
This may result in a slightly fast build due to elimination of code related to position tracking.
Avoid Cloning
-------------
### Use `&mut` functions
Rhai values are typically _cloned_ when passed around, especially into [function] calls.
Large data structures may incur material cloning overhead.
Some functions accept the first parameter as a mutable reference (i.e. `&mut`), for example
_methods_ for [custom types], and may avoid potentially-costly cloning.
### Compound assignment
For example, the `+=` (append) compound assignment takes a mutable reference to the [variable] while
the corresponding `+` (add) assignment usually doesn't. The difference in performance can be huge:
```rust
let x = create_some_very_big_and_expensive_type();
x = x + 1;
// ^ 'x' is cloned here
// The above is equivalent to:
let temp_value = x.clone() + 1;
x = temp_value;
x += 1; // <- 'x' is NOT cloned
```
### Use `take`
Another example: use the `take` function to extract a value out of a variable (replacing it with
[`()`]) without cloning.
```rust
let x = create_some_very_big_and_expensive_type();
let y = x; // <- 'x' is cloned here
let y = x.take(); // <- 'x' is NOT cloned
```
```admonish tip "Tip: Simple variable references are already optimized"
Rhai's script [optimizer][script optimization] is usually smart enough to _rewrite_ function calls
into [_method-call_]({{rootUrl}}/rust/methods.md) style or [_compound assignment_]({{rootUrl}}/language/assignment-op.md)
style to take advantage of this.
However, there are limits to its intelligence, and only **simple variable references** are optimized.
~~~rust
x = x + 1; // <- this statement...
x += 1; // ... is rewritten as this
x[y] = x[y] + 1; // <- but this is not, so this is MUCH slower...
x[y] += 1; // ... than this
some_func(x, 1); // <- this statement...
x.some_func(1); // ... is rewritten as this
some_func(x[y], 1); // <- but this is not, so 'x[y]` is cloned
~~~
```
Short Variable Names for 32-Bit Systems
---------------------------------------
On 32-bit systems, [variable] and [constant] names longer than 11 ASCII characters incur additional
allocation overhead.
This is particularly true for local variables inside a hot loop, where they are created and destroyed
in rapid succession.
Therefore, avoid long [variable] and [constant] names that are over this limit.
On 64-bit systems, this limit is raised to 23 ASCII characters, which is almost always adequate.