Skip to content

Commit a6e619e

Browse files
committed
Optimizations around the counters
1 parent 1c29f34 commit a6e619e

File tree

10 files changed

+58
-105
lines changed

10 files changed

+58
-105
lines changed

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIndexedIfExistsNode.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ protected AbstractIndexedIfExistsNode(boolean shouldExist,
4242
this.inputStoreIndexLeftKeys = leftTupleStorePositionTracker.reserveNextAvailablePosition();
4343
this.inputStoreIndexLeftCounter = leftTupleStorePositionTracker.reserveNextAvailablePosition();
4444
this.inputStoreIndexRightKeys = rightTupleStorePositionTracker.reserveNextAvailablePosition();
45-
this.indexerLeft = indexerFactory.buildIndexer(true,
46-
new ExistsCounterPositionTracker<>(leftTupleStorePositionTracker.reserveNextAvailablePosition()));
45+
this.indexerLeft = indexerFactory.buildIndexer(true, ExistsCounterPositionTracker.instance());
4746
this.indexerRight = indexerFactory.buildIndexer(false,
4847
new TuplePositionTracker<>(rightTupleStorePositionTracker.reserveNextAvailablePosition()));
4948
}

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractUnindexedIfExistsNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected AbstractUnindexedIfExistsNode(boolean shouldExist, TupleStorePositionT
3232
this.inputStoreIndexLeftCounter = leftTupleStorePositionTracker.reserveNextAvailablePosition();
3333
this.inputStoreIndexRightTuple = rightTupleStorePositionTracker.reserveNextAvailablePosition();
3434
this.leftCounterSet = new IndexedSet<>(
35-
new ExistsCounterPositionTracker<>(leftTupleStorePositionTracker.reserveNextAvailablePosition()));
35+
new ExistsCounterPositionTracker<>());
3636
this.rightTupleSet = new IndexedSet<>(new TuplePositionTracker<>(inputStoreIndexRightTuple));
3737
}
3838

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ExistsCounter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
44
import ai.timefold.solver.core.impl.bavet.common.tuple.TupleState;
55

6+
import org.jspecify.annotations.NullMarked;
7+
8+
@NullMarked
69
public final class ExistsCounter<Tuple_ extends AbstractTuple>
710
extends AbstractPropagationMetadataCarrier<Tuple_> {
811

912
final Tuple_ leftTuple;
1013
TupleState state = TupleState.DEAD; // It's the node's job to mark a new instance as CREATING.
1114
int countRight = 0;
15+
int indexedSetPositon = -1;
1216

1317
ExistsCounter(Tuple_ leftTuple) {
1418
this.leftTuple = leftTuple;

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ExistsCounterHandle.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import ai.timefold.solver.core.impl.bavet.common.index.IndexedSet;
44
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
55

6+
import org.jspecify.annotations.NullMarked;
7+
68
/**
79
* Used for filtering in {@link AbstractIfExistsNode}.
810
* There is no place where both left and right sets for each counter would be kept together,
@@ -11,6 +13,7 @@
1113
*
1214
* @param <LeftTuple_>
1315
*/
16+
@NullMarked
1417
final class ExistsCounterHandle<LeftTuple_ extends AbstractTuple> {
1518

1619
final ExistsCounter<LeftTuple_> counter;

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ExistsCounterHandlePositionTracker.java

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,29 @@
11
package ai.timefold.solver.core.impl.bavet.common;
22

3+
import java.util.function.ToIntFunction;
4+
35
import ai.timefold.solver.core.impl.bavet.common.index.ElementPositionTracker;
46
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
57

68
import org.jspecify.annotations.NullMarked;
79

810
@SuppressWarnings({ "rawtypes", "unchecked" })
911
@NullMarked
10-
record ExistsCounterHandlePositionTracker<Tuple_ extends AbstractTuple>(PositionGetter<Tuple_> positionGetter,
11-
PositionClearer<Tuple_> positionClearer,
12+
record ExistsCounterHandlePositionTracker<Tuple_ extends AbstractTuple>(
13+
ToIntFunction<ExistsCounterHandle<Tuple_>> positionGetter,
1214
PositionSetter<Tuple_> positionSetter)
1315
implements
1416
ElementPositionTracker<ExistsCounterHandle<Tuple_>> {
1517

1618
private static final ExistsCounterHandlePositionTracker LEFT = new ExistsCounterHandlePositionTracker(
17-
tracker -> tracker.leftPosition,
18-
tracker -> {
19-
var result = tracker.leftPosition;
20-
tracker.leftPosition = -1;
21-
return result;
22-
},
19+
(ToIntFunction<ExistsCounterHandle>) tracker -> tracker.leftPosition,
2320
(tracker, position) -> {
2421
var oldValue = tracker.leftPosition;
2522
tracker.leftPosition = position;
2623
return oldValue;
2724
});
2825
private static final ExistsCounterHandlePositionTracker RIGHT = new ExistsCounterHandlePositionTracker(
29-
tracker -> tracker.rightPosition,
30-
tracker -> {
31-
var result = tracker.rightPosition;
32-
tracker.rightPosition = -1;
33-
return result;
34-
},
26+
(ToIntFunction<ExistsCounterHandle>) tracker -> tracker.rightPosition,
3527
(tracker, position) -> {
3628
var oldValue = tracker.rightPosition;
3729
tracker.rightPosition = position;
@@ -47,34 +39,15 @@ public static <Tuple_ extends AbstractTuple> ExistsCounterHandlePositionTracker<
4739
}
4840

4941
@Override
50-
public int getPosition(ExistsCounterHandle<Tuple_> element) {
51-
return positionGetter.apply(element);
52-
}
53-
54-
@Override
55-
public int setPosition(ExistsCounterHandle<Tuple_> element, int position) {
56-
return positionSetter.apply(element, position);
42+
public void setPosition(ExistsCounterHandle<Tuple_> element, int position) {
43+
positionSetter.apply(element, position);
5744
}
5845

5946
@Override
6047
public int clearPosition(ExistsCounterHandle<Tuple_> element) {
61-
return positionClearer.apply(element);
62-
}
63-
64-
@FunctionalInterface
65-
@NullMarked
66-
interface PositionGetter<Tuple_ extends AbstractTuple> {
67-
68-
int apply(ExistsCounterHandle<Tuple_> tracker);
69-
70-
}
71-
72-
@FunctionalInterface
73-
@NullMarked
74-
interface PositionClearer<Tuple_ extends AbstractTuple> {
75-
76-
int apply(ExistsCounterHandle<Tuple_> tracker);
77-
48+
var oldPosition = positionGetter.applyAsInt(element);
49+
positionSetter.apply(element, -1);
50+
return oldPosition;
7851
}
7952

8053
@FunctionalInterface

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/ExistsCounterPositionTracker.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,30 @@
33
import ai.timefold.solver.core.impl.bavet.common.index.ElementPositionTracker;
44
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
55

6-
record ExistsCounterPositionTracker<Tuple_ extends AbstractTuple>(int inputStorePosition)
6+
import org.jspecify.annotations.NullMarked;
7+
8+
@SuppressWarnings({ "unchecked", "rawtypes" })
9+
@NullMarked
10+
record ExistsCounterPositionTracker<Tuple_ extends AbstractTuple>()
711
implements
812
ElementPositionTracker<ExistsCounter<Tuple_>> {
913

10-
@Override
11-
public int getPosition(ExistsCounter<Tuple_> element) {
12-
var tuple = element.getTuple();
13-
var value = tuple.getStore(inputStorePosition);
14-
return value == null ? -1 : (int) value;
14+
private static final ExistsCounterPositionTracker INSTANCE = new ExistsCounterPositionTracker();
15+
16+
public static <Tuple_ extends AbstractTuple> ExistsCounterPositionTracker<Tuple_> instance() {
17+
return INSTANCE;
1518
}
1619

1720
@Override
18-
public int setPosition(ExistsCounter<Tuple_> element, int position) {
19-
var tuple = element.getTuple();
20-
var oldValue = getPosition(element);
21-
tuple.setStore(inputStorePosition, position);
22-
return oldValue;
21+
public void setPosition(ExistsCounter<Tuple_> element, int position) {
22+
element.indexedSetPositon = position;
2323
}
2424

2525
@Override
2626
public int clearPosition(ExistsCounter<Tuple_> element) {
27-
try {
28-
return element.getTuple().removeStore(inputStorePosition);
29-
} catch (NullPointerException e) {
30-
return -1;
31-
}
27+
var oldPosition = element.indexedSetPositon;
28+
element.indexedSetPositon = -1;
29+
return oldPosition;
3230
}
3331

3432
}

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/TuplePositionTracker.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,16 @@
33
import ai.timefold.solver.core.impl.bavet.common.index.ElementPositionTracker;
44
import ai.timefold.solver.core.impl.bavet.common.tuple.AbstractTuple;
55

6+
import org.jspecify.annotations.NullMarked;
7+
8+
@NullMarked
69
public record TuplePositionTracker<Tuple_ extends AbstractTuple>(int inputStorePosition)
710
implements
811
ElementPositionTracker<Tuple_> {
912

1013
@Override
11-
public int getPosition(Tuple_ element) {
12-
var value = element.getStore(inputStorePosition);
13-
return value == null ? -1 : (int) value;
14-
}
15-
16-
@Override
17-
public int setPosition(Tuple_ element, int position) {
18-
var oldValue = getPosition(element);
14+
public void setPosition(Tuple_ element, int position) {
1915
element.setStore(inputStorePosition, position);
20-
return oldValue;
2116
}
2217

2318
@Override

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/ElementPositionTracker.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
package ai.timefold.solver.core.impl.bavet.common.index;
22

3+
import org.jspecify.annotations.NullMarked;
4+
35
/**
46
* Allows to read and modify the position of an element in an {@link IndexedSet}.
57
* Typically points to a field in the element itself.
68
*
79
* @param <T>
810
*/
11+
@NullMarked
912
public interface ElementPositionTracker<T> {
1013

11-
/**
12-
* Gets the position of the given element.
13-
*
14-
* @param element never null
15-
* @return >= 0 if the element is tracked, or -1 if it is not tracked
16-
*/
17-
int getPosition(T element);
18-
1914
/**
2015
* Sets the position of the given element.
2116
*
2217
* @param element never null
2318
* @param position >= 0
24-
* @return the previous position of the element, or -1 if it was not tracked before
2519
*/
26-
int setPosition(T element, int position);
20+
void setPosition(T element, int position);
2721

2822
/**
2923
* Clears the position of the given element.

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/index/IndexedSet.java

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
import org.jspecify.annotations.Nullable;
1313

1414
/**
15-
* {@link ArrayList}-backed set which allows to {@link #remove(Object)} an element without knowing its position
16-
* and without an expensive lookup.
15+
* {@link ArrayList}-backed set which allows to {@link #remove(Object)} an element
16+
* without knowing its position and without an expensive lookup.
17+
* It also allows for direct random access like a list.
1718
* <p>
1819
* It uses an {@link ElementPositionTracker} to track the insertion position of each element.
1920
* When an element is removed, the insertion position of later elements is not changed.
@@ -29,8 +30,10 @@
2930
* Random access is not required for Constraint Streams, but Neighborhoods make heavy use of it;
3031
* if we used the {@link ElementAwareList} implementation instead,
3132
* we would have to copy the elements to an array every time we need to access them randomly during move generation.
32-
*
33-
*
33+
* <p>
34+
* For performance reasons, this class does not check if an element was already added;
35+
* duplicates must be avoided by the caller and will cause undefined behavior.
36+
*
3437
* @param <T>
3538
*/
3639
@NullMarked
@@ -52,20 +55,19 @@ private List<T> getElementList() {
5255
}
5356

5457
/**
55-
* Appends the specified element to the end of this collection, if not already present.
56-
* Will use identity comparison to check for presence;
57-
* two different instances which {@link Object#equals(Object) equal} are considered different elements.
58+
* Appends the specified element to the end of this collection.
59+
* If the element is already present,
60+
* undefined, unexpected, and incorrect behavior should be expected.
61+
* <p>
62+
* Presence of the element can be checked using the associated {@link ElementPositionTracker}.
63+
* For performance reasons, this method avoids that check.
5864
*
5965
* @param element element to be appended to this collection
60-
* @throws IllegalStateException if the element was already present in this collection
6166
*/
6267
public void add(T element) {
6368
var actualElementList = getElementList();
6469
actualElementList.add(element);
65-
if (elementPositionTracker.setPosition(element, actualElementList.size() - 1) >= 0) {
66-
throw new IllegalStateException("Impossible state: the element (%s) was already added to the IndexedSet."
67-
.formatted(element));
68-
}
70+
elementPositionTracker.setPosition(element, actualElementList.size() - 1);
6971
}
7072

7173
/**

core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/IndexedSetTest.java

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ void addMultipleElements() {
2525
assertThat(set.asList()).containsExactly("A", "B", "C");
2626
}
2727

28-
@Test
29-
void addDuplicateElement() {
30-
var set = new IndexedSet<>(stringTracker);
31-
32-
set.add("A");
33-
assertThatThrownBy(() -> set.add("A"))
34-
.isInstanceOf(IllegalStateException.class)
35-
.hasMessageContaining("was already added");
36-
}
37-
3828
@Test
3929
void removeElement() {
4030
var set = new IndexedSet<>(stringTracker);
@@ -226,13 +216,8 @@ private static final class SimpleTracker<T> implements ElementPositionTracker<T>
226216
private final Map<T, Integer> positions = new HashMap<>();
227217

228218
@Override
229-
public int setPosition(T element, int position) {
230-
return positions.put(element, position) == null ? -1 : position;
231-
}
232-
233-
@Override
234-
public int getPosition(T element) {
235-
return positions.getOrDefault(element, -1);
219+
public void setPosition(T element, int position) {
220+
positions.put(element, position);
236221
}
237222

238223
@Override

0 commit comments

Comments
 (0)