Skip to content

Commit 8c593d7

Browse files
committed
perf: further improve performance of precompute
The update queue does deduplicate the updates. But we can skip iterating the entire collection, if we've already been over it once.
1 parent f8b9aa8 commit 8c593d7

File tree

1 file changed

+20
-20
lines changed

1 file changed

+20
-20
lines changed

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
* @param <Tuple_>
2929
*/
3030
@NullMarked
31-
public final class RecordAndReplayPropagator<Tuple_ extends AbstractTuple>
32-
implements Propagator {
31+
public final class RecordAndReplayPropagator<Tuple_ extends AbstractTuple> implements Propagator {
3332

3433
private final Set<Object> retractQueue;
3534
private final Set<Object> insertQueue;
@@ -42,32 +41,29 @@ public final class RecordAndReplayPropagator<Tuple_ extends AbstractTuple>
4241
private final Supplier<BavetPrecomputeBuildHelper<Tuple_>> precomputeBuildHelperSupplier;
4342
private final UnaryOperator<Tuple_> internalTupleToOutputTupleMapper;
4443
private final Map<Object, List<Tuple_>> objectToOutputTuplesMap;
45-
private final Map<Class<?>, Boolean> objectClassToIsEntitySourceClass;
44+
private final Set<Object> alreadyUpdatingSet = Collections.newSetFromMap(new IdentityHashMap<>());
45+
private final Map<Class<?>, Boolean> objectClassToIsEntitySourceClassMap;
4646

4747
private final StaticPropagationQueue<Tuple_> propagationQueue;
4848

49-
public RecordAndReplayPropagator(
50-
Supplier<BavetPrecomputeBuildHelper<Tuple_>> precomputeBuildHelperSupplier,
51-
UnaryOperator<Tuple_> internalTupleToOutputTupleMapper,
52-
TupleLifecycle<Tuple_> nextNodesTupleLifecycle, int size) {
49+
public RecordAndReplayPropagator(Supplier<BavetPrecomputeBuildHelper<Tuple_>> precomputeBuildHelperSupplier,
50+
UnaryOperator<Tuple_> internalTupleToOutputTupleMapper, TupleLifecycle<Tuple_> nextNodesTupleLifecycle, int size) {
5351
this.precomputeBuildHelperSupplier = precomputeBuildHelperSupplier;
5452
this.internalTupleToOutputTupleMapper = internalTupleToOutputTupleMapper;
5553
this.objectToOutputTuplesMap = CollectionUtils.newIdentityHashMap(size);
5654

5755
// Guesstimate that updates are dominant.
5856
this.retractQueue = CollectionUtils.newIdentityHashSet(size / 20);
5957
this.insertQueue = CollectionUtils.newIdentityHashSet(size / 20);
60-
this.objectClassToIsEntitySourceClass = new HashMap<>();
58+
this.objectClassToIsEntitySourceClassMap = new HashMap<>();
6159
this.seenEntitySet = CollectionUtils.newIdentityHashSet(size);
6260
this.seenFactSet = CollectionUtils.newIdentityHashSet(size);
6361

6462
this.propagationQueue = new StaticPropagationQueue<>(nextNodesTupleLifecycle);
6563
}
6664

67-
public RecordAndReplayPropagator(
68-
Supplier<BavetPrecomputeBuildHelper<Tuple_>> precomputeBuildHelperSupplier,
69-
UnaryOperator<Tuple_> internalTupleToOutputTupleMapper,
70-
TupleLifecycle<Tuple_> nextNodesTupleLifecycle) {
65+
public RecordAndReplayPropagator(Supplier<BavetPrecomputeBuildHelper<Tuple_>> precomputeBuildHelperSupplier,
66+
UnaryOperator<Tuple_> internalTupleToOutputTupleMapper, TupleLifecycle<Tuple_> nextNodesTupleLifecycle) {
7167
this(precomputeBuildHelperSupplier, internalTupleToOutputTupleMapper, nextNodesTupleLifecycle, 1000);
7268
}
7369

@@ -77,14 +73,18 @@ public void insert(Object object) {
7773
}
7874

7975
public void update(Object object) {
76+
if (!alreadyUpdatingSet.add(object)) {
77+
// The list was already sent to the propagation queue.
78+
// Don't iterate over it again, even though the queue would deduplicate its contents.
79+
return;
80+
}
8081
// Updates happen very frequently, so we optimize by avoiding the update queue
8182
// and going straight to the propagation queue.
8283
// The propagation queue deduplicates updates internally.
8384
var outTupleList = objectToOutputTuplesMap.get(object);
84-
if (outTupleList == null) {
85-
return;
85+
if (outTupleList != null) {
86+
outTupleList.forEach(propagationQueue::update);
8687
}
87-
outTupleList.forEach(propagationQueue::update);
8888
}
8989

9090
public void retract(Object object) {
@@ -122,7 +122,7 @@ public void propagateRetracts() {
122122
// Do not remove queued retracts from inserts; if a fact property
123123
// change, there will be both a retract and insert for that fact
124124
for (var object : insertQueue) {
125-
if (objectClassToIsEntitySourceClass.computeIfAbsent(object.getClass(),
125+
if (objectClassToIsEntitySourceClassMap.computeIfAbsent(object.getClass(),
126126
precomputeBuildHelper::isSourceEntityClass)) {
127127
seenEntitySet.add(object);
128128
} else {
@@ -158,6 +158,7 @@ private static <A> List<BavetRootNode<A>> getRootNodes(Object object, NodeNetwor
158158
@Override
159159
public void propagateUpdates() {
160160
propagationQueue.propagateUpdates();
161+
alreadyUpdatingSet.clear();
161162
}
162163

163164
@Override
@@ -192,8 +193,7 @@ private void invalidateCache() {
192193
objectToOutputTuplesMap.clear();
193194
}
194195

195-
private void recalculateTuples(NodeNetwork internalNodeNetwork,
196-
Map<Class<?>, List<BavetRootNode<?>>> classToRootNodeList,
196+
private void recalculateTuples(NodeNetwork internalNodeNetwork, Map<Class<?>, List<BavetRootNode<?>>> classToRootNodeList,
197197
RecordingTupleLifecycle<Tuple_> recordingTupleLifecycle) {
198198
var internalTupleToOutputTupleMap = new IdentityHashMap<Tuple_, Tuple_>(seenEntitySet.size());
199199
for (var invalidated : seenEntitySet) {
@@ -207,12 +207,12 @@ private void recalculateTuples(NodeNetwork internalNodeNetwork,
207207
internalNodeNetwork.settle();
208208
}
209209
if (mappedTuples.isEmpty()) {
210-
objectToOutputTuplesMap.put(invalidated, Collections.emptyList());
210+
objectToOutputTuplesMap.remove(invalidated);
211211
} else {
212212
objectToOutputTuplesMap.put(invalidated, mappedTuples);
213213
}
214214
}
215215
objectToOutputTuplesMap.values().stream().flatMap(List::stream).forEach(this::insertIfAbsent);
216216
}
217217

218-
}
218+
}

0 commit comments

Comments
 (0)