Skip to content

Commit c42acc7

Browse files
authored
perf: improve performance of Bavet indexing (#1324)
Refactors indexing in Bavet so that: - IndexKeys (formerly IndexProperties) become bimorphic as opposed to megamorphic, enabling all kinds of JVM optimizations. - Many instances of IndexKeys are no longer created as they were unnecessary. (Case in point: SingleIndexProperties is gone, without replacement.) - There is less indirection in the nodes. - Naming of keys, functions and mappings is standardized across the board. In the score director benchmarks, these changes show throughput improvements of up to 10 %, with GC pressure reduced by up to 40 %.
1 parent 3bf5d40 commit c42acc7

38 files changed

+865
-964
lines changed

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetIfExistsBiConstraintStream.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,23 +65,20 @@ public <Score_ extends Score<Score_>> void buildNode(NodeBuildHelper<Score_> bui
6565
TupleLifecycle<BiTuple<A, B>> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList);
6666
IndexerFactory<C> indexerFactory = new IndexerFactory<>(joiner);
6767
var node = indexerFactory.hasJoiners()
68-
? (filtering == null ? new IndexedIfExistsBiNode<>(shouldExist,
69-
indexerFactory.buildBiLeftMapping(), indexerFactory.buildRightMapping(),
68+
? (filtering == null ? new IndexedIfExistsBiNode<>(shouldExist, indexerFactory,
7069
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
7170
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
7271
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()),
7372
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()),
74-
downstream, indexerFactory.buildIndexer(true), indexerFactory.buildIndexer(false))
75-
: new IndexedIfExistsBiNode<>(shouldExist,
76-
indexerFactory.buildBiLeftMapping(), indexerFactory.buildRightMapping(),
73+
downstream)
74+
: new IndexedIfExistsBiNode<>(shouldExist, indexerFactory,
7775
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
7876
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
7977
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
8078
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()),
8179
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()),
8280
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()),
83-
downstream, indexerFactory.buildIndexer(true), indexerFactory.buildIndexer(false),
84-
filtering))
81+
downstream, filtering))
8582
: (filtering == null ? new UnindexedIfExistsBiNode<>(shouldExist,
8683
buildHelper.reserveTupleStoreIndex(parentAB.getTupleSource()),
8784
buildHelper.reserveTupleStoreIndex(parentBridgeC.getTupleSource()), downstream)

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/BavetJoinBiConstraintStream.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,15 @@ public <Score_ extends Score<Score_>> void buildNode(NodeBuildHelper<Score_> bui
5656
TupleLifecycle<BiTuple<A, B>> downstream = buildHelper.getAggregatedTupleLifecycle(childStreamList);
5757
IndexerFactory<B> indexerFactory = new IndexerFactory<>(joiner);
5858
var node = indexerFactory.hasJoiners()
59-
? new IndexedJoinBiNode<>(
60-
indexerFactory.buildUniLeftMapping(), indexerFactory.buildRightMapping(),
59+
? new IndexedJoinBiNode<>(indexerFactory,
6160
buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()),
6261
buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()),
6362
buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()),
6463
buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()),
6564
buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()),
6665
buildHelper.reserveTupleStoreIndex(rightParent.getTupleSource()),
6766
downstream, filtering, outputStoreSize + 2,
68-
outputStoreSize, outputStoreSize + 1,
69-
indexerFactory.buildIndexer(true), indexerFactory.buildIndexer(false))
67+
outputStoreSize, outputStoreSize + 1)
7068
: new UnindexedJoinBiNode<>(
7169
buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()),
7270
buildHelper.reserveTupleStoreIndex(leftParent.getTupleSource()),

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/IndexedIfExistsBiNode.java

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,37 @@
11
package ai.timefold.solver.core.impl.score.stream.bavet.bi;
22

3-
import java.util.function.BiFunction;
4-
import java.util.function.Function;
5-
63
import ai.timefold.solver.core.api.function.TriPredicate;
74
import ai.timefold.solver.core.impl.score.stream.bavet.common.AbstractIndexedIfExistsNode;
8-
import ai.timefold.solver.core.impl.score.stream.bavet.common.ExistsCounter;
9-
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexProperties;
10-
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer;
5+
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory;
116
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple;
127
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;
138
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple;
149

1510
final class IndexedIfExistsBiNode<A, B, C> extends AbstractIndexedIfExistsNode<BiTuple<A, B>, C> {
1611

17-
private final BiFunction<A, B, IndexProperties> mappingAB;
1812
private final TriPredicate<A, B, C> filtering;
1913

20-
public IndexedIfExistsBiNode(boolean shouldExist,
21-
BiFunction<A, B, IndexProperties> mappingAB, Function<C, IndexProperties> mappingC,
22-
int inputStoreIndexLeftProperties, int inputStoreIndexLeftCounterEntry, int inputStoreIndexRightProperties,
23-
int inputStoreIndexRightEntry,
24-
TupleLifecycle<BiTuple<A, B>> nextNodesTupleLifecycle,
25-
Indexer<ExistsCounter<BiTuple<A, B>>> indexerAB, Indexer<UniTuple<C>> indexerC) {
26-
this(shouldExist, mappingAB, mappingC,
27-
inputStoreIndexLeftProperties, inputStoreIndexLeftCounterEntry, -1, inputStoreIndexRightProperties,
28-
inputStoreIndexRightEntry, -1,
29-
nextNodesTupleLifecycle, indexerAB, indexerC,
30-
null);
14+
public IndexedIfExistsBiNode(boolean shouldExist, IndexerFactory<C> indexerFactory,
15+
int inputStoreIndexLeftKeys, int inputStoreIndexLeftCounterEntry,
16+
int inputStoreIndexRightKeys, int inputStoreIndexRightEntry,
17+
TupleLifecycle<BiTuple<A, B>> nextNodesTupleLifecycle) {
18+
this(shouldExist, indexerFactory,
19+
inputStoreIndexLeftKeys, inputStoreIndexLeftCounterEntry, -1,
20+
inputStoreIndexRightKeys, inputStoreIndexRightEntry, -1,
21+
nextNodesTupleLifecycle, null);
3122
}
3223

33-
public IndexedIfExistsBiNode(boolean shouldExist,
34-
BiFunction<A, B, IndexProperties> mappingAB, Function<C, IndexProperties> mappingC,
35-
int inputStoreIndexLeftProperties, int inputStoreIndexLeftCounterEntry, int inputStoreIndexLeftTrackerList,
36-
int inputStoreIndexRightProperties, int inputStoreIndexRightEntry, int inputStoreIndexRightTrackerList,
37-
TupleLifecycle<BiTuple<A, B>> nextNodesTupleLifecycle,
38-
Indexer<ExistsCounter<BiTuple<A, B>>> indexerAB, Indexer<UniTuple<C>> indexerC,
39-
TriPredicate<A, B, C> filtering) {
40-
super(shouldExist, mappingC,
41-
inputStoreIndexLeftProperties, inputStoreIndexLeftCounterEntry, inputStoreIndexLeftTrackerList,
42-
inputStoreIndexRightProperties, inputStoreIndexRightEntry, inputStoreIndexRightTrackerList,
43-
nextNodesTupleLifecycle, indexerAB, indexerC,
44-
filtering != null);
45-
this.mappingAB = mappingAB;
24+
public IndexedIfExistsBiNode(boolean shouldExist, IndexerFactory<C> indexerFactory,
25+
int inputStoreIndexLeftKeys, int inputStoreIndexLeftCounterEntry, int inputStoreIndexLeftTrackerList,
26+
int inputStoreIndexRightKeys, int inputStoreIndexRightEntry, int inputStoreIndexRightTrackerList,
27+
TupleLifecycle<BiTuple<A, B>> nextNodesTupleLifecycle, TriPredicate<A, B, C> filtering) {
28+
super(shouldExist, indexerFactory.buildBiLeftKeysExtractor(), indexerFactory,
29+
inputStoreIndexLeftKeys, inputStoreIndexLeftCounterEntry, inputStoreIndexLeftTrackerList,
30+
inputStoreIndexRightKeys, inputStoreIndexRightEntry, inputStoreIndexRightTrackerList,
31+
nextNodesTupleLifecycle, filtering != null);
4632
this.filtering = filtering;
4733
}
4834

49-
@Override
50-
protected IndexProperties createIndexProperties(BiTuple<A, B> leftTuple) {
51-
return mappingAB.apply(leftTuple.factA, leftTuple.factB);
52-
}
53-
5435
@Override
5536
protected boolean testFiltering(BiTuple<A, B> leftTuple, UniTuple<C> rightTuple) {
5637
return filtering.test(leftTuple.factA, leftTuple.factB, rightTuple.factA);

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/bi/IndexedJoinBiNode.java

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,32 @@
11
package ai.timefold.solver.core.impl.score.stream.bavet.bi;
22

33
import java.util.function.BiPredicate;
4-
import java.util.function.Function;
54

65
import ai.timefold.solver.core.impl.score.stream.bavet.common.AbstractIndexedJoinNode;
7-
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexProperties;
8-
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.Indexer;
6+
import ai.timefold.solver.core.impl.score.stream.bavet.common.index.IndexerFactory;
97
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.BiTuple;
108
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.TupleLifecycle;
119
import ai.timefold.solver.core.impl.score.stream.bavet.common.tuple.UniTuple;
1210

1311
final class IndexedJoinBiNode<A, B> extends AbstractIndexedJoinNode<UniTuple<A>, B, BiTuple<A, B>> {
1412

15-
private final Function<A, IndexProperties> mappingA;
1613
private final BiPredicate<A, B> filtering;
1714
private final int outputStoreSize;
1815

19-
public IndexedJoinBiNode(Function<A, IndexProperties> mappingA, Function<B, IndexProperties> mappingB,
16+
public IndexedJoinBiNode(IndexerFactory<B> indexerFactory,
2017
int inputStoreIndexA, int inputStoreIndexEntryA, int inputStoreIndexOutTupleListA,
2118
int inputStoreIndexB, int inputStoreIndexEntryB, int inputStoreIndexOutTupleListB,
2219
TupleLifecycle<BiTuple<A, B>> nextNodesTupleLifecycle, BiPredicate<A, B> filtering,
23-
int outputStoreSize,
24-
int outputStoreIndexOutEntryA, int outputStoreIndexOutEntryB,
25-
Indexer<UniTuple<A>> indexerA,
26-
Indexer<UniTuple<B>> indexerB) {
27-
super(mappingB,
20+
int outputStoreSize, int outputStoreIndexOutEntryA, int outputStoreIndexOutEntryB) {
21+
super(indexerFactory.buildUniLeftKeysExtractor(), indexerFactory,
2822
inputStoreIndexA, inputStoreIndexEntryA, inputStoreIndexOutTupleListA,
2923
inputStoreIndexB, inputStoreIndexEntryB, inputStoreIndexOutTupleListB,
3024
nextNodesTupleLifecycle, filtering != null,
31-
outputStoreIndexOutEntryA, outputStoreIndexOutEntryB,
32-
indexerA, indexerB);
33-
this.mappingA = mappingA;
25+
outputStoreIndexOutEntryA, outputStoreIndexOutEntryB);
3426
this.filtering = filtering;
3527
this.outputStoreSize = outputStoreSize;
3628
}
3729

38-
@Override
39-
protected IndexProperties createIndexPropertiesLeft(UniTuple<A> leftTuple) {
40-
return mappingA.apply(leftTuple.factA);
41-
}
42-
4330
@Override
4431
protected BiTuple<A, B> createOutTuple(UniTuple<A> leftTuple, UniTuple<B> rightTuple) {
4532
return new BiTuple<>(leftTuple.factA, rightTuple.factA, outputStoreSize);

core/src/main/java/ai/timefold/solver/core/impl/score/stream/bavet/common/AbstractIfExistsNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected void initCounterLeft(ExistsCounter<LeftTuple_> counter) {
5252

5353
protected final void updateUnchangedCounterLeft(ExistsCounter<LeftTuple_> counter) {
5454
if (counter.state != TupleState.OK) {
55-
// Counter state does not change because the index properties didn't change
55+
// Counter state does not change because the index keys didn't change
5656
return;
5757
}
5858
// Still needed to propagate the update for downstream filters, matchWeighers, ...

0 commit comments

Comments
 (0)