38
38
import org .apache .tinkerpop .gremlin .process .traversal .step .map .OrderGlobalStep ;
39
39
import org .apache .tinkerpop .gremlin .process .traversal .strategy .AbstractTraversalStrategy ;
40
40
import org .apache .tinkerpop .gremlin .process .traversal .util .TraversalHelper ;
41
+ import org .apache .tinkerpop .gremlin .structure .Graph ;
42
+ import org .javatuples .Pair ;
41
43
42
44
import java .util .Collections ;
45
+ import java .util .HashMap ;
46
+ import java .util .HashSet ;
43
47
import java .util .List ;
48
+ import java .util .Map ;
44
49
import java .util .Set ;
50
+ import java .util .concurrent .atomic .AtomicInteger ;
51
+ import java .util .concurrent .atomic .AtomicLong ;
52
+ import java .util .stream .Collectors ;
45
53
46
54
/**
47
55
* {@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
61
69
private static final FilterRankingStrategy INSTANCE = new FilterRankingStrategy ();
62
70
private static final Set <Class <? extends OptimizationStrategy >> PRIORS = Collections .singleton (IdentityRemovalStrategy .class );
63
71
64
- private FilterRankingStrategy () {
65
- }
72
+ private FilterRankingStrategy () { }
66
73
67
74
@ Override
68
75
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
+ }
87
125
}
88
126
}
89
127
}
90
- }
128
+ }, traversal );
91
129
}
92
130
}
93
131
@@ -144,19 +182,29 @@ private static int getMaxStepRank(final TraversalParent parent, final int startR
144
182
return maxStepRank ;
145
183
}
146
184
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 ) {
148
187
if (step instanceof LambdaHolder )
149
188
return true ;
150
- if (step instanceof Scoping ) {
189
+ if (step instanceof Scoping && ! labels . isEmpty () ) {
151
190
final Set <String > scopes = ((Scoping ) step ).getScopeKeys ();
152
191
for (final String label : labels ) {
153
192
if (scopes .contains (label ))
154
193
return true ;
155
194
}
156
195
}
196
+
157
197
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 ())
159
205
return true ;
206
+ else
207
+ return p .getValue1 ().stream ().anyMatch (labels ::contains );
160
208
}
161
209
return false ;
162
210
}
@@ -169,4 +217,4 @@ public Set<Class<? extends OptimizationStrategy>> applyPrior() {
169
217
public static FilterRankingStrategy instance () {
170
218
return INSTANCE ;
171
219
}
172
- }
220
+ }
0 commit comments