diff --git a/benches/eval_module.rs b/benches/eval_module.rs new file mode 100644 index 00000000..98843eeb --- /dev/null +++ b/benches/eval_module.rs @@ -0,0 +1,87 @@ +#![feature(test)] + +///! Test evaluating with scope +extern crate test; + +use rhai::{module_resolvers::StaticModuleResolver, Engine, Module, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_module_merged(bench: &mut Bencher) { + let script = r#" + fn foo(x) { x + 1 } + fn bar(x) { foo(x) } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + let module = Module::eval_ast_as_new(Default::default(), &ast, true, &engine).unwrap(); + + let mut resolver = StaticModuleResolver::new(); + resolver.insert("testing", module); + engine.set_module_resolver(Some(resolver)); + + let ast = engine + .compile( + r#" + fn foo(x) { x - 1 } + import "testing" as t; + t::bar(41) + "#, + ) + .unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_module_unmerged(bench: &mut Bencher) { + let script = r#" + fn foo(x) { x + 1 } + fn bar(x) { foo(x) } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + let module = Module::eval_ast_as_new(Default::default(), &ast, false, &engine).unwrap(); + + let mut resolver = StaticModuleResolver::new(); + resolver.insert("testing", module); + engine.set_module_resolver(Some(resolver)); + + let ast = engine + .compile( + r#" + fn foo(x) { x - 1 } + import "testing" as t; + t::bar(41) + "#, + ) + .unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_function_call(bench: &mut Bencher) { + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine + .compile( + r#" + fn foo(x) { x - 1 } + fn bar(x) { foo(x) } + bar(41) + "#, + ) + .unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/doc/src/rust/modules/ast.md b/doc/src/rust/modules/ast.md index 0348579a..fed0cb09 100644 --- a/doc/src/rust/modules/ast.md +++ b/doc/src/rust/modules/ast.md @@ -27,9 +27,13 @@ functions exposed by the module and the namespace that they can access: | `merge_namespaces` value | Description | Namespace | Performance | Call global functions | Call functions in same module | | :----------------------: | ------------------------------------------------ | :-----------------: | :---------: | :-------------------: | :---------------------------: | -| `true` | encapsulate entire `AST` into each function call | module, then global | slower | yes | yes | +| `true` | encapsulate entire `AST` into each function call | module, then global | 2x slower | yes | yes | | `false` | register each function independently | global only | fast | yes | no | +If the ultimate intention is to load the [module] directly into an [`Engine`] via `Engine::load_package`, +set `merge_namespaces` to `false` because there will not be any _module_ namespace as `Engine::load_package` +flattens everything into the _global_ namespace anyway. + Examples -------- @@ -78,7 +82,9 @@ let ast = engine.compile(r#" // a copy of the entire 'AST' into each function, allowing functions in the module script // to cross-call each other. // -// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false. +// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false +// which makes function calls 2x faster but at the expense of not being able to cross-call +// functions in the same module script. let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?; // 'module' now contains: diff --git a/src/fn_register.rs b/src/fn_register.rs index 5c519d4c..3df06d5e 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -178,7 +178,7 @@ macro_rules! def_register { (imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { // ^ function ABI type // ^ function parameter generic type name (A, B, C etc.) -// ^ call argument(like A, *B, &mut C etc) + // ^ call argument(like A, *B, &mut C etc) // ^ function parameter marker type (T, Ref or Mut) // ^ function parameter actual type (T, &T or &mut T) // ^ argument let statement diff --git a/src/module/mod.rs b/src/module/mod.rs index 83a5ca24..3981ead4 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1260,8 +1260,12 @@ impl Module { /// defined in the module, are _merged_ into a _unified_ namespace before each call. /// Therefore, all functions will be found, at the expense of some performance. /// - /// * If `false`, each function is registered independently and cannot cross-call - /// each other. Functions are searched in the global namespace. + /// * If `false`, each function is registered independently and cannot cross-call each other. + /// Functions are searched in the global namespace. + /// This is roughly 2x faster than the `true` case. + /// + /// Set `merge_namespaces` to `false` if the ultimate intention is to load the module + /// via `Engine::load_package` because it does not create any module namespace. /// /// # Examples /// diff --git a/tests/modules.rs b/tests/modules.rs index e1a8bae6..f14a8276 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -361,3 +361,40 @@ fn test_module_str() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_ast_namespace() -> Result<(), Box> { + let script = r#" + fn foo(x) { x + 1 } + fn bar(x) { foo(x) } + "#; + + let mut engine = Engine::new(); + + let ast = engine.compile(script)?; + + let module = Module::eval_ast_as_new(Default::default(), &ast, true, &engine)?; + + let mut resolver = StaticModuleResolver::new(); + resolver.insert("testing", module); + engine.set_module_resolver(Some(resolver)); + + assert_eq!( + engine.eval::(r#"import "testing" as t; t::foo(41)"#)?, + 42 + ); + assert_eq!( + engine.eval::(r#"import "testing" as t; t::bar(41)"#)?, + 42 + ); + assert_eq!( + engine.eval::(r#"fn foo(x) { x - 1 } import "testing" as t; t::foo(41)"#)?, + 42 + ); + assert_eq!( + engine.eval::(r#"fn foo(x) { x - 1 } import "testing" as t; t::bar(41)"#)?, + 42 + ); + + Ok(()) +}