This is an automated email from the ASF dual-hosted git repository.
zabetak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new f33e1b8 [CALCITE-2902] Improve performance of
AbstractRelNode#computeDigest
f33e1b8 is described below
commit f33e1b8f5609f11b039577694023241156951c08
Author: Stamatis Zampetakis <[email protected]>
AuthorDate: Fri Mar 8 18:56:44 2019 +0100
[CALCITE-2902] Improve performance of AbstractRelNode#computeDigest
1. Use StringBuilder instead of PrintWriter for building the digest, which
is more efficient to initialize and append to.
2. Improve (sligthly) the performance of AbstractRelNode#getRelTypeName
(used in the digest) by performing:
a. one lastIndexOf operation instead of potentially two;
b. char instead of char[] array comparison.
3. Add micro benchmark with different implementations of
AbstractRelNode#getRelTypeName showing the benefit of the adopted
implementation.
4. Improve RelWriter interface with default methods and refactor respective
classes.
---
.../org/apache/calcite/rel/AbstractRelNode.java | 117 +++++----
.../java/org/apache/calcite/rel/RelWriter.java | 12 +-
.../calcite/rel/externalize/RelJsonWriter.java | 13 +-
.../calcite/rel/externalize/RelWriterImpl.java | 16 --
.../AbstractRelNodeGetRelTypeNameBenchmark.java | 261 +++++++++++++++++++++
5 files changed, 340 insertions(+), 79 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
index 59a9179..f4899a1 100644
--- a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
+++ b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
@@ -27,7 +27,6 @@ import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTrait;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.core.CorrelationId;
-import org.apache.calcite.rel.externalize.RelWriterImpl;
import org.apache.calcite.rel.metadata.Metadata;
import org.apache.calcite.rel.metadata.MetadataFactory;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
@@ -46,8 +45,6 @@ import com.google.common.collect.ImmutableSet;
import org.slf4j.Logger;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -190,16 +187,14 @@ public abstract class AbstractRelNode implements RelNode {
}
public final String getRelTypeName() {
- String className = getClass().getName();
- int i = className.lastIndexOf("$");
- if (i >= 0) {
- return className.substring(i + 1);
- }
- i = className.lastIndexOf(".");
- if (i >= 0) {
- return className.substring(i + 1);
+ String cn = getClass().getName();
+ int i = cn.length();
+ while (--i >= 0) {
+ if (cn.charAt(i) == '$' || cn.charAt(i) == '.') {
+ return cn.substring(i + 1);
+ }
}
- return className;
+ return cn;
}
public boolean isValid(Litmus litmus, Context context) {
@@ -389,42 +384,68 @@ public abstract class AbstractRelNode implements RelNode {
* @return Digest
*/
protected String computeDigest() {
- StringWriter sw = new StringWriter();
- RelWriter pw =
- new RelWriterImpl(
- new PrintWriter(sw),
- SqlExplainLevel.DIGEST_ATTRIBUTES, false) {
- protected void explain_(
- RelNode rel, List<Pair<String, Object>> values) {
- pw.write(getRelTypeName());
-
- for (RelTrait trait : traitSet) {
- pw.write(".");
- pw.write(trait.toString());
- }
-
- pw.write("(");
- int j = 0;
- for (Pair<String, Object> value : values) {
- if (j++ > 0) {
- pw.write(",");
- }
- pw.write(value.left);
- pw.write("=");
- if (value.right instanceof RelNode) {
- RelNode input = (RelNode) value.right;
- pw.write(input.getRelTypeName());
- pw.write("#");
- pw.write(Integer.toString(input.getId()));
- } else {
- pw.write(String.valueOf(value.right));
- }
- }
- pw.write(")");
- }
- };
- explain(pw);
- return sw.toString();
+ RelDigestWriter rdw = new RelDigestWriter();
+ explain(rdw);
+ return rdw.digest;
+ }
+
+ /**
+ * A writer object used exclusively for computing the digest of a RelNode.
+ *
+ * <p>The writer is meant to be used only for computing a single digest and
then thrown away.
+ * After calling {@link #done(RelNode)} the writer should be used only to
obtain the computed
+ * {@link #digest}. Any other action is prohibited.</p>
+ *
+ */
+ private static final class RelDigestWriter implements RelWriter {
+
+ private final List<Pair<String, Object>> values = new ArrayList<>();
+
+ String digest = null;
+
+ @Override public void explain(final RelNode rel, final List<Pair<String,
Object>> valueList) {
+ throw new IllegalStateException("Should not be called for computing
digest");
+ }
+
+ @Override public SqlExplainLevel getDetailLevel() {
+ return SqlExplainLevel.DIGEST_ATTRIBUTES;
+ }
+
+ @Override public RelWriter item(String term, Object value) {
+ values.add(Pair.of(term, value));
+ return this;
+ }
+
+ @Override public RelWriter done(RelNode node) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(node.getRelTypeName());
+
+ for (RelTrait trait : node.getTraitSet()) {
+ sb.append('.');
+ sb.append(trait.toString());
+ }
+
+ sb.append('(');
+ int j = 0;
+ for (Pair<String, Object> value : values) {
+ if (j++ > 0) {
+ sb.append(',');
+ }
+ sb.append(value.left);
+ sb.append('=');
+ if (value.right instanceof RelNode) {
+ RelNode input = (RelNode) value.right;
+ sb.append(input.getRelTypeName());
+ sb.append('#');
+ sb.append(input.getId());
+ } else {
+ sb.append(value.right);
+ }
+ }
+ sb.append(')');
+ digest = sb.toString();
+ return this;
+ }
}
}
diff --git a/core/src/main/java/org/apache/calcite/rel/RelWriter.java
b/core/src/main/java/org/apache/calcite/rel/RelWriter.java
index fa63bc2..551685c 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelWriter.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelWriter.java
@@ -53,7 +53,9 @@ public interface RelWriter {
* @param term Term for input, e.g. "left" or "input #1".
* @param input Input relational expression
*/
- RelWriter input(String term, RelNode input);
+ default RelWriter input(String term, RelNode input) {
+ return item(term, input);
+ }
/**
* Adds an attribute to the explanation of the current node.
@@ -67,7 +69,9 @@ public interface RelWriter {
* Adds an input to the explanation of the current node, if a condition
* holds.
*/
- RelWriter itemIf(String term, Object value, boolean condition);
+ default RelWriter itemIf(String term, Object value, boolean condition) {
+ return condition ? item(term, value) : this;
+ }
/**
* Writes the completed explanation.
@@ -78,7 +82,9 @@ public interface RelWriter {
* Returns whether the writer prefers nested values. Traditional explain
* writers prefer flattened values.
*/
- boolean nest();
+ default boolean nest() {
+ return false;
+ }
}
// End RelWriter.java
diff --git
a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
index fb96778..c2105cb 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJsonWriter.java
@@ -104,10 +104,6 @@ public class RelJsonWriter implements RelWriter {
return SqlExplainLevel.ALL_ATTRIBUTES;
}
- public RelWriter input(String term, RelNode input) {
- return this;
- }
-
public RelWriter item(String term, Object value) {
values.add(Pair.of(term, value));
return this;
@@ -125,13 +121,6 @@ public class RelJsonWriter implements RelWriter {
return list;
}
- public RelWriter itemIf(String term, Object value, boolean condition) {
- if (condition) {
- item(term, value);
- }
- return this;
- }
-
public RelWriter done(RelNode node) {
final List<Pair<String, Object>> valuesCopy =
ImmutableList.copyOf(values);
@@ -140,7 +129,7 @@ public class RelJsonWriter implements RelWriter {
return this;
}
- public boolean nest() {
+ @Override public boolean nest() {
return true;
}
diff --git
a/core/src/main/java/org/apache/calcite/rel/externalize/RelWriterImpl.java
b/core/src/main/java/org/apache/calcite/rel/externalize/RelWriterImpl.java
index af2985e..9dade7b 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelWriterImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelWriterImpl.java
@@ -131,23 +131,11 @@ public class RelWriterImpl implements RelWriter {
return detailLevel;
}
- public RelWriter input(String term, RelNode input) {
- values.add(Pair.of(term, (Object) input));
- return this;
- }
-
public RelWriter item(String term, Object value) {
values.add(Pair.of(term, value));
return this;
}
- public RelWriter itemIf(String term, Object value, boolean condition) {
- if (condition) {
- item(term, value);
- }
- return this;
- }
-
public RelWriter done(RelNode node) {
assert checkInputsPresentInExplain(node);
final List<Pair<String, Object>> valuesCopy =
@@ -170,10 +158,6 @@ public class RelWriterImpl implements RelWriter {
return true;
}
- public boolean nest() {
- return false;
- }
-
/**
* Converts the collected terms and values to a string. Does not write to
* the parent writer.
diff --git
a/ubenchmark/src/main/java/org/apache/calcite/benchmarks/AbstractRelNodeGetRelTypeNameBenchmark.java
b/ubenchmark/src/main/java/org/apache/calcite/benchmarks/AbstractRelNodeGetRelTypeNameBenchmark.java
new file mode 100644
index 0000000..3321881
--- /dev/null
+++
b/ubenchmark/src/main/java/org/apache/calcite/benchmarks/AbstractRelNodeGetRelTypeNameBenchmark.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.benchmarks;
+
+
+import org.apache.calcite.rel.AbstractRelNode;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A benchmark of alternative implementations for {@link
AbstractRelNode#getRelTypeName()}
+ * method.
+ */
+@Fork(value = 1, jvmArgsPrepend = "-Xmx1024m")
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@Threads(1)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class AbstractRelNodeGetRelTypeNameBenchmark {
+
+ /**
+ * A state holding the full class names of all built-in implementors of the
+ * {@link org.apache.calcite.rel.RelNode} interface.
+ */
+ @State(Scope.Thread)
+ public static class ClassNameState {
+
+ private final String[] fullNames = new String[]{
+ "org.apache.calcite.interpreter.InterpretableRel",
+ "org.apache.calcite.interpreter.BindableRel",
+ "org.apache.calcite.adapter.enumerable.EnumerableInterpretable",
+ "org.apache.calcite.adapter.enumerable.EnumerableRel",
+ "org.apache.calcite.adapter.enumerable.EnumerableLimit",
+ "org.apache.calcite.adapter.enumerable.EnumerableUnion",
+ "org.apache.calcite.adapter.enumerable.EnumerableCollect",
+ "org.apache.calcite.adapter.enumerable.EnumerableTableFunctionScan",
+ "org.apache.calcite.adapter.enumerable.EnumerableValues",
+ "org.apache.calcite.adapter.enumerable.EnumerableSemiJoin",
+ "org.apache.calcite.adapter.enumerable.EnumerableMinus",
+ "org.apache.calcite.adapter.enumerable.EnumerableIntersect",
+ "org.apache.calcite.adapter.enumerable.EnumerableUncollect",
+ "org.apache.calcite.adapter.enumerable.EnumerableMergeJoin",
+ "org.apache.calcite.adapter.enumerable.EnumerableProject",
+ "org.apache.calcite.adapter.enumerable.EnumerableFilter",
+ "org.apache.calcite.adapter.jdbc.JdbcToEnumerableConverter",
+ "org.apache.calcite.adapter.enumerable.EnumerableThetaJoin",
+ "org.apache.calcite.adapter.enumerable.EnumerableTableScan",
+ "org.apache.calcite.adapter.enumerable.EnumerableJoin",
+ "org.apache.calcite.adapter.enumerable.EnumerableTableModify",
+ "org.apache.calcite.adapter.enumerable.EnumerableAggregate",
+ "org.apache.calcite.adapter.enumerable.EnumerableCorrelate",
+ "org.apache.calcite.adapter.enumerable.EnumerableSort",
+ "org.apache.calcite.adapter.enumerable.EnumerableWindow",
+ "org.apache.calcite.plan.volcano.VolcanoPlannerTraitTest$FooRel",
+ "org.apache.calcite.adapter.enumerable.EnumerableCalc",
+ "org.apache.calcite.adapter.enumerable.EnumerableInterpreter",
+ "org.apache.calcite.adapter.geode.rel.GeodeToEnumerableConverter",
+ "org.apache.calcite.adapter.pig.PigToEnumerableConverter",
+ "org.apache.calcite.adapter.mongodb.MongoToEnumerableConverter",
+ "org.apache.calcite.adapter.csv.CsvTableScan",
+ "org.apache.calcite.adapter.spark.SparkToEnumerableConverter",
+
"org.apache.calcite.adapter.elasticsearch.ElasticsearchToEnumerableConverter",
+ "org.apache.calcite.adapter.file.FileTableScan",
+ "org.apache.calcite.adapter.cassandra.CassandraToEnumerableConverter",
+ "org.apache.calcite.adapter.splunk.SplunkTableScan",
+ "org.apache.calcite.adapter.jdbc.JdbcRel",
+ "org.apache.calcite.adapter.jdbc.JdbcTableScan",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcJoin",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcCalc",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcProject",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcFilter",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcAggregate",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcSort",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcUnion",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcIntersect",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcMinus",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcTableModify",
+ "org.apache.calcite.adapter.jdbc.JdbcRules$JdbcValues",
+ "org.apache.calcite.tools.PlannerTest$MockJdbcTableScan",
+ "org.apache.calcite.rel.AbstractRelNode",
+ "org.apache.calcite.rel.rules.MultiJoin",
+ "org.apache.calcite.rel.core.TableFunctionScan",
+ "org.apache.calcite.rel.BiRel",
+ "org.apache.calcite.rel.SingleRel",
+ "org.apache.calcite.rel.core.Values",
+ "org.apache.calcite.rel.core.TableScan",
+ "org.apache.calcite.plan.hep.HepRelVertex",
+ "org.apache.calcite.plan.RelOptPlanReaderTest$MyRel",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$PhysTable",
+ "org.apache.calcite.plan.volcano.PlannerTests$TestLeafRel",
+ "org.apache.calcite.plan.volcano.RelSubset",
+ "org.apache.calcite.rel.core.SetOp",
+ "org.apache.calcite.plan.volcano.VolcanoPlannerTraitTest$TestLeafRel",
+ "org.apache.calcite.adapter.druid.DruidQuery",
+
"org.apache.calcite.sql2rel.RelStructuredTypeFlattener$SelfFlatteningRel",
+ "org.apache.calcite.rel.convert.Converter",
+ "org.apache.calcite.rel.convert.ConverterImpl",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$Phys",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$PhysTable",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$PhysSort",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$PhysAgg",
+ "org.apache.calcite.plan.volcano.TraitPropagationTest$PhysProj",
+ "org.apache.calcite.interpreter.BindableRel",
+ "org.apache.calcite.adapter.enumerable.EnumerableBindable",
+ "org.apache.calcite.interpreter.Bindables$BindableTableScan",
+ "org.apache.calcite.interpreter.Bindables$BindableFilter",
+ "org.apache.calcite.interpreter.Bindables$BindableProject",
+ "org.apache.calcite.interpreter.Bindables$BindableSort",
+ "org.apache.calcite.interpreter.Bindables$BindableJoin",
+ "org.apache.calcite.interpreter.Bindables$BindableUnion",
+ "org.apache.calcite.interpreter.Bindables$BindableValues",
+ "org.apache.calcite.interpreter.Bindables$BindableAggregate",
+ "org.apache.calcite.interpreter.Bindables$BindableWindow",
+ "org.apache.calcite.adapter.druid.DruidQuery",
+ "org.apache.calcite.adapter.cassandra.CassandraRel",
+ "org.apache.calcite.adapter.cassandra.CassandraFilter",
+ "org.apache.calcite.adapter.cassandra.CassandraProject",
+ "org.apache.calcite.adapter.cassandra.CassandraLimit",
+ "org.apache.calcite.adapter.cassandra.CassandraSort",
+ "org.apache.calcite.adapter.cassandra.CassandraTableScan",
+ "org.apache.calcite.adapter.mongodb.MongoRel",
+ "org.apache.calcite.adapter.mongodb.MongoTableScan",
+ "org.apache.calcite.adapter.mongodb.MongoProject",
+ "org.apache.calcite.adapter.mongodb.MongoFilter",
+ "org.apache.calcite.adapter.mongodb.MongoAggregate",
+ "org.apache.calcite.adapter.mongodb.MongoSort",
+ "org.apache.calcite.adapter.spark.SparkRel",
+ "org.apache.calcite.adapter.spark.JdbcToSparkConverter",
+ "org.apache.calcite.adapter.spark.SparkRules$SparkValues",
+ "org.apache.calcite.adapter.spark.EnumerableToSparkConverter",
+ "org.apache.calcite.adapter.spark.SparkRules$SparkCalc",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchRel",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchFilter",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchProject",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchAggregate",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchTableScan",
+ "org.apache.calcite.adapter.elasticsearch.ElasticsearchSort",
+ "org.apache.calcite.adapter.geode.rel.GeodeRel",
+ "org.apache.calcite.adapter.geode.rel.GeodeSort",
+ "org.apache.calcite.adapter.geode.rel.GeodeTableScan",
+ "org.apache.calcite.adapter.geode.rel.GeodeProject",
+ "org.apache.calcite.adapter.geode.rel.GeodeFilter",
+ "org.apache.calcite.adapter.geode.rel.GeodeAggregate",
+ "org.apache.calcite.adapter.pig.PigRel",
+ "org.apache.calcite.adapter.pig.PigTableScan",
+ "org.apache.calcite.adapter.pig.PigAggregate",
+ "org.apache.calcite.adapter.pig.PigJoin",
+ "org.apache.calcite.adapter.pig.PigFilter",
+ "org.apache.calcite.adapter.pig.PigProject"
+ };
+
+ @Param({"11", "31", "63"})
+ private long seed;
+
+ private Random r = null;
+
+ /**
+ * Setups the random number generator at the beginning of each iteration.
+ *
+ * To have relatively comparable results the generator should always use
the same seed for the
+ * whole duration of the benchmark.
+ */
+ @Setup(Level.Iteration)
+ public void setupRandom() {
+ r = new Random(seed);
+ }
+
+ /**
+ * Returns a pseudo random class name which corresponds to an implementor
of the RelNode
+ * interface.
+ */
+ public String nextName() {
+ return fullNames[r.nextInt(fullNames.length)];
+ }
+ }
+
+ @Benchmark
+ public String useStringLastIndexOfTwoTimesV1(ClassNameState state) {
+ String cn = state.nextName();
+ int i = cn.lastIndexOf("$");
+ if (i >= 0) {
+ return cn.substring(i + 1);
+ }
+ i = cn.lastIndexOf(".");
+ if (i >= 0) {
+ return cn.substring(i + 1);
+ }
+ return cn;
+ }
+
+ @Benchmark
+ public String useStringLastIndexOfTwoTimeV2(ClassNameState state) {
+ String cn = state.nextName();
+ int i = cn.lastIndexOf('$');
+ if (i >= 0) {
+ return cn.substring(i + 1);
+ }
+ i = cn.lastIndexOf('.');
+ if (i >= 0) {
+ return cn.substring(i + 1);
+ }
+ return cn;
+ }
+
+ @Benchmark
+ public String useCustomLastIndexOf(ClassNameState state) {
+ String cn = state.nextName();
+ int i = cn.length();
+ while (--i >= 0) {
+ if (cn.charAt(i) == '$' || cn.charAt(i) == '.') {
+ return cn.substring(i + 1);
+ }
+ }
+ return cn;
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = new OptionsBuilder()
+ .include(AbstractRelNodeGetRelTypeNameBenchmark.class.getName())
+ .detectJvmArgs()
+ .build();
+
+ new Runner(opt).run();
+ }
+}
+
+// End AbstractRelNodeGetRelTypeNameBenchmark.java