Merge pull request #112 from schungx/master

Undo secion changes to README on optimization levels.
This commit is contained in:
Stephen Chung 2020-03-21 00:41:37 +08:00 committed by GitHub
commit dfb8f31256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1317,7 +1317,7 @@ For example, in the following:
123; // eliminated - no effect
"hello"; // eliminated - no effect
[1, 2, x, x*2, 5]; // eliminated - no effect
foo(42); // NOT eliminated - functions calls are not touched
foo(42); // NOT eliminated - the function 'foo' may have side effects
666 // NOT eliminated - this is the return value of the block,
// and the block is the last one
// so this is the return value of the whole script
@ -1351,12 +1351,14 @@ are spliced into the script text in order to turn on/off certain sections.
For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object
to the [`Engine`] for use in compilation and evaluation.
Beware, however, that most operators are actually function calls, and those are not optimized away:
Beware, however, that most operators are actually function calls, and those functions can be overridden,
so they are not optimized away:
```rust
const DECISION = 1;
if DECISION == 1 { // NOT optimized away because it requires a call to the '==' function
if DECISION == 1 { // NOT optimized away because you can define
: // your own '==' function to override the built-in default!
:
} else if DECISION == 2 { // same here, NOT optimized away
:
@ -1367,8 +1369,8 @@ if DECISION == 1 { // NOT optimized away because it requires a call to
}
```
because no operator functions will be run during the optimization process (unless the optimization level is
set to [`OptimizationLevel::Full`]). So, instead, do this:
because no operator functions will be run (in order not to trigger side effects) during the optimization process
(unless the optimization level is set to [`OptimizationLevel::Full`]). So, instead, do this:
```rust
const DECISION_1 = true;
@ -1405,14 +1407,11 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
* `None` is obvious - no optimization on the AST is performed.
* `Simple` (default) relies exclusively on static analysis, performing relatively _safe_ optimizations only.
In particular, no function calls will be made to determine the output value. This also means that most comparison
operators and constant arithmetic expressions are untouched.
* `Simple` (default) performs relatively _safe_ optimizations without causing side effects
(i.e. it only relies on static analysis and will not actually perform any function calls).
* `Full` is _much_ more aggressive. Functions _will_ be run, when passed constant arguments, to determine their results.
One benefit is that many more optimization opportunities arise, especially with regards to comparison operators.
Nevertheless, the majority of scripts do not excessively rely on constants, and that is why this optimization level
is opt-in; only scripts that are machine-generated tend to have constants spliced in at generation time.
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
An [`Engine`]'s optimization level is set via a call to `set_optimization_level`:
@ -1422,15 +1421,15 @@ engine.set_optimization_level(rhai::OptimizationLevel::Full);
```
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
call all functions when passed constant arguments, using the results to replace the actual calls. This also affects all operators
because most of them are implemented as functions. For instance, the same example above:
evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators
(which are implemented as functions). For instance, the same example above:
```rust
// When compiling the following with OptimizationLevel::Full...
const DECISION = 1;
// this condition is now eliminated because 'DECISION == 1' is a
if DECISION == 1 { // function call to the '==' function with constant arguments, and it returns 'true'
// this condition is now eliminated because 'DECISION == 1'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
print("hello!"); // this block is promoted to the parent level
} else {
print("boo!"); // this block is eliminated because it is never reached
@ -1449,20 +1448,25 @@ let x = (1 + 2) * 3 - 4 / 5 % 6; // <- will be replaced by 'let x = 9'
let y = (1 > 2) || (3 <= 4); // <- will be replaced by 'let y = true'
```
Function side effect considerations
----------------------------------
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`]
is usually quite safe _unless_ you register your own types and functions.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if`
statement, for example) and cause side-effects.
Function volatility considerations
---------------------------------
Rhai functions never mutate state nor cause side any effects (except `print` and `debug` which are handled specially).
The only functions allowed to mutate state are custom type getters, setters and methods, and functions calls involving custom types
are never optimized. So using [`OptimizationLevel::Full`] is usually quite safe.
However, even if a function cannot mutate state nor cause side effects, it may still be _volatile_, i.e. it may
_depend_ on the external environment and not be _pure_. A perfect example is a function that gets the current time -
obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_ when it finds constant arguments.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call
will always be the same value.
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will
always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.