Bavet - 为OptaPlanner提供更快的评分引擎
Drools是一个非常快速的规则引擎,在引擎内部,OptaPlanner使用Drools作为评分引擎已经很久了。今天,我们宣布了一个更快的、轻量级的替代品。Bavet。
Bavet是OptaPlanner的一个功能。它不是一个规则引擎。它是一个纯粹的、单一用途的、增量分数计算的ConstraintStreams API的实现。Bavet是OptaPlanner的完整功能,8.27.0.Final 。你可以在一行代码中从Drools切换到Bavet。
分数计算速度是原来的两倍,零API变化。
更快
对于20个不同的用例,我们比较了Bavet和Drools对OptaPlanner分数的计算。我们在OpenJDK 17上运行了JMH基准测试 (ParallelGC,Xmx1G),在一个稳定的基准机器上(Intel® Xeon® Silver (12 cores total / 24 threads) 和128 GiB RAM内存),没有运行任何其他的计算要求的进程。
平均来说,Bavet的分数计算速度是Drools的两倍。在车辆路由问题上,Bavet的速度甚至是Drools的三倍。
| 使用案例 | Drools | Bavet | 加速 |
|---|---|---|---|
| 作弊时间 | 4,349 | 14,543 | +234% |
| 云平衡 | 162,820 | 608,204 | +274% |
| 教练班车集结 | 38,543 | 111,991 | +191% |
| 会议安排 | 1,072 | 1,264 | +18% |
| 课程设置 | 32,272 | 38,933 | +21% |
| 考试 | 11,821 | 25,712 | +118% |
| 机组调度 | 97,020 | 126,563 | +30% |
| 投资 | 68,935 | 401,806 | +483% |
| 机器再分配 | 13,384 | 28,619 | +114% |
| 会议安排 | 2,291 | 2,158 | -6% |
| 奎恩 | 177,528 | 285,268 | +61% |
| 幼儿园 | 10,657 | 21,090 | +98% |
| 帕斯 | 50,971 | 47,551 | -7% |
| 项目工作安排 | 23,715 | 78,291 | +230% |
| 摇滚之旅 | 33,997 | 152,472 | +348% |
| 任务分配 | 10,531 | 20,680 | +96% |
| 网球 | 106,172 | 236,437 | +123% |
| 旅游比赛 | 49,428 | 77,143 | +56% |
| 饮料 | 169,125 | 430,384 | +154% |
| 车辆路线 | 8,247 | 26,187 | +218% |
| 平均数。 | 53,644 | 136,765 | +132% |
在90%的用例中,Bavet比Drools快。当然,你的里程可能有所不同。打开Bavet,如果它在你的用例中不快,请告诉我们。
Drools和Bavet都在不断改进。这场性能竞赛还远远没有结束。
扩展性
Bavet的扩展性好吗?
在商品硬件上,我们在不同的数据集大小上运行了5分钟的VRP基准测试,以比较Drools和Bavet的扩展情况。
| BELGIUM-N50-K10 | BELGIUM-N100-K10 | BELGIUM-N500-K20 | BELGIUM-N1000-K20 | BELGIUM-N2750-K55 | 平均数 | |
|---|---|---|---|---|---|---|
| 滴水不漏 | 76,919/s | 58,365/s | 36,609/s | 23,394/s | 29,770/s | 45,011/s |
| Bavet | 307,290/s | 242,400/s | 147,595/s | 89,850/s | 91,115/s | 175,650/s |
| 速度提升 | +299.50% | +315.32% | +303.17% | +284.07% | +206.06% | +290.24% |
同样的故事,但性能差距确实随着规模的扩大而缩小。
不是一个规则引擎
Bavet不是一个规则引擎,它故意不支持推理,也不支持复杂事件处理(CEP),以及其他常见的商业规则引擎功能。
OptaPlanner只需要一个评分引擎。它的Drools实现只使用了Drools的一小部分功能。另一方面,Bavet是一个为OptaPlanner定制的评分引擎。它是OptaPlanner的一部分。它在OptaPlanner之外没有任何用途。
对于增量分数的计算,Bavet借用了RETE算法和Drools的Phreak算法的技术。例如,Bavet中的JoinNode包含insert(),update() 和retract() 方法。但在表面之下,它是一个非常不同的实现。将其与Drools中JoinNode中类似方法的方法签名进行比较。
历史和命名
我在2019年创建了Bavet作为一个POC,并把它作为一个实验性的、快速的、不完整的功能添加到OptaPlanner中。它被冻结在那里。3年了。直到最近,当Lukáš Petrovický和我完成了所有缺失的功能,并将其重构为今天的性能感觉。
命名方面,bavet是弗拉芒语(荷兰)的俚语,指围嘴。如果你的孩子在流口水,那就非常有用。我想出这个名字的时候,我们正和孩子们在一家叫Bavet的意大利面馆吃饭,同时面对这幅壁画。
好吧,也许我并没有花太多精力在这上面。
但它并不真的需要一个好名字。这只是OptaPlanner的分数计算选项之一。一个实施细节,真的。
稳定性
我们相信Bavet是非常稳定的。我们定期对Bavet成功地进行48小时以上的压力测试。这些压力测试通过解决许多用例中的大量数据集来抑制分数损坏。
更加轻量级
在OpenShift和Kubernetes云中,pod的大小很重要。通过使用Bavet,你可以缩小OptaPlanner的classpath,排除Drools的依赖。Bavet的jar是400 KB 。
在OptaPlannerhello-world quickstart上,只包含Bavet的Maven汇编jar-with-dependencies ,其大小为10 MB 。
| 核心依赖项 | 大小 | 减少 | 核心排除法 |
|---|---|---|---|
| 全部(默认) | 17.5 MB | 0% | 无 |
| 仅限Drools CS | 17.1 MB | -2% | optaplanner-constraint-drl,optaplanner-constraint-streams-bavet |
| Bavet CS only | 7.0 MB | -60% | optaplanner-constraint-drl,optaplanner-constraint-streams-drools |
默认情况下,optaplanner-core 同时包括Drools和Bavet,所以你必须在Maven或Gradle中明确排除它。
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-constraint-drl</artifactId>
</exclusion>
<exclusion>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-constraint-streams-drools</artifactId>
</exclusion>
</exclusions>
</dependency>
这样一来,optaplanner-core ,从42个过渡依赖项减少到17个。具体来说,所有这些罐子都会从你的classpath中移除。
\- org.optaplanner:optaplanner-constraint-streams-drools:...
+- org.drools:drools-engine:...
| +- org.kie:kie-api:...
| +- org.kie:kie-internal:...
| +- org.drools:drools-core:...
| | +- org.kie:kie-util-xml:...
| | +- org.drools:drools-wiring-api:...
| | +- org.drools:drools-wiring-static:...
| | +- org.drools:drools-util:...
| | \- commons-codec:commons-codec:...
| +- org.drools:drools-wiring-dynamic:...
| +- org.drools:drools-kiesession:...
| +- org.drools:drools-tms:...
| +- org.drools:drools-compiler:...
| | +- org.drools:drools-drl-parser:...
| | +- org.drools:drools-drl-extensions:...
| | +- org.drools:drools-drl-ast:...
| | +- org.kie:kie-memory-compiler:...
| | +- org.drools:drools-ecj:...
| | +- org.kie:kie-util-maven-support:...
| | \- org.antlr:antlr-runtime:...
| +- org.drools:drools-model-compiler:...
| | \- org.drools:drools-canonical-model:...
| \- org.drools:drools-model-codegen:...
| +- org.drools:drools-codegen-common:...
| +- com.github.javaparser:javaparser-core:...
| +- org.drools:drools-mvel-parser:...
| \- org.drools:drools-mvel-compiler:...
\- org.drools:drools-alphanetwork-compiler:...
Bavet (optaplanner-constraint-streams-bavet) 没有横向依赖关系(除了optaplanner-constraint-streams-common )。
试试吧
首先,如果你还没有升级到OptaPlanner8.27.0.Final 或更高版本。如果你正在使用已被淘汰的scoreDRL ,首先从scoreDRL迁移到约束流。
**默认情况下,OptaPlanner仍然使用Drools的约束流。**要使用Bavet,请明确地将ConstraintStreamImplType 改为BAVET 。
Plain Java
在你的*.java 文件中切换到Bavet:
SolverFactory<TimeTable> solverFactory = SolverFactory.create(new SolverConfig()
...
.withConstraintStreamImplType(ConstraintStreamImplType.BAVET)
...);
或在你的solverConfig.xml:
<scoreDirectorFactory>
...
<constraintStreamImplType>BAVET</constraintStreamImplType>
</scoreDirectorFactory>
Quarkus
在src/main/resources/application.properties 中切换到Bavet:
quarkus.optaplanner.solver.constraintStreamImplType=BAVET
春天
在src/main/resources/application.properties 中切换到Bavet:
optaplanner.solver.constraintStreamImplType=BAVET




