-
Notifications
You must be signed in to change notification settings - Fork 216
Optimize Script and Method Calls #1027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Thanks for the contribution! There seem to be conflicts. Can you resolve them? |
|
Also, I see that you have replaced Do you have benchmarks to show clear improvements in runtime? Also can you elaborate a bit on what you intend to do with |
|
Need to set aside some time to circle back to this, but yes I can resolve them as well as clean up the PR a bit when I do some performance comparisons. In the experience I had when optimizing it, I found that replacing things such as For |
Then I would strongly suggest you try with a full LTO build. Sometimes Rust would not inline across crate boundaries and LTO forces it to do so, resulting in drastically reduced code. I never have release builds without LTO these days.
I have heard similar but it seems the Rust standard hashmap is the hashbrown one for std builds. And Rhai already uses a fast hashing implementation so I won't expect performance to differ. I would appreciate some benchmarks. |
|
Finally making it back to this @schungx
This doesn't seem to improve performance (mostly just file size), and requires 12+ minutes of compilation (I'd like to be able to play my game as I'm developing it, it's currently 40 seconds). I've done a bit more testing with some changes, I'll need to make a new PR since I restarted, but it doesn't seem hashbrown itself was what gave me performance improvements. I have been wondering how to cut the stack depth down, as Rhai sends spikes up my profiler. My current idea I am looking into is trying to make the statement walking iterative instead of recursive, as well as converting things like .map(||), .map_or_else(||, ||), .find(||), .or_else() ect., as these seem to eat as that as well with some performance overhead (some of this may just be without LTO, but changing them to simpler primitives doesn't seem to harm it either). |
I don't think you can. That's a limitation of Rhai's engine architecture, which is a recursive AST walker. Each level of code pushes a few new stack frames during evaluation. If stack is what you're worried about then the only way is to move to an engine that compiles the AST down to byte codes. And the stack issue really has nothing to do with using closures in things like If you're looking into performance, I'd suggest you look into turning off features. For example, did you use These would yield you more performance than counting stack levels, which really has to do with memory limitation instead of run speed. Also, in your release builds, a lot of In fact Rust Clippy would always advise you to use iterators and mapping functions instead of if statements and loops due to better optimization. So your results are quite strange from where I see it... |
The main idea I'd have for this is effectively pushing Stmts (maybe a StmtBlock?) to a queue that a higher level can loop back over, it'd not really hold much, maybe even a &mut Option would do, just so it wouldn't tunnel down the stack.
For things like map_or_else, I mainly found I got performance gains as these still execute things like closures, which are more expensive than simpler things like if or match, so the cost does add up, and still has a cost when not using a super optimized build I can't use during development. Replacing a try_fold for instance improved performance.
I run Rhai already with no default features, internals, no_custom_syntax, only_i64, std, and unchecked, hence why I'm on the search to try and improve the underlying systems.
Clippy makes no such advisory for the changes I've made, I've found these functions have consistently added overhead, even in full LTO mode, and have applied these kind of changes to my own project with success. If you used non closure variants this may be zero cost, but the closures definitely have a cost. You can view the initial changes I did here: djgaven588@852a75e |
I tried something similar but it ran afoul of Rust's borrow checker. There are certain things that need to be The only way you can have multiple mutable references to a single piece of data in Rust is to keep them in separate function call frames. That's why it is difficult to get rid of recursive function calls without going |
This is exactly the point I'm getting at. The closures should no longer exist after optimization. If I replace all these with simple constructs, I'm afraid Clippy will give me a ton of complaints about not using them. |
This is very strange indeed. I'm quite sure I checked it in Compiler Explorer before and Rust optimizes away the closures. Let me check again. EDIT: I just checked with Compiler Explorer and all those closures optimize away, leaving no trace whatsoever. |



A bit ago I spent some time optimizing Rhai for my project.
These changes helped out a lot for my use case, and are mostly focused on calling lots of scripts, and those scripts calling lots of methods.
Most of these changes involve reducing function calls (map_or_else for instance), using hashbrown always, or preventing excessive iteration (drains).