This is an automated email from the ASF dual-hosted git repository.

abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 7552dc49fb7 Reduce amount of expression objects created during 
evaluations (#15552)
7552dc49fb7 is described below

commit 7552dc49fb7937b4de2ef26e9857b8d26003e4cc
Author: Zoltan Haindrich <[email protected]>
AuthorDate: Fri Dec 15 11:41:59 2023 +0100

    Reduce amount of expression objects created during evaluations (#15552)
    
    I was looking into a query which was performing a bit poorly because the 
case_searched was touching more than 1 columns (if there is only 1 column there 
is a cache based evaluator).
    While I was doing that I've noticed that there are a few simple things 
which could help a bit:
    
    use a static TRUE/FALSE instead of creating a new object every time
    create the ExprEval early for ConstantExpr -s (except the one for 
BigInteger which seem to have some odd contract)
    return early from type autodetection
    these changes mostly reduce the amount of garbage the query creates during 
case_searched evaluation; although ExpressionSelectorBenchmark shows some 
improvements ~15% - but my manual trials on the taxi dataset with 60M rows 
showed more improvements - probably due to the fact that these changes mostly 
only reduce gc pressure.
---
 .../druid/benchmark/ExpressionFilterBenchmark.java |   2 +
 .../benchmark/ExpressionSelectorBenchmark.java     | 156 ++++++++++++++++++++-
 .../org/apache/druid/math/expr/ConstantExpr.java   |  20 ++-
 .../java/org/apache/druid/math/expr/ExprEval.java  |   6 +-
 .../druid/math/expr/ExpressionTypeConversion.java  |   3 +
 .../java/org/apache/druid/math/expr/ExprTest.java  |   7 +-
 .../org/apache/druid/math/expr/OutputTypeTest.java |  11 ++
 7 files changed, 193 insertions(+), 12 deletions(-)

diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionFilterBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionFilterBenchmark.java
index eea1804292a..e7d8ad00bc5 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionFilterBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionFilterBenchmark.java
@@ -25,6 +25,7 @@ import org.apache.druid.java.util.common.Intervals;
 import org.apache.druid.java.util.common.granularity.Granularities;
 import org.apache.druid.java.util.common.guava.Sequence;
 import org.apache.druid.java.util.common.io.Closer;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import org.apache.druid.query.expression.TestExprMacroTable;
 import org.apache.druid.query.filter.AndDimFilter;
 import org.apache.druid.query.filter.DimFilter;
@@ -70,6 +71,7 @@ public class ExpressionFilterBenchmark
 {
   static {
     NullHandling.initializeForTests();
+    ExpressionProcessing.initializeForTests();
   }
 
   @Param({"1000000"})
diff --git 
a/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionSelectorBenchmark.java
 
b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionSelectorBenchmark.java
index fcc4cf24794..d8e1dfc7646 100644
--- 
a/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionSelectorBenchmark.java
+++ 
b/benchmarks/src/test/java/org/apache/druid/benchmark/ExpressionSelectorBenchmark.java
@@ -25,8 +25,10 @@ import org.apache.druid.java.util.common.Intervals;
 import org.apache.druid.java.util.common.granularity.Granularities;
 import org.apache.druid.java.util.common.guava.Sequence;
 import org.apache.druid.java.util.common.io.Closer;
+import org.apache.druid.math.expr.ExpressionProcessing;
 import org.apache.druid.query.dimension.DefaultDimensionSpec;
 import org.apache.druid.query.dimension.ExtractionDimensionSpec;
+import org.apache.druid.query.expression.LookupEnabledTestExprMacroTable;
 import org.apache.druid.query.expression.TestExprMacroTable;
 import org.apache.druid.query.extraction.StrlenExtractionFn;
 import org.apache.druid.query.extraction.TimeFormatExtractionFn;
@@ -59,21 +61,21 @@ import org.openjdk.jmh.annotations.State;
 import org.openjdk.jmh.annotations.TearDown;
 import org.openjdk.jmh.annotations.Warmup;
 import org.openjdk.jmh.infra.Blackhole;
-
 import java.util.BitSet;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @State(Scope.Benchmark)
 @Fork(value = 1)
-@Warmup(iterations = 15)
-@Measurement(iterations = 30)
+@Warmup(iterations = 3, time = 3)
+@Measurement(iterations = 10, time = 3)
 @BenchmarkMode(Mode.AverageTime)
 @OutputTimeUnit(TimeUnit.MILLISECONDS)
 public class ExpressionSelectorBenchmark
 {
   static {
     NullHandling.initializeForTests();
+    ExpressionProcessing.initializeForTests();
   }
 
   @Param({"1000000"})
@@ -451,6 +453,154 @@ public class ExpressionSelectorBenchmark
     blackhole.consume(results);
   }
 
+  @Benchmark
+  public void caseSearched1(Blackhole blackhole)
+  {
+    final Sequence<Cursor> cursors = new 
QueryableIndexStorageAdapter(index).makeCursors(
+        null,
+        index.getDataInterval(),
+        VirtualColumns.create(
+            ImmutableList.of(
+                new ExpressionVirtualColumn(
+                    "v",
+                    "case_searched(s == 'asd' || isnull(s) || s == 'xxx', 1, s 
== 'foo' || s == 'bar', 2, 3)",
+                    ColumnType.LONG,
+                    TestExprMacroTable.INSTANCE
+                )
+            )
+        ),
+        Granularities.ALL,
+        false,
+        null
+    );
+
+    final List<?> results = cursors
+        .map(cursor -> {
+          final ColumnValueSelector selector = 
cursor.getColumnSelectorFactory().makeColumnValueSelector("v");
+          consumeLong(cursor, selector, blackhole);
+          return null;
+        })
+        .toList();
+
+    blackhole.consume(results);
+  }
+
+  @Benchmark
+  public void caseSearched2(Blackhole blackhole)
+  {
+    final Sequence<Cursor> cursors = new 
QueryableIndexStorageAdapter(index).makeCursors(
+        null,
+        index.getDataInterval(),
+        VirtualColumns.create(
+            ImmutableList.of(
+                new ExpressionVirtualColumn(
+                    "v",
+                    "case_searched(s == 'asd' || isnull(s) || n == 1, 1, n == 
2, 2, 3)",
+                    ColumnType.LONG,
+                    TestExprMacroTable.INSTANCE
+                )
+            )
+        ),
+        Granularities.ALL,
+        false,
+        null
+    );
+
+    final List<?> results = cursors
+        .map(cursor -> {
+          final ColumnValueSelector selector = 
cursor.getColumnSelectorFactory().makeColumnValueSelector("v");
+          consumeLong(cursor, selector, blackhole);
+          return null;
+        })
+        .toList();
+
+    blackhole.consume(results);
+  }
+
+  @Benchmark
+  public void caseSearchedWithLookup(Blackhole blackhole)
+  {
+    final Sequence<Cursor> cursors = new 
QueryableIndexStorageAdapter(index).makeCursors(
+        null,
+        index.getDataInterval(),
+        VirtualColumns.create(
+            ImmutableList.of(
+                new ExpressionVirtualColumn(
+                    "v",
+                    "case_searched(n == 1001, -1, "
+                        + "lookup(s, 'lookyloo') == 'asd1', 1, "
+                        + "lookup(s, 'lookyloo') == 'asd2', 2, "
+                        + "lookup(s, 'lookyloo') == 'asd3', 3, "
+                        + "lookup(s, 'lookyloo') == 'asd4', 4, "
+                        + "lookup(s, 'lookyloo') == 'asd5', 5, "
+                        + "-2)",
+                    ColumnType.LONG,
+                    LookupEnabledTestExprMacroTable.INSTANCE
+                )
+            )
+        ),
+        Granularities.ALL,
+        false,
+        null
+    );
+
+    final List<?> results = cursors
+        .map(cursor -> {
+          final ColumnValueSelector selector = 
cursor.getColumnSelectorFactory().makeColumnValueSelector("v");
+          consumeLong(cursor, selector, blackhole);
+          return null;
+        })
+        .toList();
+
+    blackhole.consume(results);
+  }
+
+  @Benchmark
+  public void caseSearchedWithLookup2(Blackhole blackhole)
+  {
+    final Sequence<Cursor> cursors = new 
QueryableIndexStorageAdapter(index).makeCursors(
+        null,
+        index.getDataInterval(),
+        VirtualColumns.create(
+            ImmutableList.of(
+                new ExpressionVirtualColumn(
+                    "ll",
+                    "lookup(s, 'lookyloo')",
+                    ColumnType.STRING,
+                    LookupEnabledTestExprMacroTable.INSTANCE
+                ),
+                new ExpressionVirtualColumn(
+                    "v",
+                    "case_searched(n == 1001, -1, "
+                        + "ll == 'asd1', 1, "
+                        + "ll == 'asd2', 2, "
+                        + "ll == 'asd3', 3, "
+                        + "ll == 'asd4', 4, "
+                        + "ll == 'asd5', 5, "
+                        + "-2)",
+                    ColumnType.LONG,
+                    LookupEnabledTestExprMacroTable.INSTANCE
+                )
+            )
+        ),
+        Granularities.ALL,
+        false,
+        null
+    );
+
+    final List<?> results = cursors
+        .map(cursor -> {
+          final ColumnValueSelector selector = 
cursor.getColumnSelectorFactory().makeColumnValueSelector("v");
+          consumeLong(cursor, selector, blackhole);
+          return null;
+        })
+        .toList();
+
+    blackhole.consume(results);
+  }
+
+
+
   private void consumeDimension(final Cursor cursor, final DimensionSelector 
selector, final Blackhole blackhole)
   {
     if (selector.getValueCardinality() >= 0) {
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java 
b/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java
index fdf6f080ee9..8790b2ed614 100644
--- a/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java
+++ b/processing/src/main/java/org/apache/druid/math/expr/ConstantExpr.java
@@ -157,9 +157,12 @@ class BigIntegerExpr extends ConstantExpr<BigInteger>
 
 class LongExpr extends ConstantExpr<Long>
 {
+  private final ExprEval expr;
+
   LongExpr(Long value)
   {
     super(ExpressionType.LONG, Preconditions.checkNotNull(value, "value"));
+    expr = ExprEval.ofLong(value);
   }
 
   @Override
@@ -171,7 +174,7 @@ class LongExpr extends ConstantExpr<Long>
   @Override
   public ExprEval eval(ObjectBinding bindings)
   {
-    return ExprEval.ofLong(value);
+    return expr;
   }
 
   @Override
@@ -240,9 +243,12 @@ class NullLongExpr extends ConstantExpr<Long>
 
 class DoubleExpr extends ConstantExpr<Double>
 {
+  private final ExprEval expr;
+
   DoubleExpr(Double value)
   {
     super(ExpressionType.DOUBLE, Preconditions.checkNotNull(value, "value"));
+    expr = ExprEval.ofDouble(value);
   }
 
   @Override
@@ -254,7 +260,7 @@ class DoubleExpr extends ConstantExpr<Double>
   @Override
   public ExprEval eval(ObjectBinding bindings)
   {
-    return ExprEval.ofDouble(value);
+    return expr;
   }
 
   @Override
@@ -323,9 +329,12 @@ class NullDoubleExpr extends ConstantExpr<Double>
 
 class StringExpr extends ConstantExpr<String>
 {
+  private final ExprEval expr;
+
   StringExpr(@Nullable String value)
   {
     super(ExpressionType.STRING, NullHandling.emptyToNullIfNeeded(value));
+    expr = ExprEval.of(value);
   }
 
   @Override
@@ -337,7 +346,7 @@ class StringExpr extends ConstantExpr<String>
   @Override
   public ExprEval eval(ObjectBinding bindings)
   {
-    return ExprEval.of(value);
+    return expr;
   }
 
   @Override
@@ -464,15 +473,18 @@ class ArrayExpr extends ConstantExpr<Object[]>
 
 class ComplexExpr extends ConstantExpr<Object>
 {
+  private final ExprEval expr;
+
   protected ComplexExpr(ExpressionType outputType, @Nullable Object value)
   {
     super(outputType, value);
+    expr = ExprEval.ofComplex(outputType, value);
   }
 
   @Override
   public ExprEval eval(ObjectBinding bindings)
   {
-    return ExprEval.ofComplex(outputType, value);
+    return expr;
   }
 
   @Override
diff --git a/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java 
b/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
index 35f59e5ebe7..e62021623bd 100644
--- a/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
+++ b/processing/src/main/java/org/apache/druid/math/expr/ExprEval.java
@@ -363,7 +363,7 @@ public abstract class ExprEval<T>
       case DOUBLE:
         return ExprEval.of(Evals.asDouble(value));
       case LONG:
-        return ExprEval.of(Evals.asLong(value));
+        return ofLongBoolean(value);
       case STRING:
         return ExprEval.of(String.valueOf(value));
       default:
@@ -376,7 +376,7 @@ public abstract class ExprEval<T>
    */
   public static ExprEval ofLongBoolean(boolean value)
   {
-    return ExprEval.of(Evals.asLong(value));
+    return value ? LongExprEval.TRUE : LongExprEval.FALSE;
   }
 
   public static ExprEval ofComplex(ExpressionType outputType, @Nullable Object 
value)
@@ -922,6 +922,8 @@ public abstract class ExprEval<T>
 
   private static class LongExprEval extends NumericExprEval
   {
+    private static final LongExprEval TRUE = new 
LongExprEval(Evals.asLong(true));
+    private static final LongExprEval FALSE = new 
LongExprEval(Evals.asLong(false));
     private static final LongExprEval OF_NULL = new LongExprEval(null);
 
     private LongExprEval(@Nullable Number value)
diff --git 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionTypeConversion.java
 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionTypeConversion.java
index ba576afa0c1..efe0328a837 100644
--- 
a/processing/src/main/java/org/apache/druid/math/expr/ExpressionTypeConversion.java
+++ 
b/processing/src/main/java/org/apache/druid/math/expr/ExpressionTypeConversion.java
@@ -55,6 +55,9 @@ public class ExpressionTypeConversion
   {
     ExpressionType type = eval.type();
     ExpressionType otherType = otherEval.type();
+    if (type == otherType && type.getType().isPrimitive()) {
+      return type;
+    }
     if (Types.is(type, ExprType.STRING) && Types.is(otherType, 
ExprType.STRING)) {
       return ExpressionType.STRING;
     }
diff --git a/processing/src/test/java/org/apache/druid/math/expr/ExprTest.java 
b/processing/src/test/java/org/apache/druid/math/expr/ExprTest.java
index 3c089ac5b6a..f8d222e0838 100644
--- a/processing/src/test/java/org/apache/druid/math/expr/ExprTest.java
+++ b/processing/src/test/java/org/apache/druid/math/expr/ExprTest.java
@@ -139,7 +139,7 @@ public class ExprTest
   public void testEqualsContractForStringExpr()
   {
     EqualsVerifier.forClass(StringExpr.class)
-                  .withIgnoredFields("outputType")
+                  .withIgnoredFields("outputType", "expr")
                   .withPrefabValues(ExpressionType.class, 
ExpressionType.STRING, ExpressionType.DOUBLE)
                   .usingGetClass()
                   .verify();
@@ -149,7 +149,7 @@ public class ExprTest
   public void testEqualsContractForDoubleExpr()
   {
     EqualsVerifier.forClass(DoubleExpr.class)
-                  .withIgnoredFields("outputType")
+                  .withIgnoredFields("outputType", "expr")
                   .withPrefabValues(ExpressionType.class, 
ExpressionType.DOUBLE, ExpressionType.LONG)
                   .usingGetClass()
                   .verify();
@@ -159,7 +159,7 @@ public class ExprTest
   public void testEqualsContractForLongExpr()
   {
     EqualsVerifier.forClass(LongExpr.class)
-                  .withIgnoredFields("outputType")
+                  .withIgnoredFields("outputType", "expr")
                   .withPrefabValues(ExpressionType.class, ExpressionType.LONG, 
ExpressionType.STRING)
                   .usingGetClass()
                   .verify();
@@ -187,6 +187,7 @@ public class ExprTest
                       ExpressionTypeFactory.getInstance().ofComplex("bar")
                   )
                   .withNonnullFields("outputType")
+                  .withIgnoredFields("expr")
                   .usingGetClass()
                   .verify();
   }
diff --git 
a/processing/src/test/java/org/apache/druid/math/expr/OutputTypeTest.java 
b/processing/src/test/java/org/apache/druid/math/expr/OutputTypeTest.java
index f8d663abc70..d313749fef8 100644
--- a/processing/src/test/java/org/apache/druid/math/expr/OutputTypeTest.java
+++ b/processing/src/test/java/org/apache/druid/math/expr/OutputTypeTest.java
@@ -453,6 +453,8 @@ public class OutputTypeTest extends 
InitializedNullHandlingTest
     final ExprEval<?> longEval = ExprEval.of(1L);
     final ExprEval<?> doubleEval = ExprEval.of(1.0);
     final ExprEval<?> arrayEval = ExprEval.ofLongArray(new Long[]{1L, 2L, 3L});
+    final ExprEval<?> complexEval = 
ExprEval.ofComplex(ExpressionType.UNKNOWN_COMPLEX, new Object());
+    final ExprEval<?> complexEval2 = ExprEval.ofComplex(new 
ExpressionType(ExprType.COMPLEX, null, null), new Object());
 
     // only long stays long
     Assert.assertEquals(ExpressionType.LONG, 
ExpressionTypeConversion.autoDetect(longEval, longEval));
@@ -479,6 +481,15 @@ public class OutputTypeTest extends 
InitializedNullHandlingTest
     Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(nullStringEval, arrayEval));
     Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(doubleEval, arrayEval));
     Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(longEval, arrayEval));
+
+    Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(longEval, complexEval));
+    Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(doubleEval, complexEval));
+    Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(arrayEval, complexEval));
+    Assert.assertEquals(ExpressionType.DOUBLE, 
ExpressionTypeConversion.autoDetect(complexEval, complexEval));
+    Assert.assertEquals(
+        ExpressionTypeConversion.autoDetect(complexEval, complexEval),
+        ExpressionTypeConversion.autoDetect(complexEval2, complexEval)
+    );
   }
 
   @Test


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to