PHREAK Unlinking

Environment: Drools 6.5.0.Final

When testing drools performance comparing PHREAK vs ReteOO, I found that PHREAK Unlinking plays an important part.

First of all, it’s already explained in drools docs.

https://docs.jboss.org/drools/release/6.5.0.Final/drools-docs/html/ch05.html#PHREAK

But… I really can’t understand how things actually work. I need to test with actual examples.

https://github.com/tkobayas/phreak-unlinking

Firstly, I test this DRL:

https://github.com/tkobayas/phreak-unlinking/blob/master/src/main/resources/accumulate-all-link.drl

rule "Rule1"
when
$f1 : MyFact1()
$f2 : MyFact2(id == $f1.id, value1 == $f1.value1, value2 == $f1.value2, value3 == $f1.value3, value4 == $f1.value4, value5 == $f1.value5)
$f3 : MyFact3(id == $f2.id, value1 == $f2.value1, value2 == $f2.value2, value3 == $f2.value3, value4 == $f2.value4, value5 == $f2.value5)
$f4 : MyFact4(id == $f3.id, value1 == $f3.value1, value2 == $f3.value2, value3 == $f3.value3, value4 == $f3.value4, value5 == $f3.value5)

$var0 : Long(this != 0) from accumulate($a0 : MyFact5(id == $f4.id, value1 == $f4.value1), count( $a0 ))

then
end

...

with this client code. This inserts facts and only “Rule1” will be fired.

https://github.com/tkobayas/phreak-unlinking/blob/master/src/main/java/com/github/tkobayas/phreak/unlinking/SimpleMain1.java

ReteDumper dumps Rete nodes so that we can understand how Nodes are connected.

Also make sure to enable TRACE logging for SegmentMemory and PathMemory.

https://github.com/tkobayas/phreak-unlinking/blob/master/src/main/resources/logback.xml#L11-L12

When I run SimpleMain1, the output is like this (omitting rete dump):

[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=0 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5], [RuleMem Rule6], [RuleMem Rule7], [RuleMem Rule8], [RuleMem Rule9]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=0 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5], [RuleMem Rule6], [RuleMem Rule7], [RuleMem Rule8], [RuleMem Rule9]
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule1
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule2
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule3
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule4
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule5
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule6
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule7
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule8
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule9
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule1
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule2
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule3
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule4
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule5
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule6]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule7]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule8]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule9]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule1]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule1
[PathMemory]  LinkRule name=Rule1
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule1, act#=0, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule2]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule2
[PathMemory]  LinkRule name=Rule2
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule2, act#=1, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule3]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule3
[PathMemory]  LinkRule name=Rule3
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule3, act#=2, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule4]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule4
[PathMemory]  LinkRule name=Rule4
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule4, act#=3, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule5]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule5
[PathMemory]  LinkRule name=Rule5
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule5, act#=4, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=1 rules=[RuleMem Rule6]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule6
[PathMemory]  LinkRule name=Rule6
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule6, act#=5, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=1 rules=[RuleMem Rule7]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule7
[PathMemory]  LinkRule name=Rule7
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule7, act#=6, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=1 rules=[RuleMem Rule8]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule8
[PathMemory]  LinkRule name=Rule8
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule8, act#=7, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=1 rules=[RuleMem Rule9]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule9
[PathMemory]  LinkRule name=Rule9
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule9, act#=8, salience=0, tuple=null]
...

---- number of fired rules = 1

I see all 9 rules are “Linked” even though only Rule1 is fired. Why?

When I set a breakpoint at SegmentMemory.linkNode(), I see that JoinNode calls linkNode() regardless of its constraints with other facts. AccumulateNode is also linked (explained in the docs). In other words, these kinds of rules don’t provide the information “this rule doesn’t need further evaluation” for PHREAK.

If I run benchmark (DroolsBenchmark1) with the rules, I get the result. (Make sure to disable the TRACE logging)

$ mvn clean install
$ java -Xmx2G -jar target/drools-benchmarks.jar -wi 500 -i 500 -f 2

....
Benchmark              (engine)  Mode   Cnt      Score     Error  Units
DroolsBenchmark1.test    phreak    ss  1000  25831.879 ± 441.840  us/op
DroolsBenchmark1.test    reteoo    ss  1000  18049.810 ± 304.243  us/op

PHREAK was slower than ReteOO because PHREAK couldn’t get an advantage of unlinking (and it has some overhead).

Then I try another example.

https://github.com/tkobayas/phreak-unlinking/blob/master/src/main/resources/accumulate-unlink.drl

rule "Rule1"
when
$f1 : MyFact1()
$f2 : MyFact2(id == $f1.id, value1 == "hoge1", value2 == $f1.value2, value3 == $f1.value3, value4 == $f1.value4, value5 == $f1.value5)
$f3 : MyFact3(id == $f2.id, value1 == "hoge1", value2 == $f2.value2, value3 == $f2.value3, value4 == $f2.value4, value5 == $f2.value5)
$f4 : MyFact4(id == $f3.id, value1 == "hoge1", value2 == $f3.value2, value3 == $f3.value3, value4 == $f3.value4, value5 == $f3.value5)

$var0 : Long(this != 0) from accumulate($a0 : MyFact5(id == $f4.id, value1 == $f4.value1), count( $a0 ))

then
end

...

These rules use literal constraints (AlphaNode) for value1.

[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=0 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5], [RuleMem Rule6], [RuleMem Rule7], [RuleMem Rule8], [RuleMem Rule9]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=0 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5], [RuleMem Rule6], [RuleMem Rule7], [RuleMem Rule8], [RuleMem Rule9]
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule1
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule2
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule3
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule4
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule5
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule6
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule7
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule8
[PathMemory]   LinkSegment smask=1 rmask=1 name=Rule9
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=1 rules=[RuleMem Rule1], [RuleMem Rule2], [RuleMem Rule3], [RuleMem Rule4], [RuleMem Rule5]
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule1
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule2
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule3
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule4
[PathMemory]   LinkSegment smask=2 rmask=3 name=Rule5
[SegmentMemory] LinkNode notify=true nmask=1 smask=1 spos=2 rules=[RuleMem Rule1]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule1
[PathMemory]  LinkRule name=Rule1
[PathMemory] Queue RuleAgendaItem [Activation rule=Rule1, act#=0, salience=0, tuple=null]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=1 rules=[RuleMem Rule6]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=1 rules=[RuleMem Rule7]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=1 rules=[RuleMem Rule8]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=1 rules=[RuleMem Rule9]
[SegmentMemory] LinkNode notify=true nmask=2 smask=3 spos=2 rules=[RuleMem Rule1]
[PathMemory]   LinkSegment smask=4 rmask=7 name=Rule1
[PathMemory]  LinkRule name=Rule1
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=2 rules=[RuleMem Rule2]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=2 rules=[RuleMem Rule3]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=2 rules=[RuleMem Rule4]
[SegmentMemory] LinkNode notify=true nmask=2 smask=2 spos=2 rules=[RuleMem Rule5]
[SegmentMemory] LinkNode notify=true nmask=4 smask=6 spos=1 rules=[RuleMem Rule6]
[SegmentMemory] LinkNode notify=true nmask=4 smask=6 spos=1 rules=[RuleMem Rule7]
[SegmentMemory] LinkNode notify=true nmask=4 smask=6 spos=1 rules=[RuleMem Rule8]
[SegmentMemory] LinkNode notify=true nmask=4 smask=6 spos=1 rules=[RuleMem Rule9]
---- number of fired rules = 1

Good! Only Rule1 is linked.

How about the performance?

Benchmark              (engine)  Mode   Cnt      Score     Error  Units
DroolsBenchmark2.test    phreak    ss  1000  11104.462 ± 179.234  us/op
DroolsBenchmark2.test    reteoo    ss  1000  15623.972 ± 274.120  us/op

Big improvement in PHREAK 🙂

Unlinking allows engine to avoid unnecessary evaluations. So the ratio <# of linked rules>/<# of total rules> is important. Smaller is better.

So next, I tested 100 rules with 1/100 ratio (= 1 rule is linked out of 100 rules).

https://github.com/tkobayas/phreak-unlinking/blob/master/src/main/resources/accumulate-unlink-100.drl

Benchmark              (engine)  Mode   Cnt      Score     Error  Units
DroolsBenchmark3.test    phreak    ss  1000  21900.129 ± 286.363  us/op
DroolsBenchmark3.test    reteoo    ss  1000  51405.577 ± 471.153  us/op

The advantage gets larger.

Wrap-up:

The tested rules are more or less artificial. But in real business use cases, rules tend to use literal constraints and the number of rules are large. So generally it will get the advantage of unlinking. Rather, we have to be careful when we write small benchmark rules which miss the advantage of unlinking.

One thought on “PHREAK Unlinking

Leave a comment