Make fast operators the default.

This commit is contained in:
Stephen Chung 2022-09-03 22:07:36 +08:00
parent defdc2a5bc
commit 702bb9030a
11 changed files with 73 additions and 56 deletions

View File

@ -4,6 +4,11 @@ Rhai Release Notes
Version 1.10.0
==============
This version, by default, turns on _Fast Operators_ mode, which assumes that built-in operators for
standard data types are never overloaded – in the vast majority of cases this should be so.
Avoid checking for overloads may result in substantial speed improvements especially for
operator-heavy scripts.
The minimum Rust version is now `1.61.0` in order to use some `const` generics.
Bug fixes
@ -23,7 +28,7 @@ New features
### Fast operators
* A new option `Engine::fast_operators` is introduced that short-circuits all built-in operators of built-in types for higher speed. User overloads are ignored. For operator-heavy scripts, this may yield substantial speed-up's.
* A new option `Engine::fast_operators` is introduced (default to `true`) that short-circuits all built-in operators of built-in types for higher speed. User overloads are ignored. For operator-heavy scripts, this may yield substantial speed-up's.
### Fallible type iterators

View File

@ -53,23 +53,6 @@ fn bench_eval_scope_longer(bench: &mut Bencher) {
bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_eval_scope_longer_fast_ops(bench: &mut Bencher) {
let script = "(requests_made * requests_succeeded / 100) >= 90";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
engine.set_fast_operators(true);
let mut scope = Scope::new();
scope.push("requests_made", 99 as INT);
scope.push("requests_succeeded", 90 as INT);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.run_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_eval_scope_complex(bench: &mut Bencher) {
let script = r#"

View File

@ -35,17 +35,24 @@ bitflags! {
impl LangOptions {
/// Create a new [`LangOptions`] with default values.
#[inline(always)]
#[must_use]
pub fn new() -> Self {
Self::IF_EXPR | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING | Self::SHADOW | {
#[cfg(not(feature = "no_function"))]
{
Self::ANON_FN
Self::IF_EXPR
| Self::SWITCH_EXPR
| Self::STMT_EXPR
| Self::LOOPING
| Self::SHADOW
| Self::FAST_OPS
| {
#[cfg(not(feature = "no_function"))]
{
Self::ANON_FN
}
#[cfg(feature = "no_function")]
{
Self::empty()
}
}
#[cfg(feature = "no_function")]
{
Self::empty()
}
}
}
}

View File

@ -190,8 +190,8 @@ pub struct FnCallExpr {
pub args: StaticVec<Expr>,
/// Does this function call capture the parent scope?
pub capture_parent_scope: bool,
/// Is this function call a simple symbol-based operator?
pub is_standard_operator: bool,
/// Is this function call a native operator?
pub is_native_operator: bool,
/// [Position] of the function name.
pub pos: Position,
}
@ -206,8 +206,8 @@ impl fmt::Debug for FnCallExpr {
if self.capture_parent_scope {
ff.field("capture_parent_scope", &self.capture_parent_scope);
}
if self.is_standard_operator {
ff.field("is_standard_operator", &self.is_standard_operator);
if self.is_native_operator {
ff.field("is_native_operator", &self.is_native_operator);
}
ff.field("hash", &self.hashes)
.field("name", &self.name)
@ -667,7 +667,7 @@ impl Expr {
hashes: calc_fn_hash(f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false,
is_standard_operator: false,
is_native_operator: false,
pos,
}
.into(),

View File

@ -50,8 +50,6 @@ fn main() {
// Initialize scripting engine
let mut engine = Engine::new();
engine.set_fast_operators(true);
#[cfg(not(feature = "no_optimize"))]
engine.set_optimization_level(rhai::OptimizationLevel::Simple);

View File

@ -227,14 +227,14 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
namespace,
capture_parent_scope: capture,
is_standard_operator: std_ops,
is_native_operator: native_ops,
hashes,
args,
..
} = expr;
// Short-circuit operator call if under Fast Operators mode
if *std_ops && self.fast_operators() {
// Short-circuit native binary operator call if under Fast Operators mode
if *native_ops && self.fast_operators() && args.len() == 2 {
let mut lhs = self
.get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)?
.0
@ -314,8 +314,19 @@ impl Engine {
);
self.make_function_call(
scope, global, caches, lib, this_ptr, name, first_arg, args, *hashes, *capture,
*std_ops, pos, level,
scope,
global,
caches,
lib,
this_ptr,
name,
first_arg,
args,
*hashes,
*capture,
*native_ops,
pos,
level,
)
}

View File

@ -989,7 +989,7 @@ impl Engine {
args_expr: &[Expr],
hashes: FnCallHashes,
capture_scope: bool,
is_standard_operator: bool,
is_operator: bool,
pos: Position,
level: usize,
) -> RhaiResult {
@ -1002,7 +1002,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call
match name {
_ if is_standard_operator => (),
_ if is_operator => (),
// Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => {

View File

@ -616,7 +616,7 @@ impl Engine {
return Ok(FnCallExpr {
name: state.get_interned_string(id),
capture_parent_scope,
is_standard_operator: false,
is_native_operator: false,
#[cfg(not(feature = "no_module"))]
namespace,
hashes,
@ -688,7 +688,7 @@ impl Engine {
return Ok(FnCallExpr {
name: state.get_interned_string(id),
capture_parent_scope,
is_standard_operator: false,
is_native_operator: false,
#[cfg(not(feature = "no_module"))]
namespace,
hashes,
@ -1922,6 +1922,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
args,
pos,
is_native_operator: true,
..Default::default()
}
.into_fn_call_expr(pos))
@ -1949,6 +1950,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
args,
pos,
is_native_operator: true,
..Default::default()
}
.into_fn_call_expr(pos))
@ -1967,6 +1969,7 @@ impl Engine {
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
args,
pos,
is_native_operator: true,
..Default::default()
}
.into_fn_call_expr(pos))
@ -2341,7 +2344,7 @@ impl Engine {
name: state.get_interned_string(op.as_ref()),
hashes: FnCallHashes::from_native(hash),
pos,
is_standard_operator: op_token.is_standard_symbol(),
is_native_operator: !is_valid_function_name(&op),
..Default::default()
};

View File

@ -75,17 +75,6 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
format!("{s1} Foo!").into()
});
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"hello***world"
);
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
"hello Foo!"
);
engine.set_fast_operators(true);
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"helloworld"
@ -95,5 +84,16 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
"hello"
);
engine.set_fast_operators(false);
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
"hello***world"
);
assert_eq!(
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
"hello Foo!"
);
Ok(())
}

View File

@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
engine.set_optimization_level(OptimizationLevel::Simple);
assert_eq!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
42
);
engine.set_fast_operators(false);
assert_eq!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
123

View File

@ -128,11 +128,14 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
assert_eq!(
engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?,
"6 kitties"
);
assert_eq!(engine.eval::<INT>("2 + 2")?, 4);
engine.set_fast_operators(false);
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
engine.register_static_module("test", exported_module!(test::special_array_package).into());