From 702bb9030a8fb7a3605e70a458ad248fde6ec3bf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 3 Sep 2022 22:07:36 +0800 Subject: [PATCH] Make fast operators the default. --- CHANGELOG.md | 7 ++++++- benches/eval_scope.rs | 17 ----------------- src/api/options.rs | 25 ++++++++++++++++--------- src/ast/expr.rs | 10 +++++----- src/bin/rhai-run.rs | 2 -- src/eval/expr.rs | 21 ++++++++++++++++----- src/func/call.rs | 4 ++-- src/parser.rs | 9 ++++++--- tests/native.rs | 22 +++++++++++----------- tests/optimizer.rs | 7 +++++++ tests/plugins.rs | 5 ++++- 11 files changed, 73 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c0ab0e..70ee4fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/benches/eval_scope.rs b/benches/eval_scope.rs index 9bd9beb4..2237fa8a 100644 --- a/benches/eval_scope.rs +++ b/benches/eval_scope.rs @@ -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#" diff --git a/src/api/options.rs b/src/api/options.rs index 27be8635..d5596cfd 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -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() - } - } } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index b648d7c4..ab13c5eb 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -190,8 +190,8 @@ pub struct FnCallExpr { pub args: StaticVec, /// 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(), diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index e44a3b7f..e87e99b4 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -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); diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 23454172..56bb83b2 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -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, ) } diff --git a/src/func/call.rs b/src/func/call.rs index 5bd4616c..93e808ed 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -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 => { diff --git a/src/parser.rs b/src/parser.rs index 4142cd68..23da89d0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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() }; diff --git a/tests/native.rs b/tests/native.rs index 7081fe5f..71b833c4 100644 --- a/tests/native.rs +++ b/tests/native.rs @@ -75,17 +75,6 @@ fn test_native_overload() -> Result<(), Box> { format!("{s1} Foo!").into() }); - assert_eq!( - engine.eval::(r#"let x = "hello"; let y = "world"; x + y"#)?, - "hello***world" - ); - assert_eq!( - engine.eval::(r#"let x = "hello"; let y = (); x + y"#)?, - "hello Foo!" - ); - - engine.set_fast_operators(true); - assert_eq!( engine.eval::(r#"let x = "hello"; let y = "world"; x + y"#)?, "helloworld" @@ -95,5 +84,16 @@ fn test_native_overload() -> Result<(), Box> { "hello" ); + engine.set_fast_operators(false); + + assert_eq!( + engine.eval::(r#"let x = "hello"; let y = "world"; x + y"#)?, + "hello***world" + ); + assert_eq!( + engine.eval::(r#"let x = "hello"; let y = (); x + y"#)?, + "hello Foo!" + ); + Ok(()) } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index d231540b..248a15f0 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box> { engine.set_optimization_level(OptimizationLevel::Simple); + assert_eq!( + engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, + 42 + ); + + engine.set_fast_operators(false); + assert_eq!( engine.eval::("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?, 123 diff --git a/tests/plugins.rs b/tests/plugins.rs index be1cf49b..d5072eb9 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -128,11 +128,14 @@ fn test_plugins_package() -> Result<(), Box> { assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::("let a = [1, 2, 3]; hi(a, 2)")?, 6); assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); - assert_eq!(engine.eval::("2 + 2")?, 5); assert_eq!( engine.eval::("let a = [1, 2, 3]; greet(test(a, 2))")?, "6 kitties" ); + assert_eq!(engine.eval::("2 + 2")?, 4); + + engine.set_fast_operators(false); + assert_eq!(engine.eval::("2 + 2")?, 5); engine.register_static_module("test", exported_module!(test::special_array_package).into());