NRules icon indicating copy to clipboard operation
NRules copied to clipboard

Rule session factory compilation can take a long time

Open larryPlayabl opened this issue 6 years ago • 11 comments

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

larryPlayabl avatar Jun 27 '19 05:06 larryPlayabl

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 avatar Jun 28 '19 02:06 snikolayev

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.

fkucuk avatar Nov 21 '19 05:11 fkucuk

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

snikolayev avatar Jun 29 '21 01:06 snikolayev

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

dadhi avatar Jun 03 '22 10:06 dadhi

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.

kk738 avatar Mar 11 '23 20:03 kk738

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?

larryPlayabl avatar Mar 13 '23 21:03 larryPlayabl

@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.

dadhi avatar Mar 13 '23 21:03 dadhi

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.

kk738 avatar Mar 13 '23 21:03 kk738

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?

larryPlayabl avatar Mar 16 '23 20:03 larryPlayabl

@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.

dadhi avatar Mar 16 '23 20:03 dadhi

@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.

snikolayev avatar Mar 24 '23 00:03 snikolayev