Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

OptaPlanner: Performance benefits of reusing the same constraint stream in multiple different constraints?

Right now my ConstraintProvider is structured as follows:

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");
    }
}

Would it improve OptaPlanners performance if I restructured my code so that the scheduledLecturesWithTeachers constraint stream was created once and shared between the two constraints?

@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");
}

Taking this one step further, would it be useful to even reuse it twice in the same constraint?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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");
}

>Solution :

Yes and no. What you are trying to do is called "node sharing", and it is a proven performance optimization technique.

In the case of DROOLS, the default implementation of Constraint Streams, node sharing is unlikely to bring any performance improvements. Drools itself does support node sharing, but our CS implementation on top makes some abstractions that often prevent Drools node sharing.

In the case of BAVET, the alternative implementation of Constraint Streams, this most certainly does bring performance benefits. Every stream instance you reuse only gets executed once, no matter how many times you reuse it.

In general, we discourage this approach, because it makes the code read worse and harder to understand. That said, if ultimate performance is the goal, then node sharing is the way to get there.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading