OptaPlanner:多约束中重用同一约束流的性能收益及问题
OptaPlanner约束流复用的性能与问题分析
原始ConstraintProvider结构
public class SchedulingConstraintProvider implements ConstraintProvider { @Override public Constraint[] defineConstraints(ConstraintFactory cf) { return new Constraint[] { teacherBreakConstraint(cf), teacherConflictConstraint(cf) }; } // 仅用于代码复用,方法被两处调用 UniConstraintStream<Lecture> scheduledLecturesWithTeachers(ConstraintFactory cf) { return cf.forEach(Lecture.class) .filter(Lecture::isScheduled) .filter(Lecture::hasTeacher); } private Constraint teacherBreakConstraint(ConstraintFactory cf) { return scheduledLecturesWithTeachers(cf) // 此处调用... .join(scheduledLecturesWithTeachers(cf), Joiners.equal(Lecture::getTeacher), ...some filtering) ... .penalize(ONE_HARD) .asConstraint("teacher-break"); } private Constraint teacherConflictConstraint(ConstraintFactory cf) { return scheduledLecturesWithTeachers(cf) // ...此处调用 .join(scheduledLecturesWithTeachers(cf), Joiners.equal(Lecture::getTeacher), ... some other filtering) ... .penalize(ONE_HARD) .asConstraint("teacher-conflict"); } }
跨约束共享约束流能否提升性能?
如果重构代码,把scheduledLecturesWithTeachers约束流只创建一次,在两个约束间共享,是否能提升OptaPlanner性能?
重构后的代码如下:
@Override public Constraint[] defineConstraints(ConstraintFactory cf) { // 仅创建一次通用部分,在两个约束中复用同一对象... 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 // 此处使用... .join(scheduledLecturesWithTeachers(cf), Joiners.equal(Lecture::getTeacher), ...some filtering) ... .penalize(ONE_HARD) .asConstraint("teacher-break"); } private Constraint teacherConflictConstraint(UniConstraintStream<Lecture> scheduledLecturesWithTeachers, ConstraintFactory cf) { return scheduledLecturesWithTeachers // ...此处使用 .join(scheduledLecturesWithTeachers(cf), Joiners.equal(Lecture::getTeacher), ... some other filtering) ... .penalize(ONE_HARD) .asConstraint("teacher-conflict"); }
同一约束内重复复用约束流是否有效?
更进一步,在同一约束内重复重用该约束流对象,是否能带来性能收益?
尝试的代码如下:
private Constraint teacherConflictConstraint(UniConstraintStream<Lecture> scheduledLecturesWithTeachers, ConstraintFactory cf) { return scheduledLecturesWithTeachers // 复用对象 .join(scheduledLecturesWithTeachers, // 同一约束内再次复用 Joiners.equal(Lecture::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 .......
解答
跨约束共享约束流的性能影响
OptaPlanner的约束流在编译阶段会自动做公共子表达式提取优化,就算你在多个约束里重复调用scheduledLecturesWithTeachers(cf),框架也能识别并复用这部分逻辑,所以手动提取共享的性能收益微乎其微。不过这种重构能减少重复代码,提升可维护性,是值得做的,但别指望性能有明显提升。同一约束内复用约束流的问题
绝对不能在同一个约束的流构建中重复使用同一个UniConstraintStream对象。约束流是不可变的,但每个流实例只能被消费一次,重复使用会导致内部索引或状态混乱,就像你遇到的数组越界异常。
正确的做法是,在同一约束内如果需要相同的流逻辑,要么重新调用方法创建新的流实例,要么使用split()方法(版本支持的话)分支流——不过split()适用于分支处理同一流的结果,不适合join自身的场景。
示例正确写法:
private Constraint teacherConflictConstraint(ConstraintFactory cf) { UniConstraintStream<Lecture> scheduledLecturesWithTeachers = scheduledLecturesWithTeachers(cf); return scheduledLecturesWithTeachers .join(scheduledLecturesWithTeachers(cf), // 重新创建流实例 Joiners.equal(Lecture::getTeacher), ... some other filtering) ... .penalize(ONE_HARD) .asConstraint("teacher-conflict"); }
内容的提问来源于stack exchange,提问作者aioobe
相关产品推荐
相关产品推荐

