Add optimization section to README.
This commit is contained in:
parent
7e9a4fd965
commit
da440a5dff
133
README.md
133
README.md
@ -181,21 +181,22 @@ engine.consume(
|
|||||||
x.len() + y // returning i64
|
x.len() + y // returning i64
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hello(x) { // script-functions can be overloaded: this one takes only one parameter
|
fn hello(x) { // functions can be overloaded: this one takes only one parameter
|
||||||
x * 2 // returning i64
|
x * 2 // returning i64
|
||||||
}
|
}
|
||||||
", true)?; // pass true to 'retain_functions' otherwise these functions will be cleared
|
", true)?; // pass true to 'retain_functions' otherwise these functions
|
||||||
// at the end of consume()
|
// will be cleared at the end of consume()
|
||||||
|
|
||||||
// Evaluate the function in the AST, passing arguments into the script as a tuple if there are more than one.
|
// Evaluate the function in the AST, passing arguments into the script as a tuple
|
||||||
// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions.
|
// if there are more than one. Beware, arguments must be of the correct types because
|
||||||
// If you pass in arguments of the wrong type, the Engine will not find the function.
|
// Rhai does not have built-in type conversions. If you pass in arguments of the wrong type,
|
||||||
|
// the Engine will not find the function.
|
||||||
|
|
||||||
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
|
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
|
||||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
|
||||||
|
|
||||||
let result: i64 = engine.call_fn("hello", 123_i64)?
|
let result: i64 = engine.call_fn("hello", 123_i64)?
|
||||||
// ^^^^^^^ this one calls the 'hello' function with one parameter (so need for tuple)
|
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple)
|
||||||
```
|
```
|
||||||
|
|
||||||
Values and types
|
Values and types
|
||||||
@ -255,8 +256,8 @@ Rhai's scripting engine is very lightweight. It gets its ability from the funct
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use rhai::RegisterFn; // include the `RegisterFn` trait to use `register_fn`
|
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||||
use rhai::{Dynamic, RegisterDynamicFn}; // include the `RegisterDynamicFn` trait to use `register_dynamic_fn`
|
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||||
|
|
||||||
// Normal function
|
// Normal function
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -336,13 +337,13 @@ Your function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
use rhai::{Engine, EvalAltResult, Position};
|
||||||
use rhai::RegisterResultFn; // include the `RegisterResultFn` trait to use `register_result_fn`
|
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn`
|
||||||
|
|
||||||
// Function that may fail
|
// Function that may fail
|
||||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
// Return an error if y is zero
|
// Return an error if y is zero
|
||||||
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
||||||
} else {
|
} else {
|
||||||
Ok(x / y)
|
Ok(x / y)
|
||||||
}
|
}
|
||||||
@ -540,21 +541,19 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Then push some initialized variables into the state
|
// Then push some initialized variables into the state
|
||||||
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
// NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64.
|
||||||
// Better stick to them or it gets hard to work with other variables in the script.
|
// Better stick to them or it gets hard working with the script.
|
||||||
scope.push("y".into(), 42_i64);
|
scope.push("y".into(), 42_i64);
|
||||||
scope.push("z".into(), 999_i64);
|
scope.push("z".into(), 999_i64);
|
||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
// (the second boolean argument indicates that we don't need to retain function definitions
|
engine.eval_with_scope::<()>(&mut scope, r"
|
||||||
// because we didn't declare any!)
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, r"
|
|
||||||
let x = 4 + 5 - y + z;
|
let x = 4 + 5 - y + z;
|
||||||
y = 1;
|
y = 1;
|
||||||
")?;
|
")?;
|
||||||
|
|
||||||
// Second invocation using the same state
|
// Second invocation using the same state
|
||||||
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||||
|
|
||||||
println!("result: {}", result); // should print 966
|
println!("result: {}", result); // should print 966
|
||||||
|
|
||||||
@ -668,7 +667,8 @@ let age = 42;
|
|||||||
let record = full_name + ": age " + age;
|
let record = full_name + ": age " + age;
|
||||||
record == "Bob C. Davis: age 42";
|
record == "Bob C. Davis: age 42";
|
||||||
|
|
||||||
// Strings can be indexed to get a character (disabled with the 'no_index' feature)
|
// Strings can be indexed to get a character
|
||||||
|
// (disabled with the 'no_index' feature)
|
||||||
let c = record[4];
|
let c = record[4];
|
||||||
c == 'C';
|
c == 'C';
|
||||||
|
|
||||||
@ -1052,7 +1052,8 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
|||||||
### Overriding `print` and `debug` with callback functions
|
### Overriding `print` and `debug` with callback functions
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Any function or closure that takes an &str argument can be used to override print and debug
|
// Any function or closure that takes an &str argument can be used to override
|
||||||
|
// print and debug
|
||||||
engine.on_print(|x| println!("hello: {}", x));
|
engine.on_print(|x| println!("hello: {}", x));
|
||||||
engine.on_debug(|x| println!("DEBUG: {}", x));
|
engine.on_debug(|x| println!("DEBUG: {}", x));
|
||||||
|
|
||||||
@ -1063,7 +1064,7 @@ let mut log: Vec<String> = Vec::new();
|
|||||||
engine.on_print(|s| log.push(format!("entry: {}", s)));
|
engine.on_print(|s| log.push(format!("entry: {}", s)));
|
||||||
engine.on_debug(|s| log.push(format!("DEBUG: {}", s)));
|
engine.on_debug(|s| log.push(format!("DEBUG: {}", s)));
|
||||||
|
|
||||||
// Evalulate script
|
// Evaluate script
|
||||||
engine.eval::<()>(script)?;
|
engine.eval::<()>(script)?;
|
||||||
|
|
||||||
// 'log' captures all the 'print' and 'debug' output
|
// 'log' captures all the 'print' and 'debug' output
|
||||||
@ -1072,6 +1073,98 @@ for entry in log {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optimizations
|
||||||
|
=============
|
||||||
|
|
||||||
|
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
||||||
|
|
||||||
|
For example, in the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on
|
||||||
|
123; // eliminated - no effect
|
||||||
|
"hello"; // eliminated - no effect
|
||||||
|
[1, 2, x, x*2, 5]; // eliminated - no effect
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai).
|
||||||
|
The above script optimizes to:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
let x = 999;
|
||||||
|
foo(42);
|
||||||
|
666
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Constant propagation is used to remove dead code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
||||||
|
if true { print("done!"); } // <-- the line above is equivalent to this
|
||||||
|
print("done!"); // <-- the line above is further simplified to this
|
||||||
|
// because the condition is always true
|
||||||
|
```
|
||||||
|
|
||||||
|
These are quite effective for template-based machine-generated scripts where certain constant values are spliced into the script text in order to turn on/off certain sections.
|
||||||
|
|
||||||
|
Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if 1 == 1 { ... } // '1==1' is NOT optimized away because you can define
|
||||||
|
// your own '==' function to override the built-in default!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Here be dragons!
|
||||||
|
|
||||||
|
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if true { // <-- condition always true
|
||||||
|
123.456; // <-- eliminated
|
||||||
|
hello; // <-- eliminated, EVEN THOUGH the variable doesn't exist!
|
||||||
|
foo(42) // <-- promoted up-level
|
||||||
|
}
|
||||||
|
|
||||||
|
// The above optimizes to:
|
||||||
|
|
||||||
|
foo(42)
|
||||||
|
```
|
||||||
|
|
||||||
|
Nevertheless, if you would be evaluating the original script, it would have been an error - the variable `hello` doesn't exist, so the script would have been terminated at that point with an error return.
|
||||||
|
|
||||||
|
In fact, any errors inside a statement that has been eliminated will silently _go away_:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
print("start!");
|
||||||
|
if my_decision { /* do nothing... */ } // <-- eliminated due to no effect
|
||||||
|
print("end!");
|
||||||
|
|
||||||
|
// The above optimizes to:
|
||||||
|
|
||||||
|
print("start!");
|
||||||
|
print("end!");
|
||||||
|
```
|
||||||
|
|
||||||
|
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
||||||
|
However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors.
|
||||||
|
|
||||||
|
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess),
|
||||||
|
there is a setting in `Engine` to turn off optimizations.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let engine = rhai::Engine::new();
|
||||||
|
engine.set_optimization(false); // turn off the optimizer
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
[ChaiScript]: http://chaiscript.com/
|
[ChaiScript]: http://chaiscript.com/
|
||||||
[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting
|
[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting
|
||||||
[awesome-rust]: https://github.com/rust-unofficial/awesome-rust
|
[awesome-rust]: https://github.com/rust-unofficial/awesome-rust
|
||||||
|
Loading…
Reference in New Issue
Block a user