You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by gs...@apache.org on 2023/09/15 23:41:51 UTC

[lucene] branch branch_9x updated: Defer #advanceExact on expression dependencies until their values are needed (#12560)

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

gsmiller pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new af978fb27dd Defer #advanceExact on expression dependencies until their values are needed (#12560)
af978fb27dd is described below

commit af978fb27ddfc1710d8f8d2872e05cf5a2112172
Author: Greg Miller <gs...@gmail.com>
AuthorDate: Fri Sep 15 16:26:14 2023 -0700

    Defer #advanceExact on expression dependencies until their values are needed (#12560)
---
 lucene/CHANGES.txt                                 |  3 +
 .../lucene/expressions/ExpressionValueSource.java  | 22 +++++--
 .../expressions/TestExpressionValueSource.java     | 71 ++++++++++++++++++++++
 3 files changed, 92 insertions(+), 4 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index a637a515b3c..8f5a89f119c 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -79,6 +79,9 @@ Optimizations
 
 * GITHUB#12490: Faster computation of top-k hits on boolean queries. (Adrien Grand)
 
+* GITHUB#12560: ExpressionValueSource defers #advanceExact on dependencies until their values are needed, avoiding
+  unnecessary advancing on values that are never evaluated (e.g., because of ternary expressions). (Greg Miller)
+
 Changes in runtime behavior
 ---------------------
 
diff --git a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java
index 376323c860c..8208d134a7c 100644
--- a/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java
+++ b/lucene/expressions/src/java/org/apache/lucene/expressions/ExpressionValueSource.java
@@ -90,16 +90,30 @@ class ExpressionValueSource extends DoubleValuesSource {
   static DoubleValues zeroWhenUnpositioned(DoubleValues in) {
     return new DoubleValues() {
 
-      boolean positioned = false;
+      int currentDoc = -1;
+      double value;
+      boolean computed = false;
 
       @Override
       public double doubleValue() throws IOException {
-        return positioned ? in.doubleValue() : 0;
+        if (computed == false) {
+          value = in.advanceExact(currentDoc) ? in.doubleValue() : 0;
+          computed = true;
+        }
+        return value;
       }
 
       @Override
-      public boolean advanceExact(int doc) throws IOException {
-        return positioned = in.advanceExact(doc);
+      public boolean advanceExact(int doc) {
+        // This implementation wraps all expression arguments, so we lazily advance it in case the
+        // value is never needed by the expression for a given doc (e.g., ternary branch or
+        // condition short-circuit):
+        if (currentDoc == doc) {
+          return true;
+        }
+        currentDoc = doc;
+        computed = false;
+        return true;
       }
     };
   }
diff --git a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java
index c1ad8c31ab6..87f2e674406 100644
--- a/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java
+++ b/lucene/expressions/src/test/org/apache/lucene/expressions/TestExpressionValueSource.java
@@ -150,6 +150,30 @@ public class TestExpressionValueSource extends LuceneTestCase {
     assertEquals(fib(n), (int) values.doubleValue());
   }
 
+  public void testLazyDependencies() throws Exception {
+    SimpleBindings bindings = new SimpleBindings();
+    bindings.add("f0", DoubleValuesSource.constant(1));
+    bindings.add("f1", DoubleValuesSource.constant(42));
+    bindings.add("f2", new NoAdvanceDoubleValuesSource());
+
+    // f2 should never be evaluated:
+    Expression expression = JavascriptCompiler.compile("f0 == 1 ? f1 : f2");
+    DoubleValuesSource dvs = expression.getDoubleValuesSource(bindings);
+    DoubleValues dv = dvs.getValues(reader.leaves().get(0), null);
+    dv.advanceExact(0);
+    double value = dv.doubleValue();
+    assertEquals(42, value, 0);
+
+    // one more example to show that we will also correctly short-circuit a condition (f2 should
+    // not be advanced or evaluated):
+    expression = JavascriptCompiler.compile("(1 == 1 || f2) ? f1 : 0");
+    dvs = expression.getDoubleValuesSource(bindings);
+    dv = dvs.getValues(reader.leaves().get(0), null);
+    dv.advanceExact(0);
+    value = dv.doubleValue();
+    assertEquals(42, value, 0);
+  }
+
   private int fib(int n) {
     if (n == 0) {
       return 0;
@@ -215,4 +239,51 @@ public class TestExpressionValueSource extends LuceneTestCase {
       }
     };
   }
+
+  private static class NoAdvanceDoubleValuesSource extends DoubleValuesSource {
+    @Override
+    public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
+      return new DoubleValues() {
+        @Override
+        public double doubleValue() throws IOException {
+          throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean advanceExact(int doc) throws IOException {
+          throw new UnsupportedOperationException();
+        }
+      };
+    }
+
+    @Override
+    public boolean needsScores() {
+      return false;
+    }
+
+    @Override
+    public DoubleValuesSource rewrite(IndexSearcher reader) throws IOException {
+      return this;
+    }
+
+    @Override
+    public int hashCode() {
+      return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return false;
+    }
+
+    @Override
+    public String toString() {
+      return null;
+    }
+
+    @Override
+    public boolean isCacheable(LeafReaderContext ctx) {
+      return false;
+    }
+  }
 }