Bavet - 一个更快的OptaPlanner得分引擎(附代码)

486 阅读6分钟

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的三倍。

使用案例DroolsBavet加速
作弊时间4,34914,543+234%
云平衡162,820608,204+274%
教练班车集结38,543111,991+191%
会议安排1,0721,264+18%
课程设置32,27238,933+21%
考试11,82125,712+118%
机组调度97,020126,563+30%
投资68,935401,806+483%
机器再分配13,38428,619+114%
会议安排2,2912,158-6%
奎恩177,528285,268+61%
幼儿园10,65721,090+98%
帕斯50,97147,551-7%
项目工作安排23,71578,291+230%
摇滚之旅33,997152,472+348%
任务分配10,53120,680+96%
网球106,172236,437+123%
旅游比赛49,42877,143+56%
饮料169,125430,384+154%
车辆路线8,24726,187+218%
平均数。53,644136,765+132%

在90%的用例中,Bavet比Drools快。当然,你的里程可能有所不同。打开Bavet,如果它在你的用例中不快,请告诉我们

Drools和Bavet都在不断改进。这场性能竞赛还远远没有结束。

扩展性

Bavet的扩展性好吗?

在商品硬件上,我们在不同的数据集大小上运行了5分钟的VRP基准测试,以比较Drools和Bavet的扩展情况。

BELGIUM-N50-K10BELGIUM-N100-K10BELGIUM-N500-K20BELGIUM-N1000-K20BELGIUM-N2750-K55平均数
滴水不漏76,919/s58,365/s36,609/s23,394/s29,770/s45,011/s
Bavet307,290/s242,400/s147,595/s89,850/s91,115/s175,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 MB0%
仅限Drools CS17.1 MB-2%optaplanner-constraint-drl,optaplanner-constraint-streams-bavet
Bavet CS only7.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