Skip to content

Commit

Permalink
Qute: bunch of performance improvements
Browse files Browse the repository at this point in the history
- optimize section nodes (collapse text nodes)
- reduce the overhead for CompletableFuture.all()
- cache value resolver for each part of an expression
  • Loading branch information
mkouba committed Sep 21, 2020
1 parent c197fda commit 9a1bed6
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
Expand Down Expand Up @@ -30,13 +31,19 @@ public static EvaluatedParams evaluate(EvalContext context) {
} else if (params.size() == 1) {
return new EvaluatedParams(context.evaluate(params.get(0)));
}
CompletableFuture<?>[] results = new CompletableFuture<?>[params.size()];
CompletableFuture<?>[] allResults = new CompletableFuture<?>[params.size()];
List<CompletableFuture<?>> results = new LinkedList<>();
int i = 0;
Iterator<Expression> it = params.iterator();
while (it.hasNext()) {
results[i++] = context.evaluate(it.next()).toCompletableFuture();
Expression expression = it.next();
CompletableFuture<Object> result = context.evaluate(expression).toCompletableFuture();
allResults[i++] = result;
if (!expression.isLiteral()) {
results.add(result);
}
}
return new EvaluatedParams(CompletableFuture.allOf(results), results);
return new EvaluatedParams(CompletableFuture.allOf(results.toArray(Futures.EMPTY_RESULTS)), allResults);
}

public static EvaluatedParams evaluateMessageKey(EvalContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.qute;

import io.quarkus.qute.Expression.Part;
import io.quarkus.qute.ExpressionImpl.PartImpl;
import io.quarkus.qute.Results.Result;
import java.util.Collections;
import java.util.Iterator;
Expand Down Expand Up @@ -71,22 +72,39 @@ private CompletionStage<Object> resolveReference(boolean tryParent, Object ref,
EvalContextImpl evalContext = new EvalContextImpl(tryParent, ref, part, resolutionContext);
if (!parts.hasNext()) {
// The last part - no need to compose
return resolve(evalContext, resolvers.iterator());
return resolve(evalContext, resolvers.iterator(), true);
} else {
// Next part - no need to try the parent context/outer scope
return resolve(evalContext, resolvers.iterator())
return resolve(evalContext, resolvers.iterator(), true)
.thenCompose(r -> resolveReference(false, r, parts, resolutionContext));
}
}

private CompletionStage<Object> resolve(EvalContextImpl evalContext, Iterator<ValueResolver> resolvers) {
private CompletionStage<Object> resolve(EvalContextImpl evalContext, Iterator<ValueResolver> resolvers,
boolean tryCachedResolver) {

if (tryCachedResolver) {
// Try the cached resolver first
ValueResolver cachedResolver = ((PartImpl) evalContext.part).cachedResolver;
if (cachedResolver != null && cachedResolver.appliesTo(evalContext)) {
return cachedResolver.resolve(evalContext).thenCompose(r -> {
if (Result.NOT_FOUND.equals(r)) {
return resolve(evalContext, resolvers, false);
} else {
return CompletableFuture.completedFuture(r);
}
});
}
}

if (!resolvers.hasNext()) {
ResolutionContext parent = evalContext.resolutionContext.getParent();
if (evalContext.tryParent && parent != null) {
// Continue with parent context
return resolve(
new EvalContextImpl(true, parent.getData(), evalContext.name, evalContext.params, parent),
this.resolvers.iterator());
new EvalContextImpl(true, parent.getData(), evalContext.name, evalContext.params, parent,
evalContext.part),
this.resolvers.iterator(), false);
}
LOGGER.tracef("Unable to resolve %s", evalContext);
return Results.NOT_FOUND;
Expand All @@ -95,14 +113,17 @@ private CompletionStage<Object> resolve(EvalContextImpl evalContext, Iterator<Va
if (resolver.appliesTo(evalContext)) {
return resolver.resolve(evalContext).thenCompose(r -> {
if (Result.NOT_FOUND.equals(r)) {
return resolve(evalContext, resolvers);
// Result not found - try the next resolver
return resolve(evalContext, resolvers, false);
} else {
// Cache the first resolver where a result is found
((PartImpl) evalContext.part).setCachedResolver(resolver);
return CompletableFuture.completedFuture(r);
}
});
} else {
// Try next resolver
return resolve(evalContext, resolvers);
// Try the next resolver
return resolve(evalContext, resolvers, false);
}
}

Expand All @@ -113,20 +134,22 @@ static class EvalContextImpl implements EvalContext {
final String name;
final List<Expression> params;
final ResolutionContext resolutionContext;
final Part part;

EvalContextImpl(boolean tryParent, Object base, Part part, ResolutionContext resolutionContext) {
this(tryParent, base, part.getName(),
part.isVirtualMethod() ? part.asVirtualMethod().getParameters() : Collections.emptyList(),
resolutionContext);
resolutionContext, part);
}

EvalContextImpl(boolean tryParent, Object base, String name, List<Expression> params,
ResolutionContext resolutionContext) {
ResolutionContext resolutionContext, Part part) {
this.tryParent = tryParent;
this.base = base;
this.resolutionContext = resolutionContext;
this.params = params;
this.name = name;
this.part = part;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ static class PartImpl implements Part {

protected final String name;
protected final String typeInfo;
protected volatile ValueResolver cachedResolver;

PartImpl(String name, String typeInfo) {
this.name = name;
Expand All @@ -222,6 +223,18 @@ public String getTypeInfo() {
return typeInfo;
}

void setCachedResolver(ValueResolver resolver) {
ValueResolver last = this.cachedResolver;
if (last != null) {
return;
}
synchronized (this) {
if (this.cachedResolver == null) {
this.cachedResolver = resolver;
}
}
}

@Override
public int hashCode() {
return Objects.hash(name, typeInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public Origin getOrigin() {
return origin;
}

@Override
public boolean isConstant() {
return expression.isLiteral();
}

Engine getEngine() {
return engine;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

public final class Futures {

@SuppressWarnings("unchecked")
static CompletableFuture<ResultNode>[] EMPTY_RESULTS = new CompletableFuture[0];

private Futures() {
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
package io.quarkus.qute;

import java.util.function.Consumer;
import java.util.concurrent.CompletionStage;

/**
* Template parameter declaration.
* <p>
* This node is only used when removing standalone lines.
*/
public class ParameterDeclarationNode extends TextNode {
public class ParameterDeclarationNode implements TemplateNode {

private final String value;
private final Origin origin;

public ParameterDeclarationNode(String value, Origin origin) {
super(value, origin);
this.value = value;
this.origin = origin;
}

@Override
public CompletionStage<ResultNode> resolve(ResolutionContext context) {
throw new UnsupportedOperationException();
}

public String getValue() {
return value;
}

@Override
public void process(Consumer<String> consumer) {
public Origin getOrigin() {
return origin;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import io.quarkus.qute.Expression.Part;
import io.quarkus.qute.Results.Result;
import io.quarkus.qute.SectionHelper.SectionResolutionContext;
import io.quarkus.qute.SectionHelperFactory.ParametersInfo;
import io.quarkus.qute.TemplateNode.Origin;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -135,8 +135,9 @@ Template parse(Reader reader, Optional<Variant> variant, String id, String gener
root.addBlock(part.build());
TemplateImpl template = new TemplateImpl(engine, root.build(), generatedId, variant);

Set<TemplateNode> nodesToRemove;
if (engine.removeStandaloneLines) {
Set<TemplateNode> nodesToRemove = new HashSet<>();
nodesToRemove = new HashSet<>();
List<List<TemplateNode>> lines = readLines(template.root);
for (List<TemplateNode> line : lines) {
if (isStandalone(line)) {
Expand All @@ -148,8 +149,10 @@ Template parse(Reader reader, Optional<Variant> variant, String id, String gener
}
}
}
template.root.removeNodes(nodesToRemove);
} else {
nodesToRemove = Collections.emptySet();
}
template.root.optimizeNodes(nodesToRemove);

LOGGER.tracef("Parsing finished in %s ms", System.currentTimeMillis() - start);
return template;
Expand Down Expand Up @@ -908,15 +911,21 @@ public void addParameter(String name, String type) {
currentScope.put(name, Expressions.TYPE_INFO_SEPARATOR + type + Expressions.TYPE_INFO_SEPARATOR);
}

private static final SectionHelper ROOT_SECTION_HELPER = new SectionHelper() {
@Override
public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
return context.execute();
}
};
private static final SectionHelperFactory<SectionHelper> ROOT_SECTION_HELPER_FACTORY = new SectionHelperFactory<SectionHelper>() {
@Override
public SectionHelper initialize(SectionInitContext context) {
return SectionResolutionContext::execute;
return ROOT_SECTION_HELPER;
}
};

private static final BlockNode BLOCK_NODE = new BlockNode();
private static final CommentNode COMMENT_NODE = new CommentNode();
static final CommentNode COMMENT_NODE = new CommentNode();

// A dummy node for section blocks, it's only used when removing standalone lines
private static class BlockNode implements TemplateNode {
Expand All @@ -934,16 +943,16 @@ public Origin getOrigin() {
}

// A dummy node for comments, it's only used when removing standalone lines
private static class CommentNode implements TemplateNode {
static class CommentNode implements TemplateNode {

@Override
public CompletionStage<ResultNode> resolve(ResolutionContext context) {
throw new IllegalStateException();
throw new UnsupportedOperationException();
}

@Override
public Origin getOrigin() {
throw new IllegalStateException();
throw new UnsupportedOperationException();
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class SectionBlock {
*/
List<TemplateNode> nodes;

SectionBlock(Origin origin, String id, String label, Map<String, String> parameters,
public SectionBlock(Origin origin, String id, String label, Map<String, String> parameters,
Map<String, Expression> expressions,
List<TemplateNode> nodes) {
this.origin = origin;
Expand Down Expand Up @@ -71,17 +71,52 @@ public String toString() {
return builder.toString();
}

void removeNodes(Set<TemplateNode> nodesToRemove) {
ImmutableList.Builder<TemplateNode> builder = ImmutableList.builder();
void optimizeNodes(Set<TemplateNode> nodesToRemove) {
List<TemplateNode> effectiveNodes = new ArrayList<>();
for (TemplateNode node : nodes) {
if (node instanceof SectionNode) {
builder.add(node);
((SectionNode) node).removeNodes(nodesToRemove);
} else if (!nodesToRemove.contains(node)) {
builder.add(node);
effectiveNodes.add(node);
((SectionNode) node).optimizeNodes(nodesToRemove);
} else if (node != Parser.COMMENT_NODE && !(node instanceof ParameterDeclarationNode)
&& (nodesToRemove.isEmpty() || !nodesToRemove.contains(node))) {
// Ignore comments, param declarations and nodes for removal
effectiveNodes.add(node);
}
}
nodes = builder.build();
// Collapse adjacent text and line separator nodes
List<TemplateNode> finalNodes = new ArrayList<>();
List<TextNode> group = null;
for (TemplateNode node : effectiveNodes) {
if (node instanceof TextNode) {
if (group == null) {
group = new ArrayList<>();
}
group.add((TextNode) node);
} else {
if (group != null) {
collapseGroup(group, finalNodes);
group = null;
}
finalNodes.add(node);
}
}
if (group != null) {
collapseGroup(group, finalNodes);
}
nodes = ImmutableList.copyOf(finalNodes);
}

private void collapseGroup(List<TextNode> group, List<TemplateNode> finalNodes) {
if (group.size() > 1) {
// Collapse the group...
StringBuilder val = new StringBuilder();
for (TextNode textNode : group) {
val.append(textNode.getValue());
}
finalNodes.add(new TextNode(val.toString(), group.get(0).getOrigin()));
} else {
finalNodes.add(group.get(0));
}
}

static SectionBlock.Builder builder(String id, Function<String, Expression> expressionFunc,
Expand Down
Loading

0 comments on commit 9a1bed6

Please sign in to comment.