Speed up unary operators.
This commit is contained in:
parent
62f426d477
commit
c7da3c6edb
18
CHANGELOG.md
18
CHANGELOG.md
@ -4,10 +4,18 @@ 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
|
This version introduces _Fast Operators_ mode, which is turned on by default but can be disabled via
|
||||||
standard data types are never overloaded – in the vast majority of cases this should be so.
|
a new options API: `Engine::set_fast_operators`.
|
||||||
Avoid checking for overloads may result in substantial speed improvements especially for
|
|
||||||
operator-heavy scripts.
|
_Fast Operators_ mode assumes that none of Rhai's built-in operators for standard data types are
|
||||||
|
overloaded by user-registered functions. In the vast majority of cases this should be so (really,
|
||||||
|
who overloads the `+` operator for integers anyway?).
|
||||||
|
|
||||||
|
This assumption allows the `Engine` to avoid checking for overloads for every single operator call.
|
||||||
|
This usually results in substantial speed improvements, especially for expressions.
|
||||||
|
|
||||||
|
Minimum Rust Version
|
||||||
|
--------------------
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@ -28,7 +36,7 @@ New features
|
|||||||
|
|
||||||
### Fast operators
|
### Fast operators
|
||||||
|
|
||||||
* 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.
|
* A new option `Engine::fast_operators` is introduced (default to `true`) to enable/disable _Fast Operators_ mode.
|
||||||
|
|
||||||
### Fallible type iterators
|
### Fallible type iterators
|
||||||
|
|
||||||
|
124
src/eval/expr.rs
124
src/eval/expr.rs
@ -5,14 +5,14 @@ use crate::ast::{Expr, FnCallExpr, OpAssignment};
|
|||||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
||||||
use crate::eval::FnResolutionCacheEntry;
|
use crate::eval::FnResolutionCacheEntry;
|
||||||
use crate::func::{
|
use crate::func::{
|
||||||
calc_fn_params_hash, combine_hashes, get_builtin_binary_op_fn, CallableFunction, FnAny,
|
calc_fn_params_hash, combine_hashes, gen_fn_call_signature, get_builtin_binary_op_fn,
|
||||||
|
CallableFunction, FnAny,
|
||||||
};
|
};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
||||||
use std::collections::btree_map::Entry;
|
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
use std::{collections::btree_map::Entry, num::NonZeroUsize};
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Search for a module within an imports stack.
|
/// Search for a module within an imports stack.
|
||||||
@ -223,84 +223,78 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let FnCallExpr {
|
let FnCallExpr {
|
||||||
name,
|
name, hashes, args, ..
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
namespace,
|
|
||||||
capture_parent_scope: capture,
|
|
||||||
is_native_operator: native_ops,
|
|
||||||
hashes,
|
|
||||||
args,
|
|
||||||
..
|
|
||||||
} = expr;
|
} = expr;
|
||||||
|
|
||||||
// Short-circuit native binary operator call if under Fast Operators mode
|
// Short-circuit native binary operator call if under Fast Operators mode
|
||||||
if *native_ops && self.fast_operators() && args.len() == 2 {
|
if expr.is_native_operator && self.fast_operators() && (args.len() == 1 || 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
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
let mut rhs = self
|
let mut rhs = if args.len() == 2 {
|
||||||
.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)?
|
self.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)?
|
||||||
.0
|
.0
|
||||||
.flatten();
|
.flatten()
|
||||||
|
|
||||||
let args = &mut [&mut lhs, &mut rhs];
|
|
||||||
|
|
||||||
let hash = combine_hashes(
|
|
||||||
hashes.native,
|
|
||||||
calc_fn_params_hash(args.iter().map(|a| a.type_id())),
|
|
||||||
);
|
|
||||||
|
|
||||||
let c = caches.fn_resolution_cache_mut();
|
|
||||||
|
|
||||||
let entry = if let Entry::Vacant(e) = c.entry(hash) {
|
|
||||||
match get_builtin_binary_op_fn(&name, args[0], args[1]) {
|
|
||||||
Some(f) => {
|
|
||||||
let entry = FnResolutionCacheEntry {
|
|
||||||
func: CallableFunction::from_method(Box::new(f) as Box<FnAny>),
|
|
||||||
source: None,
|
|
||||||
};
|
|
||||||
e.insert(Some(entry));
|
|
||||||
c.get(&hash).unwrap().as_ref().unwrap()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return self
|
|
||||||
.exec_fn_call(
|
|
||||||
None, global, caches, lib, name, *hashes, args, false, false, pos,
|
|
||||||
level,
|
|
||||||
)
|
|
||||||
.map(|(v, ..)| v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
match c.get(&hash).unwrap() {
|
Dynamic::UNIT
|
||||||
Some(entry) => entry,
|
};
|
||||||
None => {
|
|
||||||
return Err(ERR::ErrorFunctionNotFound(
|
let mut operands = [&mut lhs, &mut rhs];
|
||||||
self.gen_fn_call_signature(
|
let operands = if args.len() == 2 {
|
||||||
#[cfg(not(feature = "no_module"))]
|
&mut operands[..]
|
||||||
&crate::ast::Namespace::NONE,
|
} else {
|
||||||
name,
|
&mut operands[0..1]
|
||||||
args,
|
};
|
||||||
),
|
|
||||||
pos,
|
let hash = calc_fn_params_hash(operands.iter().map(|a| a.type_id()));
|
||||||
)
|
let hash = combine_hashes(hashes.native, hash);
|
||||||
.into())
|
|
||||||
}
|
let cache = caches.fn_resolution_cache_mut();
|
||||||
}
|
|
||||||
|
let func = if let Entry::Vacant(entry) = cache.entry(hash) {
|
||||||
|
let func = if args.len() == 2 {
|
||||||
|
get_builtin_binary_op_fn(&name, operands[0], operands[1])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(f) = func {
|
||||||
|
entry.insert(Some(FnResolutionCacheEntry {
|
||||||
|
func: CallableFunction::from_method(Box::new(f) as Box<FnAny>),
|
||||||
|
source: None,
|
||||||
|
}));
|
||||||
|
&cache.get(&hash).unwrap().as_ref().unwrap().func
|
||||||
|
} else {
|
||||||
|
let result = self.exec_fn_call(
|
||||||
|
None, global, caches, lib, name, *hashes, operands, false, false, pos,
|
||||||
|
level,
|
||||||
|
);
|
||||||
|
return result.map(|(v, ..)| v);
|
||||||
|
}
|
||||||
|
} else if let Some(entry) = cache.get(&hash).unwrap() {
|
||||||
|
&entry.func
|
||||||
|
} else {
|
||||||
|
let sig = gen_fn_call_signature(self, name, operands);
|
||||||
|
return Err(ERR::ErrorFunctionNotFound(sig, pos).into());
|
||||||
};
|
};
|
||||||
|
|
||||||
let func = entry.func.get_native_fn().unwrap();
|
|
||||||
let context = (self, name, None, &*global, lib, pos, level).into();
|
let context = (self, name, None, &*global, lib, pos, level).into();
|
||||||
let result = (func)(context, args);
|
let result = if func.is_plugin_fn() {
|
||||||
|
func.get_plugin_fn().unwrap().call(context, operands)
|
||||||
|
} else {
|
||||||
|
func.get_native_fn().unwrap()(context, operands)
|
||||||
|
};
|
||||||
return self.check_return_value(result, pos);
|
return self.check_return_value(result, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if !namespace.is_empty() {
|
if !expr.namespace.is_empty() {
|
||||||
// Qualified function call
|
// Qualified function call
|
||||||
let hash = hashes.native;
|
let hash = hashes.native;
|
||||||
|
let namespace = &expr.namespace;
|
||||||
|
|
||||||
return self.make_qualified_function_call(
|
return self.make_qualified_function_call(
|
||||||
scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level,
|
scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level,
|
||||||
@ -323,8 +317,8 @@ impl Engine {
|
|||||||
first_arg,
|
first_arg,
|
||||||
args,
|
args,
|
||||||
*hashes,
|
*hashes,
|
||||||
*capture,
|
expr.capture_parent_scope,
|
||||||
*native_ops,
|
expr.is_native_operator,
|
||||||
pos,
|
pos,
|
||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
|
@ -127,41 +127,48 @@ pub fn ensure_no_data_race(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
/// Generate the signature for a function call.
|
||||||
/// Generate the signature for a function call.
|
#[inline]
|
||||||
#[inline]
|
#[must_use]
|
||||||
#[must_use]
|
pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String {
|
||||||
pub(crate) fn gen_fn_call_signature(
|
format!(
|
||||||
&self,
|
"{fn_name} ({})",
|
||||||
#[cfg(not(feature = "no_module"))] namespace: &crate::ast::Namespace,
|
args.iter()
|
||||||
fn_name: &str,
|
.map(|a| if a.is::<ImmutableString>() {
|
||||||
args: &[&mut Dynamic],
|
"&str | ImmutableString | String"
|
||||||
) -> String {
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
let (ns, sep) = (
|
|
||||||
namespace.to_string(),
|
|
||||||
if namespace.is_empty() {
|
|
||||||
""
|
|
||||||
} else {
|
} else {
|
||||||
crate::tokenizer::Token::DoubleColon.literal_syntax()
|
engine.map_type_name(a.type_name())
|
||||||
},
|
})
|
||||||
);
|
.collect::<FnArgsVec<_>>()
|
||||||
#[cfg(feature = "no_module")]
|
.join(", ")
|
||||||
let (ns, sep) = ("", "");
|
)
|
||||||
|
}
|
||||||
|
|
||||||
format!(
|
/// Generate the signature for a namespace-qualified function call.
|
||||||
"{ns}{sep}{fn_name} ({})",
|
///
|
||||||
args.iter()
|
/// Not available under `no_module`.
|
||||||
.map(|a| if a.is::<ImmutableString>() {
|
#[cfg(not(feature = "no_module"))]
|
||||||
"&str | ImmutableString | String"
|
#[inline]
|
||||||
} else {
|
#[must_use]
|
||||||
self.map_type_name(a.type_name())
|
pub fn gen_qualified_fn_call_signature(
|
||||||
})
|
engine: &Engine,
|
||||||
.collect::<FnArgsVec<_>>()
|
namespace: &crate::ast::Namespace,
|
||||||
.join(", ")
|
fn_name: &str,
|
||||||
)
|
args: &[&mut Dynamic],
|
||||||
}
|
) -> String {
|
||||||
|
let (ns, sep) = (
|
||||||
|
namespace.to_string(),
|
||||||
|
if namespace.is_empty() {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
crate::tokenizer::Token::DoubleColon.literal_syntax()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
format!("{ns}{sep}{}", gen_fn_call_signature(engine, fn_name, args))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine {
|
||||||
/// Resolve a normal (non-qualified) function call.
|
/// Resolve a normal (non-qualified) function call.
|
||||||
///
|
///
|
||||||
/// Search order:
|
/// Search order:
|
||||||
@ -405,11 +412,9 @@ impl Engine {
|
|||||||
let context = (self, name, source, &*global, lib, pos, level).into();
|
let context = (self, name, source, &*global, lib, pos, level).into();
|
||||||
|
|
||||||
let result = if func.is_plugin_fn() {
|
let result = if func.is_plugin_fn() {
|
||||||
func.get_plugin_fn()
|
func.get_plugin_fn().unwrap().call(context, args)
|
||||||
.expect("plugin function")
|
|
||||||
.call(context, args)
|
|
||||||
} else {
|
} else {
|
||||||
func.get_native_fn().expect("native function")(context, args)
|
func.get_native_fn().unwrap()(context, args)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
@ -541,16 +546,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Raise error
|
// Raise error
|
||||||
_ => Err(ERR::ErrorFunctionNotFound(
|
_ => {
|
||||||
self.gen_fn_call_signature(
|
Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature(self, name, args), pos).into())
|
||||||
#[cfg(not(feature = "no_module"))]
|
}
|
||||||
&crate::ast::Namespace::NONE,
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
.into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1432,7 +1430,7 @@ impl Engine {
|
|||||||
Some(f) => unreachable!("unknown function type: {:?}", f),
|
Some(f) => unreachable!("unknown function type: {:?}", f),
|
||||||
|
|
||||||
None => Err(ERR::ErrorFunctionNotFound(
|
None => Err(ERR::ErrorFunctionNotFound(
|
||||||
self.gen_fn_call_signature(namespace, fn_name, &args),
|
gen_qualified_fn_call_signature(self, namespace, fn_name, &args),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
.into()),
|
.into()),
|
||||||
|
@ -13,7 +13,9 @@ pub mod script;
|
|||||||
|
|
||||||
pub use args::FuncArgs;
|
pub use args::FuncArgs;
|
||||||
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
||||||
pub use call::FnCallArgs;
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
pub use call::gen_qualified_fn_call_signature;
|
||||||
|
pub use call::{gen_fn_call_signature, FnCallArgs};
|
||||||
pub use callable_function::CallableFunction;
|
pub use callable_function::CallableFunction;
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use func::Func;
|
pub use func::Func;
|
||||||
|
Loading…
Reference in New Issue
Block a user