You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 .......

解答

  1. 跨约束共享约束流的性能影响
    OptaPlanner的约束流在编译阶段会自动做公共子表达式提取优化,就算你在多个约束里重复调用scheduledLecturesWithTeachers(cf),框架也能识别并复用这部分逻辑,所以手动提取共享的性能收益微乎其微。不过这种重构能减少重复代码,提升可维护性,是值得做的,但别指望性能有明显提升。

  2. 同一约束内复用约束流的问题
    绝对不能在同一个约束的流构建中重复使用同一个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:00:40