-
Notifications
You must be signed in to change notification settings - Fork 104
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
Reduce unnecessary memory allocations in Cascades #2464
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
31262ba
Memoize children calculation where applicable.
hatyo 7ef3549
TreeLike<> now extends Iterable<>
hatyo 44daf6d
Fix checkstyle violation.
hatyo cd928a8
Change order of pattern matches to avoid infinite recursion.
hatyo 3c4777b
Refactoring, add height() to TreeLike<> to improve Stack allocation.
hatyo bc017e0
Fix spotbugs violations.
hatyo 9d65c0a
Address comments.
hatyo b1837e9
refactoring.
hatyo af32934
Merge remote-tracking branch 'upstream/main' into reduce_streams_tree…
hatyo 83aa17f
Further refactoring.
hatyo 0a0d74f
Update release notes.
hatyo 4b62403
Revert making LeafValue and ValueWithChild abstract classes instead o…
hatyo 8ab73b6
Address remaining comments.
hatyo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
...ore/src/main/java/com/apple/foundationdb/record/query/plan/cascades/PreOrderIterator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* PreOrderIterator.java | ||
* | ||
* This source file is part of the FoundationDB open source project | ||
* | ||
* Copyright 2015-2024 Apple Inc. and the FoundationDB project authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.apple.foundationdb.record.query.plan.cascades; | ||
|
||
import com.google.common.base.Verify; | ||
import com.google.common.collect.Iterables; | ||
import org.apache.commons.lang3.tuple.MutablePair; | ||
import org.apache.commons.lang3.tuple.Pair; | ||
|
||
import javax.annotation.Nonnull; | ||
import javax.annotation.concurrent.NotThreadSafe; | ||
import java.util.ArrayDeque; | ||
import java.util.Deque; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.NoSuchElementException; | ||
|
||
/** | ||
* An iterator that accesses all elements of a {@link TreeLike} object in pre-order fashion. | ||
* It attempts to reduce the number of memory allocations while still being performant. It does so | ||
* by implementing a mix of level-based traversal and pre-order traversal, where a {@link Deque} is used | ||
* (as a stack) to enable depth first search, however instead of maintaining individual {@link TreeLike} node in | ||
* each stack frame the entire list of node's children is stored instead in addition to an index pointing out | ||
* to the current element that is returned by the stream. | ||
* | ||
* @param <T> The type of the iterator element. | ||
*/ | ||
@NotThreadSafe | ||
public final class PreOrderIterator<T extends TreeLike<T>> implements Iterator<T> { | ||
|
||
@Nonnull | ||
private final Deque<Pair<Iterable<? extends T>, Integer>> stack; | ||
|
||
private static final int INITIAL_POSITION = -1; | ||
|
||
private PreOrderIterator(@Nonnull final T traversable) { | ||
// initialize the stack with the {@link TreeLike}'s depth as capacity to avoid resizing. | ||
stack = new ArrayDeque<>(traversable.height()); | ||
// this is the only list allocation done to put the root in the stack. | ||
// all the remaining lists added to the stack are references to children | ||
// lists (copy by reference). | ||
stack.push(MutablePair.of(List.of(traversable.getThis()), INITIAL_POSITION)); | ||
} | ||
|
||
@Override | ||
public boolean hasNext() { | ||
while (true) { | ||
if (stack.isEmpty()) { | ||
return false; | ||
} | ||
final var top = Verify.verifyNotNull(stack.peekLast()); | ||
final var currentLevelIndex = top.getRight(); | ||
final var currentLevelItemsCount = Iterables.size(top.getLeft()); | ||
if (currentLevelIndex + 1 < currentLevelItemsCount) { | ||
return true; | ||
} else { | ||
// pop the stack, and continue doing so, until we either find a next element | ||
// by looking into previous levels in reverse (stack) order, progressively | ||
// do so until either an item is found, or the stack becomes empty, and no item | ||
// is to be found meaning that the traversal is complete. | ||
stack.removeLast(); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public T next() { | ||
if (!hasNext()) { | ||
throw new NoSuchElementException("no more elements"); | ||
} | ||
final var top = Verify.verifyNotNull(stack.peekLast()); | ||
final var currentIndexPosition = top.getRight(); | ||
final var currentLevelItems = top.getLeft(); | ||
final var nextItemIndex = currentIndexPosition + 1; | ||
|
||
// mark the next item as the current one in this stack frame, note that | ||
// the position must be valid because hasNext() is called first, and hasNext() | ||
// makes sure that, if there is a next element, it modifies the stack index such | ||
// that incrementing it would lead to finding that next element correctly. | ||
final var result = Iterables.get(currentLevelItems, nextItemIndex); | ||
top.setValue(nextItemIndex); | ||
final var resultChildren = result.getChildren(); | ||
|
||
// descend immediately to the children (if any) so to conform to pre-order DFS semantics. | ||
if (!Iterables.isEmpty(resultChildren)) { | ||
stack.add(MutablePair.of(resultChildren, INITIAL_POSITION)); | ||
} | ||
return result; | ||
} | ||
|
||
@Nonnull | ||
public static <T extends TreeLike<T>> PreOrderIterator<T> over(@Nonnull final T traversable) { | ||
return new PreOrderIterator<>(traversable); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,8 @@ public abstract class AbstractQueryPredicate implements QueryPredicate { | |
|
||
private final Supplier<Integer> semanticHashCodeSupplier; | ||
|
||
private final Supplier<Integer> heightSupplier; | ||
|
||
@SuppressWarnings("unused") | ||
protected AbstractQueryPredicate(@Nonnull final PlanSerializationContext serializationContext, | ||
@Nonnull final PAbstractQueryPredicate abstractQueryPredicateProto) { | ||
|
@@ -56,6 +58,7 @@ protected AbstractQueryPredicate(final boolean isAtomic) { | |
this.isAtomic = isAtomic; | ||
this.correlatedToSupplier = Suppliers.memoize(this::computeCorrelatedTo); | ||
this.semanticHashCodeSupplier = Suppliers.memoize(this::computeSemanticHashCode); | ||
this.heightSupplier = Suppliers.memoize(QueryPredicate.super::height); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that. |
||
} | ||
|
||
@Nonnull | ||
|
@@ -88,6 +91,11 @@ public int semanticHashCode() { | |
|
||
protected abstract int computeSemanticHashCode(); | ||
|
||
@Override | ||
public int height() { | ||
return heightSupplier.get(); | ||
} | ||
|
||
@Override | ||
public int hashCodeWithoutChildren() { | ||
return Objects.hash(isAtomic); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you not use
Pair
but define your own class? This is due to us removing commons stuff in the future.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but I don't want to do it in this PR to limit its scope, there are 80+ locations where we still use
Pair
currently, we can do this in a separate cleanup PR. (I am also usingMutablePair
by the way, but we can also replace that in a cleanup PR). #2484