Drools性能实践总结

本文总结一下Drools的性能提升要点。

要点

  • 选择7.5以后的版本。老版本存在一个性能BUG。
  • KnowledgeBuilder很耗时,编译结果要缓存起来。这样builder就不会成为瓶颈。

  • kbase过大,会导致内存消耗过多,gc也可能成为瓶颈。建议按规则相关性拆分为多个Kbase。但需要注意,如果一个kbase里的规则数太少,那么无法发挥drools的优势,跟你自己写if-else 没啥差别。所以拆分的重点是规则相关性,而不是数量

  • drools的体系注定其更适应增加rule数,而不是增加fact数。在使用时牢记此点。另外,最极端的情况,使用一个通用类去表达所有的fact,会增加beta节点向左侧匹配值的次数,也会导致性能下降,并降低规则可读性。

  • 尽量减少datafact的碎片化,尽量在同一个session中仅包含相关fact和相关rule。在创建session时,将所有fact批量insertsession中,然后fire比为每个fact单独触发规则更为可取。

  • 在调用insert()之前准备好数据远远胜过在LHS里去访问数据库获取数据,这种做法可以显著提高性能。

  • 规则之间,不要耦合。

  • insert,少from

  • 避免在then段落中对fact做约束,约束要提前到when

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 错误示范
rule "Show Sprinklers" when
$room : Room()
$sprinkler : Sprinkler()
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
end

// 正确示范
rule
when
$room : Room()
$sprinkler : Sprinkler( room == $room )
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
end
  • 建议使用属性访问而不是显式使用getter,因为前者可以通过字段索引提高性能。
1
2
3
4
// 错误示范
Person( getAge() == 50 )
// 正确示范
Person( age == 50 )
  • 尽量使用,,而不是&&drools对前者有更好的性能优化。但,不能用于带括号的组合条件中。
1
2
3
4
5
6
// 错误示范
Person( age > 50 && weight > 80 )
// 正确示范
Person( age > 50 , weight > 80 )
// 下面会编译错误
Person( ( age > 50, weight > 80 ) || height > 2 )
  • 使用fact属性传递约束优于使用变量传递约束,因为==赋值非常高效。(可以用到哈希索引)
1
Person( age == $firstAge )
  • 如果一个POJO的属性可能在drools里被修改(modify方法),但你不期待这个属性修改触发规则的重新匹配。那么加上@ClassReactive注解,这将有助于提高性能和递归。
1
2
3
4
5
6
7
8
9
10
11
12
// Java里
@ClassReactive
public static class Person {
private String firstName;
private String lastName;
}
//drools里
declare Person
@classReactive
firstName : String
lastName : String
end
  • 避免在drools里使用方言去accumulate。原因有几个,包括在维护和测试使用它们的规则时遇到困难,以及无法重用该代码。实现自己的累积函数非常简单,它们易于单元测试和使用。这种形式的累积只支持向后兼容。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 这是一个使用java方言实现累计的例子。(不建议)
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
// apply discount to $order
end
  • 善用窗口,实现基于流的小批处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
declare window Ticks
StockTick( source == "NYSE" )
over window:length( 10 )
from entry-point STStream
end

rule "RHT ticks in the window"
when
accumulate( StockTick( company == "RHT" ) from window Ticks,
$cnt : count(1) )
then
// there has been $cnt RHT ticks over the last 10 ticks
end
  • 谨慎使用嵌套访问,很可能就是性能陷阱。
1
2
3
# 嵌套访问示例
Person( address.houseNumber == 50 )
Person( getAddress().getHouseNumber() == 50 )
  • 不要过度使用eval,因为它会降低规则的声明性,从而导致引擎性能不佳。eval不能被索引,因此不如字段约束有效。
1
2
3
4
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )
  • 在编写规则时,遵循将区隔度最大的节点放在最前面,以降低计算量。

END