我使用Drools在OptaPlanner项目中进行分数计算,当我开始使用查询从工作内存到Java检索逻辑事实后,我意识到随着输入大小的增加,查询需要花费更多的时间来完成(伸缩性不好)。
我要检索的逻辑事实来自一堆约束,我正在应用程序的Java部分中使用它们,重点是重用Drools已经计算出的内容并在O(1中检索这些值。 )时间。
注意:我使用的Drools和OptaPlanner版本是7.0.0-SNAPSHOT。
要介绍我发现的扩展问题,我简化了事情,并创建了一个单独的简单drools项目,该项目仅包含一个执行该应用程序的主类和两个其他针对drools事实的类。显然,这也避免了OptaPlanner所增加的开销。
就像我在这个简单项目中提到的那样,有两个针对事实的类: Employee (其中具有属性“ id”)和 TotalHours (其中具有“员工”和“ totalHours”作为属性。
public class Employee {
public int id;
}
public class TotalHours {
public Employee employee;
public int totalHours;
}
此项目中的查询将在调用中作为参数传入的雇员与TotalHours对象的雇员部分进行匹配。请注意,对于给定的雇员,最多可以有一个TotalHours对象(零个或一个)。
query "get TotalHours for Employee" (Employee e)
totalHours : TotalHours(employee == e)
end
无论我在Java部分中创建TotalHours对象并将它们与Employee对象一起插入工作内存中,还是编写基于Employee对象创建逻辑事实的规则,都存在缩放问题。就像我之前在OptaPlanner项目中提到的事实一样,查询检索的事实是逻辑事实。
因此,很明显,查询没有像例如规则尝试将两个模式与其共有属性匹配时那样进行扩展,例如以下情况。即使在Employee与TotalHours之间的关系为1:N的情况下,这意味着一个员工可以在多个TotalHours对象中进行匹配,无论员工人数的增长,应用程序将大致需要相同的时间来执行该规则。由于哈希,它将很好地扩展。
rule "Match Employee and TotalHours"
when
$employee : Employee()
TotalHours(employee == $employee)
then
end
我正在进行两种类型的测试,以衡量简单的Drools应用程序的可伸缩性。在这两种情况下,都取决于测试大小,创建N个Employee和TotalHours对象,将它们插入工作内存中,并调用“ fireAllRules”。然后在:
测试1 –随着N的增加,测试查询的速度
测量完成N个不同大小的1000个查询所花费的时间
double totalTime = 0L;
for (int i = 0; i < testSize; i++) {
int randomIndex = random.nextInt(employees.size());
Employee employee = employees.get(randomIndex);
long startTime = System.currentTimeMillis();
QueryResults queryResults = kSession.getQueryResults("get TotalHours for Employee", employee);
long endTime = System.currentTimeMillis();
TotalHours totalHours = (TotalHours) queryResults.iterator().next().get("totalHours");
totalTime += (endTime - startTime);
}
测试2 –随着N的增加,测试“匹配员工和总时数”规则的速度
测量变量更新和执行不同大小的N所需的时间
double totalTime = 0L;
for (int i = 0; i < testSize; i++) {
int randomIndex = random.nextInt(totalHoursList.size());
TotalHours totalHours = totalHoursList.get(randomIndex);
int randomTotalHours = random.nextInt(100000);
long startTime = System.currentTimeMillis();
totalHours.setTotalHours(randomTotalHours);
FactHandle factHandle = kSession.getFactHandle(totalHours);
kSession.update(factHandle, totalHours);
kSession.fireAllRules();
long endTime = System.currentTimeMillis();
totalTime += (endTime - startTime);
}
我测量了1000、5000、10000、50000、100000和500000 Employee和TotalHours对象插入到工作内存中。 在下面的结果中,我们可以看到完成查询或执行规则的平均时间(以毫秒为单位)(来自1000次随机试验的平均值)。
执行查询所花费的平均时间在稳定增长,这可以看作是雇员人数的增加,而我们可以看到,将雇员与TotalHours匹配的规则正在根据结果逐步增加。
下面从Java Mission Control Flight Recording中截取的屏幕快照显示了大部分时间都花在了哪里,并且我们可以看到99.63%的时间是在方法“ org.drools.core.phreak.PhreakJoinNode.doLeftInserts”中花费的。
再说一次,我确保垃圾收集不会分散这些测试的注意力,我只是将初始和最大堆大小设置为6GB,在此最大的测试(500000名员工)需要大约700MB的运行时间。
我很好奇看到这个问题在最新版本的Drools中得到了解决,并且根据我得到的结果,它仍然以相同的方式运行,但缩放效果仍然不佳。我所做的是下载了最新版本的Drools(目前为7.22.0.Final)并进行了与上述相同的查询测试。
我们可以同时看到两个版本的结果。不要被新版本中平均时间的微小增加所欺骗,因为运行测试时我的计算机上的开销更大。
同样基于这张从Java Mission Control Flight中截取的屏幕快照,即使堆栈看起来略有不同,热点仍然是“ org.drools.core.phreak.PhreakJoinNode.doLeftInserts”。
除了所有事情之外,我还在寻找一种有效的O(1)方式来检索Drools已经计算出的内容,并能够在应用程序的Java端使用该信息。