Skip to content

Commit

Permalink
perf: reduce allocations in CS
Browse files Browse the repository at this point in the history
  • Loading branch information
triceo committed Jan 10, 2024
1 parent 3cb5490 commit 92ff8fa
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ protected void decrementCounterRight(ExistsCounter<LeftTuple_> counter) {

protected ElementAwareList<FilteringTracker<LeftTuple_>> updateRightTrackerList(UniTuple<Right_> rightTuple) {
ElementAwareList<FilteringTracker<LeftTuple_>> rightTrackerList = rightTuple.getStore(inputStoreIndexRightTrackerList);
rightTrackerList.forEach(filteringTacker -> {
decrementCounterRight(filteringTacker.counter);
filteringTacker.remove();
});
for (FilteringTracker<LeftTuple_> tuple : rightTrackerList) {
decrementCounterRight(tuple.counter);
tuple.remove();
}
return rightTrackerList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,21 @@ public final void insertLeft(LeftTuple_ leftTuple) {

ExistsCounter<LeftTuple_> counter = new ExistsCounter<>(leftTuple);
ElementAwareListEntry<ExistsCounter<LeftTuple_>> counterEntry = indexerLeft.put(indexProperties, counter);
leftTuple.setStore(inputStoreIndexLeftCounterEntry, counterEntry);
updateCounterRight(leftTuple, indexProperties, counter, counterEntry);
initCounterLeft(counter);
}

private void updateCounterRight(LeftTuple_ leftTuple, IndexProperties indexProperties, ExistsCounter<LeftTuple_> counter,
ElementAwareListEntry<ExistsCounter<LeftTuple_>> counterEntry) {
leftTuple.setStore(inputStoreIndexLeftCounterEntry, counterEntry);
if (!isFiltering) {
counter.countRight = indexerRight.size(indexProperties);
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList = new ElementAwareList<>();
var leftTrackerList = new ElementAwareList<FilteringTracker<LeftTuple_>>();
indexerRight.forEach(indexProperties,
rightTuple -> updateCounterFromLeft(leftTuple, rightTuple, counter, leftTrackerList));
leftTuple.setStore(inputStoreIndexLeftTrackerList, leftTrackerList);
}
initCounterLeft(counter);
}

@Override
Expand Down Expand Up @@ -106,16 +110,7 @@ public final void updateLeft(LeftTuple_ leftTuple) {
updateIndexerLeft(oldIndexProperties, counterEntry, leftTuple);
counter.countRight = 0;
leftTuple.setStore(inputStoreIndexLeftProperties, newIndexProperties);
counterEntry = indexerLeft.put(newIndexProperties, counter);
leftTuple.setStore(inputStoreIndexLeftCounterEntry, counterEntry);
if (!isFiltering) {
counter.countRight = indexerRight.size(newIndexProperties);
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList = new ElementAwareList<>();
indexerRight.forEach(newIndexProperties,
rightTuple -> updateCounterFromLeft(leftTuple, rightTuple, counter, leftTrackerList));
leftTuple.setStore(inputStoreIndexLeftTrackerList, leftTrackerList);
}
updateCounterRight(leftTuple, newIndexProperties, counter, indexerLeft.put(newIndexProperties, counter));
updateCounterLeft(counter);
}
}
Expand Down Expand Up @@ -154,10 +149,14 @@ public final void insertRight(UniTuple<Right_> rightTuple) {

ElementAwareListEntry<UniTuple<Right_>> rightEntry = indexerRight.put(indexProperties, rightTuple);
rightTuple.setStore(inputStoreIndexRightEntry, rightEntry);
updateCounterLeft(rightTuple, indexProperties);
}

private void updateCounterLeft(UniTuple<Right_> rightTuple, IndexProperties indexProperties) {
if (!isFiltering) {
indexerLeft.forEach(indexProperties, this::incrementCounterRight);
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> rightTrackerList = new ElementAwareList<>();
var rightTrackerList = new ElementAwareList<FilteringTracker<LeftTuple_>>();
indexerLeft.forEach(indexProperties, counter -> updateCounterFromRight(rightTuple, counter, rightTrackerList));
rightTuple.setStore(inputStoreIndexRightTrackerList, rightTrackerList);
}
Expand Down Expand Up @@ -191,14 +190,7 @@ public final void updateRight(UniTuple<Right_> rightTuple) {
rightTuple.setStore(inputStoreIndexRightProperties, newIndexProperties);
rightEntry = indexerRight.put(newIndexProperties, rightTuple);
rightTuple.setStore(inputStoreIndexRightEntry, rightEntry);
if (!isFiltering) {
indexerLeft.forEach(newIndexProperties, this::incrementCounterRight);
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> rightTrackerList = new ElementAwareList<>();
indexerLeft.forEach(newIndexProperties,
counter -> updateCounterFromRight(rightTuple, counter, rightTrackerList));
rightTuple.setStore(inputStoreIndexRightTrackerList, rightTrackerList);
}
updateCounterLeft(rightTuple, newIndexProperties);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ private OutTuple_ findOutTuple(ElementAwareList<OutTuple_> outTupleList, Element
ElementAwareListEntry<OutTuple_> outEntry = outTuple.getStore(outputStoreIndexOutEntry);
ElementAwareList<OutTuple_> outEntryList = outEntry.getList();
if (outList == outEntryList) {
outTupleList.prematurelyTerminateIterator(); // Performance optimization.
return outTuple;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ public final void insertLeft(LeftTuple_ leftTuple) {
counter.countRight = rightTupleList.size();
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList = new ElementAwareList<>();
rightTupleList.forEach(rightTuple -> updateCounterFromLeft(leftTuple, rightTuple, counter, leftTrackerList));
for (UniTuple<Right_> tuple : rightTupleList) {
updateCounterFromLeft(leftTuple, tuple, counter, leftTrackerList);
}
leftTuple.setStore(inputStoreIndexLeftTrackerList, leftTrackerList);
}
initCounterLeft(counter);
Expand All @@ -75,7 +77,9 @@ public final void updateLeft(LeftTuple_ leftTuple) {
ElementAwareList<FilteringTracker<LeftTuple_>> leftTrackerList = leftTuple.getStore(inputStoreIndexLeftTrackerList);
leftTrackerList.forEach(FilteringTracker::remove);
counter.countRight = 0;
rightTupleList.forEach(rightTuple -> updateCounterFromLeft(leftTuple, rightTuple, counter, leftTrackerList));
for (UniTuple<Right_> tuple : rightTupleList) {
updateCounterFromLeft(leftTuple, tuple, counter, leftTrackerList);
}
updateCounterLeft(counter);
}
}
Expand Down Expand Up @@ -108,7 +112,9 @@ public final void insertRight(UniTuple<Right_> rightTuple) {
leftCounterList.forEach(this::incrementCounterRight);
} else {
ElementAwareList<FilteringTracker<LeftTuple_>> rightTrackerList = new ElementAwareList<>();
leftCounterList.forEach(counter -> updateCounterFromRight(rightTuple, counter, rightTrackerList));
for (ExistsCounter<LeftTuple_> tuple : leftCounterList) {
updateCounterFromRight(rightTuple, tuple, rightTrackerList);
}
rightTuple.setStore(inputStoreIndexRightTrackerList, rightTrackerList);
}
}
Expand All @@ -123,7 +129,9 @@ public final void updateRight(UniTuple<Right_> rightTuple) {
}
if (isFiltering) {
ElementAwareList<FilteringTracker<LeftTuple_>> rightTrackerList = updateRightTrackerList(rightTuple);
leftCounterList.forEach(counter -> updateCounterFromRight(rightTuple, counter, rightTrackerList));
for (ExistsCounter<LeftTuple_> tuple : leftCounterList) {
updateCounterFromRight(rightTuple, tuple, rightTrackerList);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public final void insertLeft(LeftTuple_ leftTuple) {
leftTuple.setStore(inputStoreIndexLeftEntry, leftEntry);
ElementAwareList<OutTuple_> outTupleListLeft = new ElementAwareList<>();
leftTuple.setStore(inputStoreIndexLeftOutTupleList, outTupleListLeft);
rightTupleList.forEach(rightTuple -> insertOutTupleFiltered(leftTuple, rightTuple));
for (UniTuple<Right_> tuple : rightTupleList) {
insertOutTupleFiltered(leftTuple, tuple);
}
}

@Override
Expand Down Expand Up @@ -80,7 +82,9 @@ public final void insertRight(UniTuple<Right_> rightTuple) {
rightTuple.setStore(inputStoreIndexRightEntry, rightEntry);
ElementAwareList<OutTuple_> outTupleListRight = new ElementAwareList<>();
rightTuple.setStore(inputStoreIndexRightOutTupleList, outTupleListRight);
leftTupleList.forEach(leftTuple -> insertOutTupleFiltered(leftTuple, rightTuple));
for (LeftTuple_ tuple : leftTupleList) {
insertOutTupleFiltered(tuple, rightTuple);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public int size(IndexProperties indexProperties) {
public void forEach(IndexProperties indexProperties, Consumer<T> tupleConsumer) {
Key_ indexKey = indexProperties.toKey(propertyIndex);
Indexer<T> downstreamIndexer = downstreamIndexerMap.get(indexKey);
if (downstreamIndexer == null || downstreamIndexer.isEmpty()) {
if (downstreamIndexer == null) {
return;
}
downstreamIndexer.forEach(indexProperties, tupleConsumer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@
/**
* Linked list that allows to add and remove an element in O(1) time.
* Ideal for incremental operations with frequent undo.
* <p>
* This class is not thread-safe.
*
* @param <T> The element type. Often a tuple.
*/
public final class ElementAwareList<T> implements Iterable<T> {

private ElementAwareListIterator sharedIterator;

private int size = 0;
private ElementAwareListEntry<T> first = null;
private ElementAwareListEntry<T> last = null;

public ElementAwareList() {
}

public ElementAwareListEntry<T> add(T tuple) {
ElementAwareListEntry<T> entry = new ElementAwareListEntry<>(this, tuple, last);
if (first == null) {
Expand Down Expand Up @@ -56,6 +63,50 @@ public int size() {
return size;
}

/**
* Convenience method for where it is easy to use a non-capturing lambda.
* If a capturing lambda consumer were to be created for this method, use {@link #iterator()} instead,
* which will consume less memory.
* <p>
*
* For example, the following code is perfectly fine:
*
* <code>
* for (int i = 0; i &lt; 3; i++) {
* elementAwareList.forEach(entry -&gt; doSomething(entry));
* }
* </code>
*
* It will create only one lambda instance, regardless of the number of iterations;
* it doesn't need to capture any state.
* On the contrary, the following code will create three instances of a capturing lambda,
* one for each iteration of the for loop:
*
* <code>
* for (int a: List.of(1, 2, 3)) {
* elementAwareList.forEach(entry -&gt; doSomething(entry, a));
* }
* </code>
*
* In this case, the lambda would need to capture "a" which is different in every iteration.
* Therefore, it will generally be better to use the iterator variant,
* as that will only ever create one instance of the iterator,
* regardless of the number of iterations:
*
* <code>
* for (int a: List.of(1, 2, 3)) {
* for (var entry: elementAwareList) {
* doSomething(entry, a);
* }
* }
* </code>
*
* This is only an issue on the hot path,
* where this method can create quite a large garbage collector pressure
* on account of creating throw-away instances of capturing lambdas.
*
* @param tupleConsumer The action to be performed for each element
*/
@Override
public void forEach(Consumer<? super T> tupleConsumer) {
ElementAwareListEntry<T> entry = first;
Expand All @@ -67,31 +118,34 @@ public void forEach(Consumer<? super T> tupleConsumer) {
}
}

/**
* See {@link #forEach(Consumer)} for a discussion on the correct use of this method.
*
* @return never null
*/
@Override
public Iterator<T> iterator() {
return new Iterator<>() {

private ElementAwareListEntry<T> nextEntry = first;

@Override
public boolean hasNext() {
if (size == 0) {
return false;
}
return nextEntry != null;
}

@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T element = nextEntry.getElement();
nextEntry = nextEntry.next;
return element;
}
if (sharedIterator == null || sharedIterator.nextEntry != null) {
// Create a new instance on first access, or when the previous one is still in use (not fully consumed).
sharedIterator = new ElementAwareListIterator();
} else {
// Otherwise the instance is reused, significantly reducing garbage collector pressure when on the hot path.
sharedIterator.nextEntry = first;
}
return sharedIterator;
}

};
/**
* In order for iterator sharing to work properly,
* each iterator must be fully consumed.
* For iterators which are not fully consumed,
* this method can be used to free the iterator to be used by another iteration operation.
* This technically breaks the abstraction of this class,
* but the measured benefit of this change is +5% to 10% of score calculation speed
* on account of not creating gigabytes of iterators per minute.
*/
public void prematurelyTerminateIterator() {
sharedIterator.nextEntry = null;
}

@Override
Expand All @@ -114,4 +168,27 @@ public String toString() {
}
}

private final class ElementAwareListIterator implements Iterator<T> {

private ElementAwareListEntry<T> nextEntry = first;

@Override
public boolean hasNext() {
if (size == 0) {
return false;
}
return nextEntry != null;
}

@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
T element = nextEntry.getElement();
nextEntry = nextEntry.next;
return element;
}

}
}

0 comments on commit 92ff8fa

Please sign in to comment.