Skip to content
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

Rule session factory compilation can take a long time #191

Open
larryPlayabl opened this issue Jun 27, 2019 · 11 comments
Open

Rule session factory compilation can take a long time #191

larryPlayabl opened this issue Jun 27, 2019 · 11 comments

Comments

@larryPlayabl
Copy link

larryPlayabl commented Jun 27, 2019

We're using NRules in an interactive realtime application that needs to compile the rule factory on application start. We're starting to notice that our rule factory compilation is taking a long time. (10 seconds and growing so far on my PC, and about 40 seconds on the low power device we'll eventually be supporting.)

Specifically, this time is spent in RuleRepositoryExtensions.Compile() method.

Doing some initial profiling, the bulk of the time spent seems to be in RuleElementVisitor`1.Visit().

Drilling into this a bit, at first glance I see the bulk of the time spent in ExpressionComparer and LambdaCompiler methods.

A few screenshots of profiling are attached in case this is helpful.
Screen Shot 2019-06-26 at 10 12 48 PM
Screen Shot 2019-06-26 at 10 11 36 PM
Screen Shot 2019-06-26 at 10 10 28 PM
Screen Shot 2019-06-26 at 10 09 44 PM

@snikolayev
Copy link
Member

Some more details. The app is loading about 400 rules, with about half of rules using Or clauses, some with 10+ clauses within ORs. Since OR'd rules are effectively like adding additional rules, it's probably close to 750 rules equivalent.

snikolayev added a commit that referenced this issue Oct 14, 2019
Collapse multiple expression classes into a few generic ones; unify the code.
Compare expressions for node sharing prior to compilation, thereby improving compilation performance in those cases where there is node sharing.
Eliminate some unnecessary allocations during rule compilation (should help with #191 ).
@fkucuk
Copy link

fkucuk commented Nov 21, 2019

I am experiencing the same issue. It takes about five minutes to compile. I have 1200 rules.
But, I am using this library for mapping properties. I am aware I am not using it for its purpose.

@snikolayev
Copy link
Member

As an option, consider https://github.com/NRules/NRules/wiki/Expression-Compiler to hook up FastExpressionCompiler that will improve the compilation performance.

@dadhi
Copy link

dadhi commented Jun 3, 2022

@larryPlayabl @fkucuk Did someone actually tried FastExpressionCompiler and what was the performance?

@kk738
Copy link

kk738 commented Mar 11, 2023

I tried the FastExpressionCompiler and this indeed did speed up the process.
But I would have the general question if there is a possibility to avoid the compilation at all by just passing the plain lambda functions into rete with the RuleBuilder, not an expression. Can this be achieved, or is the framework just not built in a way supporting that.
I used a sequential naive algorithm before, which simply run two nested for each loops for all rules and facts evaluating when to fire. Now changing the rules to expressions and using NRules actually slowed down the evaluation. I think this comes down to the compile, because it slowed down the sequential evaluation by a factor of 4 as well, switching to Expressions.

@larryPlayabl
Copy link
Author

larryPlayabl commented Mar 13, 2023

Did someone actually tried FastExpressionCompiler and what was the performance?

@dadhi I did try it and it sped up compilation a bit.

@kk738 Isn't compilation a one-time hit though? So even if it's a bit slow, you can continue to use the compiled factory once you have it, and spinning up new sessions from the compiled factory is very quick. Is there a reason you're compiling more than once?

@dadhi
Copy link

dadhi commented Mar 13, 2023

@larryPlayabl What kind of expression are those?
If you will be able to extract a sample, I may check it with FEC and see how to optimize the specific case.

@kk738
Copy link

kk738 commented Mar 13, 2023

Did someone actually tried FastExpressionCompiler and what was the performance?

@dadhi I did try it and it sped up compilation a bit.

@kk738 Isn't compilation a one-time hit though? So even if it's a bit slow, you can continue to use the compiled factory once you have it, and spinning up new sessions from the compiled factory is very quick. Is there a reason you're compiling more than once?

Thanks for that hint, I simply didn't consider this. So I simply misused the lib, because of the way my application used to work before.
Nevertheless, wouldn't it be possible to compile these rules beforehand and then output the compiled rules somehow into a static Factory that could be used later on. So the compile could be done on a fast machine with idk t4 files or a SourceGenerator (or other even ways), and then the compiled rules could be used directly as a ISessionFactory?
But before doing that it would probably be easier to build a ISessionFactory containing compiled rules by hand, which might be achievable?
I am obviously not enough into the architecture of this library, that's why I believe I am asking rather naive question here. But I'm just curious.

@larryPlayabl
Copy link
Author

What kind of expression are those? If you will be able to extract a sample, I may check it with FEC and see how to optimize the specific case.

@dadhi Sorry, what do you mean by kind of expression. The C# class type of the expression, or are they categorized in some other way?

@dadhi
Copy link

dadhi commented Mar 16, 2023

@larryPlayabl I mean the example of expression in use.
It may be just a chain of constructor calls, or TryCatch with IfElse and Blocks. It may contain nested closures. It may be 30 levels deep or 300 children wide. Those kinds affect the compilation speed and memory spent in a specific way.

@snikolayev
Copy link
Member

snikolayev commented Mar 24, 2023

@dadhi I just realized that you are the owner of the FEC project and so that's why you were asking about the types of expressions others are trying to compile. TBH I personally haven't had a ton of issues with the compilation performance, even using the built-in .net compiler, but clearly, it's more painful for some folks.
@larryPlayabl @kk738 I think this is a chance for you to share the types of expressions (types of expression nodes used) that you see causing the biggest impact on compilation performance, which could help @dadhi improve FEC performance, making everyone benefit from it.
Also, if I'm not mistaken, FEC can fallback to .NET native expression compiler if it's unable to handle some expression, which is another reason why you may not be seeing as dramatic a benefit as you might have expected. So, finding these cases where the fallback occurs, and reporting those to FEC project will also potentially benefit everyone.

@kk738 it was already pointed out by @larryPlayabl that normally, you would expect compilation to only happen once, at the application startup, and then you just keep ISessionFactory around and don't really count rule compilation towards rules evaluation time.
I find your idea of precompiling expressions interesting (and it has been raised before), so I might explore it. I was intrigued by the introduction of source generators in . NET, so perhaps that's an option worth exploring.
Regarding your point on using delegates instead of expressions in NRules - not possible, as delegates are opaque, and the engine needs to rewrite and stitch the expressions together to make it work in cases where expressions depend on other expressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants