@snikolayev I'm curious to experiment a bit with some resource pooling to try to reduce allocations in NRules. I'd love to hear your thoughts on it when you have a moment.
One low-hanging-fruit spot I'd like to try is Tuple reuse. It's very clear where Tuples are constructed, and they could instead come from a pool of Tuples which can be reused. Any ideas to help me track down the point/points in the code where can a Tuple be safely returned to the pool for reuse?
@snikolayev Thanks for the pointers. Yes, it makes sense to start with the easiest options that offer the greatest optimization for amount of effort and risk. I'll openly admit that I'm not especially well versed in this kind of optimization, and would definitely want to keep you in the loop as I experiment to make sure I'm going in the right direction. I know there can be subtle details in the implementation that may make all the difference in how much allocation savings we're actually reaping.
The object arrays created in the LHSExpression Invoke methods look like they'd be pretty straightforward to pool. Do you believe that swapping these arrays out for List<object> that are cleared and repopulated each time would offer an allocation advantage?
If that won't help significantly, I could also imagine caching a Dictionary<ITuple, object> to be used by these LHSExpression methods. I believe this would work because the object will always be the same size for the Tuple. Obviously, this cache would need to be updated whenever a given tuple's facts change. That could either be done by iterating through them and updating them on each call to Invoke, or by checking for a "dirty" flag on the tuple which would indicate that the facts have changed since the last invoke, though that'd be more complex.
For the Tuple.Facts IEnumerable, I could give each Tuple a List<Fact> that's cleared and repopulated by walking the tuples on each call to get the IEnumerable. However, again, maybe there'd be a more efficient way to do that and only update the Facts list as needed. Think that'd be possible?
IDependencyResolver(see https://github.com/NRules/NRules/wiki/Rule-Dependencies). Another approach is to inject dependencies into rules classes at rules instantiation time. In this case you need to implement
IRuleActivator(see https://github.com/NRules/NRules/wiki/Fluent-Rules-Loading#Rule-Activation). When you implement one or both of these interfaces, you can simply just forward resolution requests to your existing service container.
@snikolayev Is it possible to consume scoped services in MyRule (: Rule)? Currently it only work with Singleton service ( services.AddSingleton<IServiceTest, ServiceTest>(); ).
When I tried scoped services ( services.AddScoped<IServiceTest, ServiceTest>(); ) it threw an error as shown in the image above.
IDependencyResolver. When implementing the resolver, forward the resolution requests to the scoped service provider, instead of the root service provider. This also means you would have to create a new session per scope, create a dependency resolver for that scope and set it on that session.
RuleRepository, as that's only used with the rules defined using C# DSL. When using
RuleBuilder, you are getting back
IRuleDefinitions, and you can feed them directly to RuleCompiler.Compile method.
I also happen to have a question, I'm working on a project that requires going through rules and at some point having several choices (2-30 but possibly one choice leads to more choices down the line). I would like to be able to let the engine start from the current state and "trying" multiple solutions. I'm trying to figure out a way a simple and hopefully efficient way to do this. Here's what I experimented with/considered so far:
1) Considered using LinkedFacts, and while that handles new facts retraction, updates to existing facts wouldn't be reverted. So make a log of updates and revert manually?
2) Deep-copy all facts, make choice, continue execution. Once run is over, retract all, insert all fact copies, make new choice, repeat.
3) Deep-copy all facts, halt current session, create new session for each choice, starting with the fact copies.
I have the impression I'm missing a clever way to use the framework to help me. Additionally, doing this in a way where I can leverage already calculated fact combinations would be great as there are combinations that would be repeated.
The reason I'm trying to use NRules for this is that new and updated facts tend to trigger a limited number of rules and fact changes. For reference the "scale" I'm working with is along the lines of ~50-200 facts and probably ~50-200 rules.