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 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. The minimum Rust version is now `1.61.0` in order to use some `const` generics.
Bug fixes Bug fixes
@ -23,7 +28,7 @@ New features
### Fast operators ### 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 ### 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.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] #[bench]
fn bench_eval_scope_complex(bench: &mut Bencher) { fn bench_eval_scope_complex(bench: &mut Bencher) {
let script = r#" let script = r#"

View File

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

View File

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

View File

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

View File

@ -227,14 +227,14 @@ impl Engine {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
namespace, namespace,
capture_parent_scope: capture, capture_parent_scope: capture,
is_standard_operator: std_ops, is_native_operator: native_ops,
hashes, hashes,
args, args,
.. ..
} = expr; } = expr;
// Short-circuit operator call if under Fast Operators mode // Short-circuit native binary operator call if under Fast Operators mode
if *std_ops && self.fast_operators() { if *native_ops && self.fast_operators() && args.len() == 2 {
let mut lhs = self let mut lhs = self
.get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)? .get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)?
.0 .0
@ -314,8 +314,19 @@ impl Engine {
); );
self.make_function_call( self.make_function_call(
scope, global, caches, lib, this_ptr, name, first_arg, args, *hashes, *capture, scope,
*std_ops, pos, level, 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], args_expr: &[Expr],
hashes: FnCallHashes, hashes: FnCallHashes,
capture_scope: bool, capture_scope: bool,
is_standard_operator: bool, is_operator: bool,
pos: Position, pos: Position,
level: usize, level: usize,
) -> RhaiResult { ) -> RhaiResult {
@ -1002,7 +1002,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call let redirected; // Handle call() - Redirect function call
match name { match name {
_ if is_standard_operator => (), _ if is_operator => (),
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {

View File

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

View File

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

View File

@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
engine.set_optimization_level(OptimizationLevel::Simple); 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!( assert_eq!(
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
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]; 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]; hi(a, 2)")?, 6);
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(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!( assert_eq!(
engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?, engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?,
"6 kitties" "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()); engine.register_static_module("test", exported_module!(test::special_array_package).into());