3838import org .apache .tinkerpop .gremlin .process .traversal .step .map .OrderGlobalStep ;
3939import org .apache .tinkerpop .gremlin .process .traversal .strategy .AbstractTraversalStrategy ;
4040import org .apache .tinkerpop .gremlin .process .traversal .util .TraversalHelper ;
41+ import org .apache .tinkerpop .gremlin .structure .Graph ;
42+ import org .javatuples .Pair ;
4143
4244import java .util .Collections ;
45+ import java .util .HashMap ;
46+ import java .util .HashSet ;
4347import java .util .List ;
48+ import java .util .Map ;
4449import 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+ }
0 commit comments