Skip to content

Commit 665fa1b

Browse files
committed
Merge branch 'TINKERPOP-2919' into 3.5-dev
2 parents a0066d0 + c0e3c4f commit 665fa1b

File tree

4 files changed

+123
-26
lines changed

4 files changed

+123
-26
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
3232
* Changed `with()` configuration for `ARGS_BATCH_SIZE` and `ARGS_EVAL_TIMEOUT` to be more forgiving on the type of `Number` used for the value.
3333
* Changed `gremlin-console` to add imports via an ImportCustomizer to reduce time spent resolving imports.
3434
* Bumped to Groovy 2.5.21.
35+
* Refactored `FilterRankingStrategy` to improve performance for deeply nested traversals.
3536
* Added `AuthInfoProvider` interface and `NewDynamicAuth()` to gremlin-go for dynamic authentication support.
3637
* Bumped to `snakeyaml` 2.0 to fix security vulnerability.
3738
* Bumped to Apache `commons-configuration` 2.9.0 to fix security vulnerability.

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/FilterRankingStrategy.java

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,18 @@
3838
import org.apache.tinkerpop.gremlin.process.traversal.step.map.OrderGlobalStep;
3939
import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
4040
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
41+
import org.apache.tinkerpop.gremlin.structure.Graph;
42+
import org.javatuples.Pair;
4143

4244
import java.util.Collections;
45+
import java.util.HashMap;
46+
import java.util.HashSet;
4347
import java.util.List;
48+
import java.util.Map;
4449
import java.util.Set;
50+
import java.util.concurrent.atomic.AtomicInteger;
51+
import java.util.concurrent.atomic.AtomicLong;
52+
import java.util.stream.Collectors;
4553

4654
/**
4755
* {@code FilterRankingStrategy} reorders filter- and order-steps according to their rank. Step ranks are defined within
@@ -61,33 +69,63 @@ public final class FilterRankingStrategy extends AbstractTraversalStrategy<Trave
6169
private static final FilterRankingStrategy INSTANCE = new FilterRankingStrategy();
6270
private static final Set<Class<? extends OptimizationStrategy>> PRIORS = Collections.singleton(IdentityRemovalStrategy.class);
6371

64-
private FilterRankingStrategy() {
65-
}
72+
private FilterRankingStrategy() { }
6673

6774
@Override
6875
public void apply(final Traversal.Admin<?, ?> traversal) {
69-
boolean modified = true;
70-
while (modified) {
71-
modified = false;
72-
final List<Step> steps = traversal.getSteps();
73-
for (int i = 0; i < steps.size() - 1; i++) {
74-
final Step<?, ?> step = steps.get(i);
75-
final Step<?, ?> nextStep = step.getNextStep();
76-
if (!usesLabels(nextStep, step.getLabels())) {
77-
final int nextRank = getStepRank(nextStep);
78-
if (nextRank != 0) {
79-
if (!step.getLabels().isEmpty()) {
80-
TraversalHelper.copyLabels(step, nextStep, true);
81-
modified = true;
82-
}
83-
if (getStepRank(step) > nextRank) {
84-
traversal.removeStep(nextStep);
85-
traversal.addStep(i, nextStep);
86-
modified = true;
76+
// this strategy is only applied to the root (recursively) because it uses a tiny cache which will drastically
77+
// speed up the ranking function in the event of encountering traversals with significant depth and branching.
78+
// if we let normal strategy application apply then the cache will reset since this strategy and the traversal
79+
// does not hold any state about that cache. tried using the marker pattern used in other strategies but that
80+
// didn't work so well.
81+
if (traversal.isRoot()) {
82+
// TraversalParent steps require a costly function to calculate if labels are in use in their child
83+
// traversals. This little cache keeps the effective data of that function which is if there is a
84+
// lambda in the children and the set of scope keys. note that the lambda sorta trumps the labels in
85+
// that if there is a lambda there's no real point to doing any sort of eval of the labels.
86+
final Map<TraversalParent, Pair<Boolean, Set<String>>> traversalParentCache = new HashMap<>();
87+
final Map<Step, Integer> stepRanking = new HashMap<>();
88+
89+
// build up the little cache
90+
final Map<TraversalParent, List<Step<?,?>>> m =
91+
TraversalHelper.getStepsOfAssignableClassRecursively(traversal, Scoping.class, LambdaHolder.class).stream().
92+
collect(Collectors.groupingBy(step -> ((Step) step).getTraversal().getParent()));
93+
m.forEach((k, v) -> {
94+
final boolean hasLambda = v.stream().anyMatch(s -> s instanceof LambdaHolder);
95+
if (hasLambda) {
96+
traversalParentCache.put(k, Pair.with(true, Collections.emptySet()));
97+
} else {
98+
traversalParentCache.put(k, Pair.with(false, v.stream().filter(s -> s instanceof Scoping).
99+
flatMap(s -> ((Scoping) s).getScopeKeys().stream()).collect(Collectors.toSet())));
100+
}
101+
});
102+
103+
TraversalHelper.applyTraversalRecursively(t -> {
104+
boolean modified = true;
105+
while (modified) {
106+
modified = false;
107+
final List<Step> steps = t.getSteps();
108+
for (int i = 0; i < steps.size() - 1; i++) {
109+
final Step<?, ?> step = steps.get(i);
110+
final Set<String> labels = step.getLabels();
111+
final Step<?, ?> nextStep = step.getNextStep();
112+
if (!usesLabels(nextStep, labels, traversalParentCache)) {
113+
final int nextRank = stepRanking.computeIfAbsent(nextStep, FilterRankingStrategy::getStepRank);
114+
if (nextRank != 0) {
115+
if (!step.getLabels().isEmpty()) {
116+
TraversalHelper.copyLabels(step, nextStep, true);
117+
modified = true;
118+
}
119+
if (stepRanking.computeIfAbsent(step, FilterRankingStrategy::getStepRank) > nextRank) {
120+
t.removeStep(nextStep);
121+
t.addStep(i, nextStep);
122+
modified = true;
123+
}
124+
}
87125
}
88126
}
89127
}
90-
}
128+
}, traversal);
91129
}
92130
}
93131

@@ -144,19 +182,29 @@ private static int getMaxStepRank(final TraversalParent parent, final int startR
144182
return maxStepRank;
145183
}
146184

147-
private static boolean usesLabels(final Step<?, ?> step, final Set<String> labels) {
185+
private static boolean usesLabels(final Step<?, ?> step, final Set<String> labels,
186+
final Map<TraversalParent, Pair<Boolean, Set<String>>> traversalParentCache) {
148187
if (step instanceof LambdaHolder)
149188
return true;
150-
if (step instanceof Scoping) {
189+
if (step instanceof Scoping && !labels.isEmpty()) {
151190
final Set<String> scopes = ((Scoping) step).getScopeKeys();
152191
for (final String label : labels) {
153192
if (scopes.contains(label))
154193
return true;
155194
}
156195
}
196+
157197
if (step instanceof TraversalParent) {
158-
if (TraversalHelper.anyStepRecursively(s -> usesLabels(s, labels), (TraversalParent) step))
198+
// when the step is a parent and is not in the cache it means it's not gonna be using labels
199+
if (!traversalParentCache.containsKey(step)) return false;
200+
201+
// if we do have a pair then check the boolean first, as it instantly means labels are in use
202+
// (or i guess can't be detected because it's a lambda)
203+
final Pair<Boolean, Set<String>> p = traversalParentCache.get(step);
204+
if (p.getValue0())
159205
return true;
206+
else
207+
return p.getValue1().stream().anyMatch(labels::contains);
160208
}
161209
return false;
162210
}
@@ -169,4 +217,4 @@ public Set<Class<? extends OptimizationStrategy>> applyPrior() {
169217
public static FilterRankingStrategy instance() {
170218
return INSTANCE;
171219
}
172-
}
220+
}

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelper.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,29 @@ public static <S> List<S> getStepsOfAssignableClassRecursively(final Scope scope
290290
return list;
291291
}
292292

293+
/**
294+
* Get steps of the specified classes throughout the traversal.
295+
*/
296+
public static List<Step<?,?>> getStepsOfAssignableClassRecursively(final Traversal.Admin<?, ?> traversal, final Class<?>... stepClasses) {
297+
final List<Step<?,?>> list = new ArrayList<>();
298+
for (final Step<?, ?> step : traversal.getSteps()) {
299+
for (Class<?> stepClass : stepClasses) {
300+
if (stepClass.isAssignableFrom(step.getClass()))
301+
list.add(step);
302+
}
303+
304+
if (step instanceof TraversalParent) {
305+
for (final Traversal.Admin<?, ?> localChild : ((TraversalParent) step).getLocalChildren()) {
306+
list.addAll(TraversalHelper.getStepsOfAssignableClassRecursively(localChild, stepClasses));
307+
}
308+
for (final Traversal.Admin<?, ?> globalChild : ((TraversalParent) step).getGlobalChildren()) {
309+
list.addAll(TraversalHelper.getStepsOfAssignableClassRecursively(globalChild, stepClasses));
310+
}
311+
}
312+
}
313+
return list;
314+
}
315+
293316
public static boolean isGlobalChild(Traversal.Admin<?, ?> traversal) {
294317
while (!(traversal.isRoot())) {
295318
if (traversal.getParent().getLocalChildren().contains(traversal))

gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/TraversalHelperTest.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTraversalStep;
3535
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.NotStep;
3636
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FlatMapStep;
37+
import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep;
3738
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep;
3839
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TraversalFlatMapStep;
3940
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TraversalMapStep;
@@ -419,6 +420,30 @@ public void shouldGetLabels() {
419420
@Test
420421
public void shouldFindStepsRecursively() {
421422
final Traversal<?,?> traversal = __.V().repeat(__.out().simplePath());
422-
assertTrue(TraversalHelper.anyStepRecursively(s -> s instanceof PathFilterStep, traversal.asAdmin()));
423+
assertThat(TraversalHelper.anyStepRecursively(s -> s instanceof PathFilterStep, traversal.asAdmin()), is(true));
424+
}
425+
426+
@Test
427+
public void shouldGetStepsOfAssignableClassRecursivelyNoTypes() {
428+
final Traversal.Admin<?,?> traversal = __.V().repeat(__.out()).project("x").by(out().in().fold()).asAdmin();
429+
final List<Step<?,?>> steps = TraversalHelper.getStepsOfAssignableClassRecursively(traversal);
430+
assertEquals(0, steps.size());
431+
}
432+
433+
@Test
434+
public void shouldGetStepsOfAssignableClassRecursivelyOneType() {
435+
final Traversal.Admin<?,?> traversal = __.V().repeat(__.out()).project("x").by(out().in().fold()).asAdmin();
436+
final List<Step<?,?>> steps = TraversalHelper.getStepsOfAssignableClassRecursively(traversal, VertexStep.class);
437+
assertEquals(3, steps.size());
438+
assertThat(steps.stream().allMatch(s -> s instanceof VertexStep), is(true));
439+
}
440+
441+
@Test
442+
public void shouldGetStepsOfAssignableClassRecursivelyMultipleTypes() {
443+
final Traversal.Admin<?,?> traversal = __.V().repeat(__.out()).project("x").by(out().in().fold()).asAdmin();
444+
final List<Step<?,?>> steps = TraversalHelper.getStepsOfAssignableClassRecursively(traversal, VertexStep.class, FoldStep.class);
445+
assertEquals(4, steps.size());
446+
assertEquals(3, steps.stream().filter(s -> s instanceof VertexStep).count());
447+
assertEquals(1, steps.stream().filter(s -> s instanceof FoldStep).count());
423448
}
424449
}

0 commit comments

Comments
 (0)