How-To #2 : Performances tips

Introduction

This article will provide some tips on how to optimize performances when using MRules. Depending on the version, some of them become unuseful, as the internal optimizer becomes more and more powerful. This will be precised each time.

Compilation mode

Compilation (coupled with optimization) is the first step of the life cycle of a rule set. MRules provides two modes for this : the STANDARD mode and the LIGHT mode.

In a general way, the STANDARD mode should be used everytime it’s possible, meaning when you know the structure of the input and output Java beans.

The LIGHT mode allows greater flexibility, but some optimizations can’t be performed if using it. For example, in STANDARD mode, constant values are cast only once at compilation type, in order to correspond to target type. In LIGHT mode, they are cast at runtime, at each execution.

Choose the right rule implementation

To perform actions based on the value of input data, the most common way is to perform an evaluation. For example:

IF (input.value == "A") THEN ACTION "A"
IF (input.value == "B") THEN ACTION "B"
IF (input.value == "C") THEN ACTION "C"
IF (input.value == "D") THEN ACTION "D"

This can lead to complex  rules and to more computations than necessary.

To avoid this, the INDEX rule is very adapted to this type of cases:

INDEX(input.value)
WHEN "A" THEN ACTION "A"

  WHEN "B" THEN ACTION "B"
  WHEN "C" THEN ACTION "C"
  WHEN "D" THEN ACTION "D"

The “Transportation fees computation” demo shows a use case written using the two techniques.

Condition writing

The EVAL condition (used to perform evaluation, for example “value == otherValue”) is generally written using three inputs:

  • source: the first value to be compared
  • operator: the evaluation to perform (equality, greater than, lesser than, …)
  • reference: the second value to be compared

The name of the input values is choosen accordingly to how they are treated: if types are different, the reference is cast to correspond to the source type, so that the evaluation can be made.

So, if one of the values is a constant, it should be the “reference”, so that it will be cast at compilation time. If not, the cast can not be made and the variable value will be cast at each execution to correspond to the constant type.

For example:

IF ("125" IN input.collectionOfInteger) THEN ACTION "A"

In this case, all integer values in the collection will be cast to Strings, and then the engine will check if the String “125” is present in the temporary collection of Strings.

If written:

IF (input.collectionOfInteger CONTAINS "125") THEN ACTION "A"

Then the String “125” will be cast as Integer at compilation time and no unuseful operation performed at execution.

Starting from version 1.8.0, the optimizer detects this situation and automatically corrects it.

Use variables when necessary

If some complex operation are performed several times, it might be useful to store the result in a variable to avoid double (or more) computations.

For example:

IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "A") THEN ACTION "A"
IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "B") THEN ACTION "B"

In this case, the SPLIT might be performed twice. Better write:

$tempArray = SPLIT("A;B;C;D")
IF (input.value1 IN $tempArray AND input.value2 == "A") THEN ACTION "A
IF (input.value1 IN $tempArray AND input.value2 == "B") THEN ACTION "B"

Note:

  • Versions anterior to 1.7.0 will try to re-order conditions to evaluate first the simplest ones
  • Version 1.7.0 (and more) will transform the result of the SPLIT operation into a constant value in the previous case (as the splitted value is a constant).
  • Version 1.8.0 will be able to cache the result of the SPLIt operation (if not performed on a constant value) and reuse it.

Performance extension

MRules is based on Java reflection API to access input and output data. This API becomes faster and faster at each version of the JRE, but it stays slower than the compiled code.

If you are running  MRules using a JRE 7 or greater, the extension allowing to generate bytecode when possible instead of using reflexion might be a good choice.

For public fields and methods of the input / output beans, no reflexion will be used. Global performances at execution will be greatly improved (up to 60 % faster). The generated bytecode is cached and generated only once for a given method, even if the rule engine is recompiled. It is also shared between different rule engines if the Java beans are the same.

Upgrade

New versions provide more and more features and optimizations, allowing rules writers to write the rules in a more intuitive way and to not being aware of all subtleties of performance improvements.

The version 1.8.0 is a major version concerning performances, at all levels. The cache usage has been improved and is more accurate. The internal optimizer is more powerful. Some parts of the library code have been reworked to be always faster.

So don’t hesitate and always upgrade to the latest version !