OptaPlanner:在多个不同的约束中重用相同的约束流的性能优势?



现在我的ConstraintProvider结构如下:

public class SchedulingConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory cf) {
return new Constraint[] {
teacherBreakConstraint(cf),
teacherConflictConstraint(cf)
};
}
// Simply for code reuse. Method called from two places.
UniConstraintStream<Lecture> scheduledLecturesWithTeachers(ConstraintFactory cf) {
return cf.forEach(Lecture.class)
.filter(Lecture::isScheduled)
.filter(Lecture::hasTeacher);
}
private Constraint teacherBreakConstraint(ConstraintFactory cf) {
return scheduledLecturesWithTeachers(cf)          // <---- called here...
.join(scheduledLecturesWithTeachers(cf),
Joiners.equal(Lectures::getTeacher),
...some filtering)
...
.penalize(ONE_HARD)
.asConstraint("teacher-break");
}
private Constraint teacherConflictConstraint(ConstraintFactory cf) {
return scheduledLecturesWithTeachers(cf)          // <---- ...and here
.join(scheduledLecturesWithTeachers(cf),
Joiners.equal(Lectures::getTeacher),
... some other filtering)
...
.penalize(ONE_HARD)
.asConstraint("teacher-conflict");
}
}

如果我重组我的代码,使scheduledLecturesWithTeachers约束流创建一次,并在两个约束之间共享,它会提高OptaPlanners性能吗?

?
@Override
public Constraint[] defineConstraints(ConstraintFactory cf) {

// Create this common part ONCE, then reuse the same object in both constraints...
UniConstraintStream<Lecture> scheduledLecturesWithTeachers = scheduledLecturesWithTeachers(cf);
return new Constraint[] {
teacherBreakConstraint(scheduledLecturesWithTeachers, cf),
teacherConflictConstraint(scheduledLecturesWithTeachers, cf)
};
}
UniConstraintStream<Lecture> scheduledLecturesWithTeachers(ConstraintFactory cf) {
return cf.forEach(Lecture.class)
.filter(Lecture::isScheduled)
.filter(Lecture::hasTeacher);
}
private Constraint teacherBreakConstraint(UniConstraintStream<Lecture> scheduledLecturesWithTeachers, ConstraintFactory cf) {
return scheduledLecturesWithTeachers          // <---- used here...
.join(scheduledLecturesWithTeachers(cf),
Joiners.equal(Lectures::getTeacher),
...some filtering)
...
.penalize(ONE_HARD)
.asConstraint("teacher-break");
}
private Constraint teacherConflictConstraint(UniConstraintStream<Lecture> scheduledLecturesWithTeachers, ConstraintFactory cf) {
return scheduledLecturesWithTeachers            // <---- ...and here
.join(scheduledLecturesWithTeachers(cf),
Joiners.equal(Lectures::getTeacher),
... some other filtering)
...
.penalize(ONE_HARD)
.asConstraint("teacher-conflict");
}

更进一步,在相同的约束下重用它两次有用吗?

private Constraint teacherConflictConstraint(UniConstraintStream<Lecture> scheduledLecturesWithTeachers, ConstraintFactory cf) {
return scheduledLecturesWithTeachers          // <---- reused object
.join(scheduledLecturesWithTeachers,  // <---- reused again in same constraint
Joiners.equal(Lectures::getTeacher),
... some other filtering)
...
.penalize(ONE_HARD)
.asConstraint("teacher-conflict");
}

更新:在单个约束中多次重用约束流的最后一种方法似乎有问题。我得到以下异常:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at org.drools.modelcompiler.constraints.LambdaConstraint.initBetaIndex(LambdaConstraint.java:102)
at org.drools.modelcompiler.constraints.LambdaConstraint.initIndexes(LambdaConstraint.java:92)
at org.drools.modelcompiler.constraints.LambdaConstraint.<init>(LambdaConstraint.java:72)
at org.drools.modelcompiler.KiePackagesBuilder.createSingleConstraint(KiePackagesBuilder.java:1190)
at org.drools.modelcompiler.KiePackagesBuilder.createConstraint(KiePackagesBuilder.java:1169)
at org.drools.modelcompiler.KiePackagesBuilder.addConstraintsToPattern(KiePackagesBuilder.java:1162)
at org.drools.modelcompiler.KiePackagesBuilder.addConstraintsToPattern(KiePackagesBuilder.java:1157)
at org.drools.modelcompiler.KiePackagesBuilder.buildPattern(KiePackagesBuilder.java:808)
at org.drools.modelcompiler.KiePackagesBuilder.conditionToElement(KiePackagesBuilder.java:485)
at org.drools.modelcompiler.KiePackagesBuilder.addSubConditions(KiePackagesBuilder.java:723)
at org.drools.modelcompiler.KiePackagesBuilder.populateLHS(KiePackagesBuilder.java:448)
at org.drools.modelcompiler.KiePackagesBuilder.compileRule(KiePackagesBuilder.java:251)
at org.drools.modelcompiler.KiePackagesBuilder.build(KiePackagesBuilder.java:222)
at org.drools.modelcompiler.KieBaseBuilder.createKieBaseFromModel(KieBaseBuilder.java:84)
at org.drools.modelcompiler.KieBaseBuilder.createKieBaseFromModel(KieBaseBuilder.java:64)
at org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirectorFactory.buildKieBaseFromModel(DroolsConstraintStreamScoreDirectorFactory.java:94)
at org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirectorFactory.buildKieBase(DroolsConstraintStreamScoreDirectorFactory.java:85)
at org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirectorFactory.<init>(DroolsConstraintStreamScoreDirectorFactory.java:52)
at org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirectorFactoryService.buildScoreDirectorFactory(DroolsConstraintStreamScoreDirectorFactoryService.java:75)
at org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirectorFactoryService.lambda$buildScoreDirectorFactory$0(DroolsConstraintStreamScoreDirectorFactoryService.java:54)
at org.optaplanner.core.impl.score.director.ScoreDirectorFactoryFactory.decideMultipleScoreDirectorFactories(ScoreDirectorFactoryFactory.java:147)
at org.optaplanner.core.impl.score.director.ScoreDirectorFactoryFactory.buildScoreDirectorFactory(ScoreDirectorFactoryFactory.java:40)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildScoreDirectorFactory(DefaultSolverFactory.java:165)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:71)
at org.optaplanner.core.api.solver.SolverFactory.createFromXmlFile(SolverFactory.java:72)
at .......

是,不是。您正在尝试做的事情被称为"节点共享",这是一种经过验证的性能优化技术。

DROOLS的情况下,默认实现约束流,节点共享不太可能带来任何性能改进。Drools本身确实支持节点共享,但是我们的CS实现在上面做了一些抽象,这些抽象通常会阻止Drools节点共享。

BAVET(约束流的替代实现)的情况下,这无疑带来了性能优势。无论重用多少次,重用的每个流实例只执行一次。

一般来说,我们不鼓励这种方法,因为它使代码读起来更糟糕,更难以理解。也就是说,如果最终性能是目标,那么节点共享是实现这一目标的途径。

理论上节点共享应使它们进入同一执行节点网络。

实际上,这取决于。

对于Drools CS,我不确定。

对于Bavet CS,节点共享目前只在两个流相等的情况下工作。如果两个流执行相同的操作并且它们的所有输入流也相等,则它们相等。它基本上是递归的

但是在Java中,如果两个谓词不是同一个实例,就很难判断它们是否相同。流口水会用黑魔法来检测。巴维特故意不这么做。所以大多数时候,Bavet将无法看到两个过滤器流(或任何建立在过滤器流上的流)是相同的。


话虽这么说:我怀疑在大多数情况下,像您上面展示的那样重写代码可以算作过早优化。我不会这么做,但你可以这么做。使用optaplanner-benchmark来确定是否值得更困难的读取影响。

最新更新