Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: further improve Bavet join performance #1335

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory;
import ai.timefold.solver.core.impl.score.stream.bavet.common.NodeBuildHelper;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;

final class BavetFilterBiConstraintStream<Solution_, A, B>
extends BavetAbstractBiConstraintStream<Solution_, A, B> {
Expand All @@ -30,7 +31,7 @@ public BavetFilterBiConstraintStream(BavetConstraintFactory<Solution_> constrain
@Override
public <Score_ extends Score<Score_>> void buildNode(NodeBuildHelper<Score_> buildHelper) {
buildHelper.<BiTuple<A, B>> putInsertUpdateRetract(this, childStreamList,
tupleLifecycle -> new ConditionalBiTupleLifecycle<>(predicate, tupleLifecycle));
tupleLifecycle -> TupleLifecycle.conditionally(tupleLifecycle, predicate));
}

// ************************************************************************
Expand All @@ -46,7 +47,7 @@ public int hashCode() {
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (o instanceof BavetFilterBiConstraintStream other) {
} else if (o instanceof BavetFilterBiConstraintStream<?, ?, ?> other) {
return parent == other.parent
&& predicate == other.predicate;
} else {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package ai.timefold.solver.core.impl.score.stream.bavet.common;

import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState;

Expand All @@ -27,8 +25,8 @@
* have the same {@link TupleSource}), it creates another clone.
*/
public abstract class AbstractConcatNode<LeftTuple_ extends AbstractTuple, RightTuple_ extends AbstractTuple, OutTuple_ extends AbstractTuple>
extends AbstractNode
implements LeftTupleLifecycle<LeftTuple_>, RightTupleLifecycle<RightTuple_> {
extends AbstractTwoInputNode<LeftTuple_, RightTuple_> {

private final int leftSourceTupleCloneStoreIndex;
private final int rightSourceTupleCloneStoreIndex;
protected final int outputStoreSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private OutTuple_ accumulate(InTuple_ tuple, Group<OutTuple_, ResultContainer_>
}

private Group<OutTuple_, ResultContainer_> getOrCreateGroup(GroupKey_ userSuppliedKey) {
var groupMapKey = useAssertingGroupKey ? new AssertingGroupKey(userSuppliedKey) : userSuppliedKey;
var groupMapKey = useAssertingGroupKey ? new AssertingGroupKey<>(userSuppliedKey) : userSuppliedKey;
if (hasMultipleGroups) {
// Avoids computeIfAbsent in order to not create lambdas on the hot path.
var group = groupMap.get(groupMapKey);
Expand Down Expand Up @@ -167,8 +167,9 @@ private Group<OutTuple_, ResultContainer_> createGroupWithoutGroupKey() {
return group;
}

@SuppressWarnings("unchecked")
private GroupKey_ extractUserSuppliedKey(Object groupMapKey) {
return useAssertingGroupKey ? ((AssertingGroupKey) groupMapKey).getKey() : (GroupKey_) groupMapKey;
return useAssertingGroupKey ? ((AssertingGroupKey<GroupKey_>) groupMapKey).key() : (GroupKey_) groupMapKey;
}

@Override
Expand Down Expand Up @@ -289,39 +290,23 @@ private void updateOutTupleToFinisher(OutTuple_ outTuple, ResultContainer_ resul
* Since this situation is far too frequent and users run into this,
* we have this helper class that will optionally throw an exception when it detects this.
*/
private final class AssertingGroupKey {

private final GroupKey_ key;
private final int initialHashCode;
private record AssertingGroupKey<GroupKey_>(GroupKey_ key, int initialHashCode) {

public AssertingGroupKey(GroupKey_ key) {
this.key = key;
this.initialHashCode = key == null ? 0 : key.hashCode();
this(key, key == null ? 0 : key.hashCode());
}

public GroupKey_ getKey() {
public GroupKey_ key() {
if (key != null && key.hashCode() != initialHashCode) {
throw new IllegalStateException("hashCode of object (" + key + ") of class (" + key.getClass()
+ ") has changed while it was being used as a group key within groupBy ("
+ AbstractGroupNode.this.getClass() + ").\n"
+ "Group key hashCode must consistently return the same integer, "
+ "as required by the general hashCode contract.");
throw new IllegalStateException(
"""
hashCode of object (%s) of class (%s) has changed while it was being used as a group key.
Group key hashCode must consistently return the same integer, as required by the general hashCode contract."""
.formatted(key, key.getClass().getName()));
}
return key;
}

@Override
public boolean equals(Object other) {
if (other == null || getClass() != other.getClass())
return false;
return Objects.equals(getKey(), ((AssertingGroupKey) other).getKey());
}

@Override
public int hashCode() {
var key = getKey();
return key == null ? 0 : key.hashCode();
}
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package ai.timefold.solver.core.impl.score.stream.bavet.common;

import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple;
Expand All @@ -19,8 +17,7 @@
* @param <Right_>
*/
public abstract class AbstractIfExistsNode<LeftTuple_ extends AbstractTuple, Right_>
extends AbstractNode
implements LeftTupleLifecycle<LeftTuple_>, RightTupleLifecycle<UniTuple<Right_>> {
extends AbstractTwoInputNode<LeftTuple_, UniTuple<Right_>> {

protected final boolean shouldExist;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package ai.timefold.solver.core.impl.score.stream.bavet.common;

import java.util.Objects;

import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.KeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.UniKeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.KeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.UniKeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;
Expand Down Expand Up @@ -57,7 +59,7 @@ public final void insertLeft(LeftTuple_ leftTuple) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + leftTuple
+ ") was already added in the tupleStore.");
}
var indexKeys = keysExtractorLeft.apply(leftTuple);
var indexKeys = keysExtractorLeft.apply(leftTuple, null);
leftTuple.setStore(inputStoreIndexLeftKeys, indexKeys);

var counter = new ExistsCounter<>(leftTuple);
Expand Down Expand Up @@ -87,11 +89,11 @@ public final void updateLeft(LeftTuple_ leftTuple) {
insertLeft(leftTuple);
return;
}
var newIndexKeys = keysExtractorLeft.apply(leftTuple);
var newIndexKeys = keysExtractorLeft.apply(leftTuple, oldIndexKeys);
ElementAwareListEntry<ExistsCounter<LeftTuple_>> counterEntry = leftTuple.getStore(inputStoreIndexLeftCounterEntry);
var counter = counterEntry.getElement();

if (oldIndexKeys.equals(newIndexKeys)) {
if (Objects.equals(oldIndexKeys, newIndexKeys)) {
// No need for re-indexing because the index keys didn't change
// The indexers contain counters in the DEAD state, to track the rightCount.
if (!isFiltering) {
Expand Down Expand Up @@ -143,7 +145,7 @@ public final void insertRight(UniTuple<Right_> rightTuple) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + rightTuple
+ ") was already added in the tupleStore.");
}
var indexKeys = keysExtractorRight.apply(rightTuple);
var indexKeys = keysExtractorRight.apply(rightTuple, null);
rightTuple.setStore(inputStoreIndexRightKeys, indexKeys);

var rightEntry = indexerRight.put(indexKeys, rightTuple);
Expand All @@ -169,8 +171,8 @@ public final void updateRight(UniTuple<Right_> rightTuple) {
insertRight(rightTuple);
return;
}
var newIndexKeys = keysExtractorRight.apply(rightTuple);
if (oldIndexKeys.equals(newIndexKeys)) {
var newIndexKeys = keysExtractorRight.apply(rightTuple, oldIndexKeys);
if (Objects.equals(oldIndexKeys, newIndexKeys)) {
// No need for re-indexing because the index keys didn't change
if (isFiltering) {
var rightTrackerList = updateRightTrackerList(rightTuple);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package ai.timefold.solver.core.impl.score.stream.bavet.common;

import java.util.Objects;

import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.KeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory.UniKeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.KeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.UniKeysExtractor;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;
Expand Down Expand Up @@ -58,7 +60,7 @@ public final void insertLeft(LeftTuple_ leftTuple) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + leftTuple
+ ") was already added in the tupleStore.");
}
var indexKeys = keysExtractorLeft.apply(leftTuple);
var indexKeys = keysExtractorLeft.apply(leftTuple, null);
var outTupleListLeft = new ElementAwareList<OutTuple_>();
leftTuple.setStore(inputStoreIndexLeftOutTupleList, outTupleListLeft);
indexAndPropagateLeft(leftTuple, indexKeys);
Expand All @@ -72,8 +74,8 @@ public final void updateLeft(LeftTuple_ leftTuple) {
insertLeft(leftTuple);
return;
}
var newIndexKeys = keysExtractorLeft.apply(leftTuple);
if (oldIndexKeys.equals(newIndexKeys)) {
var newIndexKeys = keysExtractorLeft.apply(leftTuple, oldIndexKeys);
if (Objects.equals(oldIndexKeys, newIndexKeys)) {
// No need for re-indexing because the index keys didn't change
// Prefer an update over retract-insert if possible
innerUpdateLeft(leftTuple, consumer -> indexerRight.forEach(oldIndexKeys, consumer));
Expand Down Expand Up @@ -114,7 +116,7 @@ public final void insertRight(UniTuple<Right_> rightTuple) {
throw new IllegalStateException("Impossible state: the input for the tuple (" + rightTuple
+ ") was already added in the tupleStore.");
}
var indexKeys = keysExtractorRight.apply(rightTuple);
var indexKeys = keysExtractorRight.apply(rightTuple, null);
var outTupleListRight = new ElementAwareList<OutTuple_>();
rightTuple.setStore(inputStoreIndexRightOutTupleList, outTupleListRight);
indexAndPropagateRight(rightTuple, indexKeys);
Expand All @@ -128,8 +130,8 @@ public final void updateRight(UniTuple<Right_> rightTuple) {
insertRight(rightTuple);
return;
}
var newIndexKeys = keysExtractorRight.apply(rightTuple);
if (oldIndexKeys.equals(newIndexKeys)) {
var newIndexKeys = keysExtractorRight.apply(rightTuple, oldIndexKeys);
if (Objects.equals(oldIndexKeys, newIndexKeys)) {
// No need for re-indexing because the index keys didn't change
// Prefer an update over retract-insert if possible
innerUpdateRight(rightTuple, consumer -> indexerLeft.forEach(oldIndexKeys, consumer));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import java.util.function.Consumer;

import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleState;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple;
Expand All @@ -21,8 +19,7 @@
* @param <Right_>
*/
public abstract class AbstractJoinNode<LeftTuple_ extends AbstractTuple, Right_, OutTuple_ extends AbstractTuple>
extends AbstractNode
implements LeftTupleLifecycle<LeftTuple_>, RightTupleLifecycle<UniTuple<Right_>> {
extends AbstractTwoInputNode<LeftTuple_, UniTuple<Right_>> {

protected final int inputStoreIndexLeftOutTupleList;
protected final int inputStoreIndexRightOutTupleList;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ai.timefold.solver.core.impl.score.stream.bavet.common;

import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.AbstractTuple;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.LeftTupleLifecycle;
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.RightTupleLifecycle;

public abstract class AbstractTwoInputNode<LeftTuple_ extends AbstractTuple, RightTuple_ extends AbstractTuple>
extends AbstractNode
implements LeftTupleLifecycle<LeftTuple_>, RightTupleLifecycle<RightTuple_> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ public final void retractLeft(LeftTuple_ leftTuple) {
var counter = counterEntry.getElement();
counterEntry.remove();
if (isFiltering) {
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList = leftTuple.getStore(inputStoreIndexLeftTrackerList);
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList =
leftTuple.removeStore(inputStoreIndexLeftTrackerList);
leftTrackerList.forEach(FilteringTracker::remove);
}
killCounterLeft(counter);
Expand Down
Loading
Loading