drools - 了解 Phreak 算法的性能
问题描述
我试图了解是什么让 Drools 在我们的用例中表现不佳。我在这里阅读了 Phreak 文档:https ://access.redhat.com/documentation/en-us/red_hat_decision_manager/7.4/html/decision_engine_in_red_hat_decision_manager/phreak-algorithm-con_decision-engine
它似乎没有提到关于如何完成节点到节点跳转的任何内容。这是一个示例来解释我的意思:
假设我有一个包含三个字段的 Person 对象:name、lastName、SSN
我这样定义了大量规则:当 lastName = 'Smith' 和 SSN = 'XXXXXXXXXX' 然后 name = "Jane"。假设我有很多人以“Smith”作为姓氏,比如 10,000,那么获得一个姓氏和 SSN 的单一名字的复杂性是多少?是否需要 10,000 次比较,或者“Smith”节点是否与所有底层 SSN 保持某种形式的哈希映射?
如果我使用范围(例如日期范围)而不是具有相等运算符的 SSN,会如何定义如下规则:“当 lastName = 'Smith' and startedShool >= 2005 and finishedSchool <= 2010”。节点是否保留一些花哨的数据结构来加快使用范围运算符的查询?
编辑:根据请求,我添加了一个示例来解释规则是如何设置的
我们有一个名为 Evidence 的类作为输入和输出。我们在不同的激活组中构建每个证据实例并将其添加到集合中。我们通常定义一些包罗万象的、低显着性的广泛规则,并添加具有更具体条件和更高显着性的规则。
这是两个显着性增加的规则的示例。我们定义了其中的约 10K。
可以想象一种树结构,在每个级别上,显着性都在增加,条件变得更加严格。Evidence 类作为一种键值对起作用,因此它们中的许多将具有相同的共同节点(例如,名称=位置)。
要执行规则,我们将添加两个证据(例如 [name= location, stringValue='BBB'] 和 [name = code, stringValue = theCode])并触发规则。
rule "RULE 1::product"
salience 0
activation-group "product"
when
$outputs : EvidenceSet.Builder( )
Evidence( name == "location" && stringValue == "BBB" )
then
$outputs.addEvidences(Evidence.newBuilder().setName("Product").setStringValue("999"));
end
rule "RULE 2::product"
salience 1
activation-group "product"
when
$outputs : EvidenceSet.Builder( )
Evidence( name == "location" && stringValue == "BBB" )
Evidence( name == "code" && stringValue == "thecode" )
then
$outputs.addEvidences(Evidence.newBuilder().setName("Product").setStringValue("777"));
end
解决方案
这是规则模板
rule "RULE %s::product"
when
$outputs : EvidenceSet.Builder( )
Evidence( name == "location" && stringValue == "BBB" )
Evidence( name == "code" && stringValue matches ".*code.*" )
then
$outputs.addEvidences(Evidence.newBuilder().setName("Product").setStringValue("777"));
end
这是从模板中构建 10000 条规则 drl 的代码
public static void main(String[] args) throws IOException {
String template = Resources.toString(Resources.getResource("template.drl"), Charset.defaultCharset());
try (PrintStream file = new PrintStream(new File("test.drl"))) {
for (int i = 0; i < 10_000; i++)
file.println(String.format(template, i));
}
}
这是基于您的 drl 语法的域类,不包括 equals、hashCode 和 toString,对我来说看起来很奇怪......
public class Evidence {
public String name;
public String stringValue;
public Evidence() {
}
public Evidence(String name, String stringValue) {
this.name = name;
this.stringValue = stringValue;
}
public static Builder newBuilder() {
return new Builder();
}
}
public class EvidenceSet {
public static Set<Evidence> evidenceSet = new HashSet<>();
public static class Builder {
public Evidence evidence = new Evidence();
public Builder setName(String name) {
evidence.name = name;
return this;
}
public Builder setStringValue(String stringValue) {
evidence.stringValue = stringValue;
return this;
}
public void addEvidences(Builder builder) {
evidenceSet.add(builder.evidence);
}
}
}
这是执行 10k 规则文件的测试
@DroolsSession("classpath:/test.drl")
public class PlaygroundTest {
private PerfStat firstStat = new PerfStat("first");
private PerfStat othersStat = new PerfStat("others");
@Rule
public DroolsAssert drools = new DroolsAssert();
@Test
public void testIt() {
firstStat.start();
drools.insertAndFire(new EvidenceSet.Builder(), new Evidence("location", "BBB"), new Evidence("code", "thecode"));
firstStat.stop();
for (int i = 0; i < 500; i++) {
othersStat.start();
drools.insertAndFire(new Evidence("code", "code" + i));
othersStat.stop();
}
System.out.printf("first, ms: %s%n", firstStat.getStat().getAvgTimeMs());
System.out.printf("others, ms: %s%n", othersStat.getStat().getAvgTimeMs());
System.out.println(EvidenceSet.evidenceSet);
}
}
规则和测试应该符合您的要求,以匹配规则中的所有决策节点。议程组和显着性与性能无关,为简单起见此处略过。
没有“for”部分的测试,给出以下分析结果
“赢家”是org.drools.core.rule.JavaDialectRuntimeData.PackageClassLoader.internalDefineClass
为drools创建和注册自动生成的java类然后阻塞,这是重量级操作。
带有“for”部分的测试给出了完全不同的画面
通知internalDefineClass
保持不变,测试代码执行变得显着。因为加载了 10k 规则类,并且在我的 6 核(12 个逻辑)CPU 机器上花费了 12.5 秒。之后,所有其他插入均在 avg 中执行。700 毫秒。让我们分析一下那个时候。
然后规则块在平均 0.01 毫秒内执行。
RULE 9::product - min: 0.00 avg: 0.01 max: 1.98 activations: 501
10000 RHS * 0.01 ms = 100 ms是 10k 条规则执行以下操作所花费的时间
$outputs.addEvidences(Evidence.newBuilder().setName("Product").setStringValue("777"));
虚拟对象的创建和操作变得值得注意,因为您将其倍增至 10k。根据第二次性能分析结果,大多数时候它会将测试运行器输出打印到控制台。总共100毫秒。考虑到您粗鲁的对象创建服务于“构建器”范式,RHS 执行速度非常快。
要回答您的问题,我会猜测并提出以下问题:
您不得重用已编译的规则会话。
它可以被低估然后阻塞执行时间,如果你在 RHS 的某个地方有虚拟的 String.format ,那么整个执行时间将会值得注意,因为 format 比较慢并且你正在处理 10k 执行。
可能存在笛卡尔规则匹配。仅仅因为您使用集合作为结果,您只能猜测该集合中插入了多少“完全相同的结果”,这意味着巨大的执行浪费。
如果您使用的是全状态会话,我看不到任何内存清理和对象收回。这可能会在 OOME 之前导致性能问题。这是运行的内存占用...
day2...笛卡尔匹配
我删除了日志记录并对笛卡尔匹配进行了测试
@Test
public void testIt() {
firstStat.start();
drools.insertAndFire(new EvidenceSet.Builder(), new Evidence("location", "BBB"), new Evidence("code", "thecode"));
firstStat.stop();
for (int i = 0; i < 40; i++) {
othersStat.start();
drools.insertAndFire(new Evidence("location", "BBB"), new Evidence("code", "code" + i));
othersStat.stop();
}
drools.printPerformanceStatistic();
System.out.printf("first, ms: %s%n", firstStat.getStat().getAvgTimeMs());
System.out.printf("others, ms: %s%n", othersStat.getStat().getAvgTimeMs());
System.out.println(EvidenceSet.evidenceSet);
}
请注意,我们正在插入new Evidence("location", "BBB")
每次迭代。我只能以 40 次迭代运行测试,否则我最终会使用 OOME(对我来说是新事物)。这是 40 次迭代的 CPU 和内存消耗:
每条规则被触发 1681 次!RHS 平均 时间并不显着(优化并从执行中删除?),但是当块被执行时......
RULE 999::product - min: 0.00 avg: 0.00 max: 1.07 activations: 1681
RULE 99::product - min: 0.00 avg: 0.00 max: 1.65 activations: 1681
RULE 9::product - min: 0.00 avg: 0.00 max: 1.50 activations: 1681
first, ms: 10271.322
others, ms: 1614.294
删除笛卡尔匹配后,相同的测试执行得更快,并且没有内存和 GC 问题
RULE 999::product - min: 0.00 avg: 0.04 max: 1.27 activations: 41
RULE 99::product - min: 0.00 avg: 0.04 max: 1.28 activations: 41
RULE 9::product - min: 0.00 avg: 0.04 max: 1.52 activations: 41
first, ms: 10847.358
others, ms: 102.015
现在我可以将迭代次数增加到 1000 次,并且迭代的平均执行时间更短
RULE 999::product - min: 0.00 avg: 0.00 max: 1.06 activations: 1001
RULE 99::product - min: 0.00 avg: 0.00 max: 1.20 activations: 1001
RULE 9::product - min: 0.00 avg: 0.01 max: 1.79 activations: 1001
first, ms: 10986.619
others, ms: 71.748
插入事实而不撤回将有它的限制
摘要:您需要确保没有得到笛卡尔匹配。
省略任何笛卡尔匹配的快速解决方案是保持唯一Evidence
的 s。以下不是执行此操作的“常用方法”,但不需要更改规则条件并增加更多复杂性。
Evidence
向类添加方法
public boolean isDuplicate(Evidence evidence) {
return this != evidence && hashCode() == evidence.hashCode() && equals(evidence);
}
添加具有最高显着性的规则
rule "unique known evidence"
salience 10000
when
e: Evidence()
Evidence(this.isDuplicate(e))
then
retract(e);
end
这已经过测试,并且似乎可以通过重现笛卡尔问题的先前 junit 测试来工作。有趣的是,对于这个测试(1000 次迭代,1000 条规则),isDuplicate
方法被调用了 2008004 次,所有调用所花费的总时间为 172.543 毫秒。在我的机器上。规则的 RHS(事件撤回)至少比其他规则(证据收集)长 3 倍。
RULE 999::product - min: 0.00 avg: 0.00 max: 1.07 activations: 1001
RULE 99::product - min: 0.00 avg: 0.01 max: 1.81 activations: 1001
RULE 9::product - min: 0.00 avg: 0.00 max: 1.25 activations: 1001
unique known evidence - min: 0.01 avg: 0.03 max: 1.88 activations: 1000
first, ms: 8974.7
others, ms: 69.197
推荐阅读
- c# - ProcessStart() 在应用服务中找不到文件名
- spring-boot - vue如何将带有文件的列表发布到spring
- javascript - 方法“模拟”仅用于在单个节点上运行。找到了 0 个
- pandas - 熊猫根据另一列中的条件从一列中提取公共子字符串
- java - Maven Java没有警告非详尽的开关
- c - C中ListNode中的指针
- java - 在 Windows 上使用 JUnit 失败的 Mockito
- mediawiki - Cargo 和 SMW 有什么区别?
- node.js - 特定类型的文本数组的 Knex 迁移中的默认值不起作用
- html - 带有自定义边框的 CSS 表格