-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #715 from seadowg/repeated-expr-2
Use lazy indexing repeated expression caching to optimize predicates
- Loading branch information
Showing
17 changed files
with
1,037 additions
and
153 deletions.
There are no files selected for viewing
99 changes: 99 additions & 0 deletions
99
src/main/java/org/javarosa/core/model/CompareChildToAbsoluteExpression.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,99 @@ | ||
package org.javarosa.core.model; | ||
|
||
import kotlin.Pair; | ||
import org.javarosa.core.model.condition.EvaluationContext; | ||
import org.javarosa.core.model.instance.DataInstance; | ||
import org.javarosa.core.model.instance.TreeReference; | ||
import org.javarosa.xpath.expr.XPathCmpExpr; | ||
import org.javarosa.xpath.expr.XPathEqExpr; | ||
import org.javarosa.xpath.expr.XPathExpression; | ||
import org.javarosa.xpath.expr.XPathNumericLiteral; | ||
import org.javarosa.xpath.expr.XPathPathExpr; | ||
import org.javarosa.xpath.expr.XPathStringLiteral; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.Arrays; | ||
import java.util.LinkedList; | ||
import java.util.Queue; | ||
|
||
public class CompareChildToAbsoluteExpression { | ||
|
||
private final XPathPathExpr relativeSide; | ||
private final XPathExpression absoluteSide; | ||
private final XPathExpression original; | ||
|
||
public CompareChildToAbsoluteExpression(XPathPathExpr relativeSide, XPathExpression absoluteSide, XPathExpression original) { | ||
this.relativeSide = relativeSide; | ||
this.absoluteSide = absoluteSide; | ||
this.original = original; | ||
} | ||
|
||
public Object evalRelative(DataInstance sourceInstance, EvaluationContext evaluationContext, TreeReference child, int childIndex) { | ||
EvaluationContext rescopedContext = evaluationContext.rescope(child, childIndex); | ||
return getRelativeSide().eval(sourceInstance, rescopedContext).unpack(); | ||
} | ||
|
||
public Object evalAbsolute(DataInstance sourceInstance, EvaluationContext evaluationContext) { | ||
if (absoluteSide instanceof XPathPathExpr) { | ||
return ((XPathPathExpr) getAbsoluteSide()).eval(sourceInstance, evaluationContext).unpack(); | ||
} else { | ||
return absoluteSide.eval(sourceInstance, evaluationContext); | ||
} | ||
} | ||
|
||
public XPathPathExpr getRelativeSide() { | ||
return relativeSide; | ||
} | ||
|
||
public XPathExpression getAbsoluteSide() { | ||
return absoluteSide; | ||
} | ||
|
||
public XPathExpression getOriginal() { | ||
return original; | ||
} | ||
|
||
@Nullable | ||
public static CompareChildToAbsoluteExpression parse(XPathExpression expression) { | ||
XPathExpression a = null; | ||
XPathExpression b = null; | ||
|
||
if (expression instanceof XPathCmpExpr) { | ||
a = ((XPathCmpExpr) expression).a; | ||
b = ((XPathCmpExpr) expression).b; | ||
} else if (expression instanceof XPathEqExpr) { | ||
a = ((XPathEqExpr) expression).a; | ||
b = ((XPathEqExpr) expression).b; | ||
} | ||
|
||
Pair<XPathPathExpr, XPathExpression> relativeAndAbsolute = getRelativeAndAbsolute(a, b); | ||
if (relativeAndAbsolute != null) { | ||
return new CompareChildToAbsoluteExpression(relativeAndAbsolute.getFirst(), relativeAndAbsolute.getSecond(), expression); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
private static Pair<XPathPathExpr, XPathExpression> getRelativeAndAbsolute(XPathExpression a, XPathExpression b) { | ||
XPathPathExpr relative = null; | ||
XPathExpression absolute = null; | ||
|
||
Queue<XPathExpression> subExpressions = new LinkedList<>(Arrays.asList(a, b)); | ||
while (!subExpressions.isEmpty()) { | ||
XPathExpression subExpression = subExpressions.poll(); | ||
if (subExpression instanceof XPathPathExpr && ((XPathPathExpr) subExpression).init_context == XPathPathExpr.INIT_CONTEXT_RELATIVE) | ||
relative = (XPathPathExpr) subExpression; | ||
else if (subExpression instanceof XPathPathExpr && ((XPathPathExpr) subExpression).init_context == XPathPathExpr.INIT_CONTEXT_ROOT) { | ||
absolute = subExpression; | ||
} else if (subExpression instanceof XPathNumericLiteral || subExpression instanceof XPathStringLiteral) { | ||
absolute = subExpression; | ||
} | ||
} | ||
|
||
if (relative != null && absolute != null) { | ||
return new Pair<>(relative, absolute); | ||
} else { | ||
return null; | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/main/java/org/javarosa/core/model/CompareChildToAbsoluteExpressionFilter.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,49 @@ | ||
package org.javarosa.core.model; | ||
|
||
import org.javarosa.core.model.condition.EvaluationContext; | ||
import org.javarosa.core.model.condition.PredicateFilter; | ||
import org.javarosa.core.model.instance.DataInstance; | ||
import org.javarosa.core.model.instance.TreeReference; | ||
import org.javarosa.xpath.expr.XPathCmpExpr; | ||
import org.javarosa.xpath.expr.XPathEqExpr; | ||
import org.javarosa.xpath.expr.XPathExpression; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Caches down stream evaluations (in the {@link PredicateFilter} chain) for supported expressions - currently just | ||
* {@link XPathCmpExpr} and {@link XPathEqExpr}. Repeated evaluations are fetched in O(1) time. | ||
*/ | ||
public class CompareChildToAbsoluteExpressionFilter implements PredicateFilter { | ||
|
||
private final Map<String, List<TreeReference>> cachedEvaluations = new HashMap<>(); | ||
|
||
@NotNull | ||
@Override | ||
public List<TreeReference> filter(@NotNull DataInstance sourceInstance, @NotNull TreeReference nodeSet, @NotNull XPathExpression predicate, @NotNull List<TreeReference> children, @NotNull EvaluationContext evaluationContext, @NotNull Supplier<List<TreeReference>> next) { | ||
if (sourceInstance.getInstanceId() == null) { | ||
return next.get(); | ||
} | ||
|
||
CompareChildToAbsoluteExpression candidate = CompareChildToAbsoluteExpression.parse(predicate); | ||
if (candidate != null) { | ||
Object absoluteValue = candidate.evalAbsolute(sourceInstance, evaluationContext); | ||
String key = nodeSet.toString() + predicate + candidate.getRelativeSide() + absoluteValue.toString(); | ||
|
||
if (cachedEvaluations.containsKey(key)) { | ||
return cachedEvaluations.get(key); | ||
} else { | ||
List<TreeReference> filtered = next.get(); | ||
cachedEvaluations.put(key, filtered); | ||
return filtered; | ||
} | ||
} else { | ||
return next.get(); | ||
} | ||
} | ||
|
||
} |
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
45 changes: 0 additions & 45 deletions
45
src/main/java/org/javarosa/core/model/IdempotentInMemPredicateCache.java
This file was deleted.
Oops, something went wrong.
48 changes: 48 additions & 0 deletions
48
src/main/java/org/javarosa/core/model/IdempotentPredicateCache.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,48 @@ | ||
package org.javarosa.core.model; | ||
|
||
import org.javarosa.core.model.condition.EvaluationContext; | ||
import org.javarosa.core.model.condition.PredicateFilter; | ||
import org.javarosa.core.model.instance.DataInstance; | ||
import org.javarosa.core.model.instance.TreeReference; | ||
import org.javarosa.xpath.expr.XPathExpression; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Caches down stream evaluations (in the {@link PredicateFilter} chain) for "idempotent" (with respect to current form | ||
* state) predicates. Can only be used for static instances or in cases where form state won't change - will cause | ||
* clashes otherwise. Repeated evaluations are fetched in O(1) time. | ||
*/ | ||
public class IdempotentPredicateCache implements PredicateFilter { | ||
|
||
private final Map<String, List<TreeReference>> cachedEvaluations = new HashMap<>(); | ||
|
||
@NotNull | ||
@Override | ||
public List<TreeReference> filter(@NotNull DataInstance sourceInstance, @NotNull TreeReference nodeSet, @NotNull XPathExpression predicate, @NotNull List<TreeReference> children, @NotNull EvaluationContext evaluationContext, @NotNull Supplier<List<TreeReference>> next) { | ||
String key = getKey(nodeSet, predicate); | ||
|
||
if (cachedEvaluations.containsKey(key)) { | ||
return cachedEvaluations.get(key); | ||
} else { | ||
List<TreeReference> filtered = next.get(); | ||
if (isCacheable(predicate)) { | ||
cachedEvaluations.put(key, filtered); | ||
} | ||
|
||
return filtered; | ||
} | ||
} | ||
|
||
private String getKey(TreeReference nodeSet, XPathExpression predicate) { | ||
return nodeSet.toString() + predicate.toString(); | ||
} | ||
|
||
private boolean isCacheable(XPathExpression predicate) { | ||
return predicate.isIdempotent(); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
src/main/java/org/javarosa/core/model/IndexPredicateFilter.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,94 @@ | ||
package org.javarosa.core.model; | ||
|
||
import org.javarosa.core.model.condition.EvaluationContext; | ||
import org.javarosa.core.model.condition.PredicateFilter; | ||
import org.javarosa.core.model.instance.DataInstance; | ||
import org.javarosa.core.model.instance.TreeReference; | ||
import org.javarosa.measure.Measure; | ||
import org.javarosa.xpath.expr.XPathEqExpr; | ||
import org.javarosa.xpath.expr.XPathExpression; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.function.Supplier; | ||
|
||
import static java.util.Collections.emptyList; | ||
|
||
/** | ||
* Uses a (lazily constructed) index to evaluate a predicate for supported expressions - currently just | ||
* {@link XPathEqExpr} where one side is relative to the instance child being filtered. Evaluations are fetched in | ||
* O(1) time with O(n) expression evaluations only being required the first time a relative side is evaluated. | ||
*/ | ||
public class IndexPredicateFilter implements PredicateFilter { | ||
|
||
private final InMemTreeReferenceIndex index = new InMemTreeReferenceIndex(); | ||
|
||
@NotNull | ||
@Override | ||
public List<TreeReference> filter(@NotNull DataInstance sourceInstance, @NotNull TreeReference nodeSet, @NotNull XPathExpression predicate, @NotNull List<TreeReference> children, @NotNull EvaluationContext evaluationContext, @NotNull Supplier<List<TreeReference>> next) { | ||
if (sourceInstance.getInstanceId() == null || !(predicate instanceof XPathEqExpr)) { | ||
return next.get(); | ||
} | ||
|
||
CompareChildToAbsoluteExpression candidate = CompareChildToAbsoluteExpression.parse(predicate); | ||
if (candidate != null) { | ||
XPathEqExpr original = (XPathEqExpr) candidate.getOriginal(); | ||
if (original.isEqual()) { | ||
String section = sourceInstance.getInstanceId() + nodeSet + candidate.getRelativeSide().toString(); | ||
if (!index.contains(section)) { | ||
buildIndex(sourceInstance, candidate, children, evaluationContext, section); | ||
} | ||
|
||
Object absoluteValue = candidate.evalAbsolute(sourceInstance, evaluationContext); | ||
return index.lookup(section, absoluteValue.toString()); | ||
} else { | ||
return next.get(); | ||
} | ||
} else { | ||
return next.get(); | ||
} | ||
} | ||
|
||
private void buildIndex(DataInstance sourceInstance, CompareChildToAbsoluteExpression predicate, List<TreeReference> children, EvaluationContext evaluationContext, String section) { | ||
for (int i = 0; i < children.size(); i++) { | ||
TreeReference child = children.get(i); | ||
|
||
Measure.log("IndexEvaluation"); | ||
String relativeValue = predicate.evalRelative(sourceInstance, evaluationContext, child, i).toString(); | ||
index.add(section, relativeValue, child); | ||
} | ||
} | ||
|
||
private static class InMemTreeReferenceIndex { | ||
|
||
private final Map<String, Map<String, List<TreeReference>>> map = new HashMap<>(); | ||
|
||
public boolean contains(String section) { | ||
return map.containsKey(section); | ||
} | ||
|
||
public void add(String section, String key, TreeReference reference) { | ||
if (!map.containsKey(section)) { | ||
map.put(section, new HashMap<>()); | ||
} | ||
|
||
Map<String, List<TreeReference>> sectionMap = map.get(section); | ||
if (!sectionMap.containsKey(key)) { | ||
sectionMap.put(key, new ArrayList<>()); | ||
} | ||
|
||
sectionMap.get(key).add(reference); | ||
} | ||
|
||
public List<TreeReference> lookup(String section, String key) { | ||
if (map.containsKey(section) && map.get(section).containsKey(key)) { | ||
return map.get(section).get(key); | ||
} else { | ||
return emptyList(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.