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

nfsantos pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 839c982efa OAK-11505 - Reduce object allocation in FullTextIndexEditor 
(#2098)
839c982efa is described below

commit 839c982efaefcc21d631eb3f71398872e1a5a4f7
Author: Nuno Santos <[email protected]>
AuthorDate: Mon Feb 24 11:18:54 2025 +0100

    OAK-11505 - Reduce object allocation in FullTextIndexEditor (#2098)
---
 .../search/spi/editor/FulltextDocumentMaker.java   |   1 -
 .../search/spi/editor/FulltextIndexEditor.java     | 135 ++++++++++++---------
 2 files changed, 75 insertions(+), 61 deletions(-)

diff --git 
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java
 
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java
index 72d63fa238..ab1df0dfc0 100644
--- 
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java
+++ 
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextDocumentMaker.java
@@ -723,5 +723,4 @@ public abstract class FulltextDocumentMaker<D> implements 
DocumentMaker<D> {
         //cameCase file name to allow faster like search
         indexNodeName(doc, value);
     }
-
 }
diff --git 
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditor.java
 
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditor.java
index 9c70a511ea..cf51a33b0c 100644
--- 
a/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditor.java
+++ 
b/oak-search/src/main/java/org/apache/jackrabbit/oak/plugins/index/search/spi/editor/FulltextIndexEditor.java
@@ -22,7 +22,6 @@ import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.commons.PathUtils;
 import org.apache.jackrabbit.oak.commons.collections.IterableUtils;
-import org.apache.jackrabbit.oak.commons.collections.SetUtils;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.search.Aggregate;
 import org.apache.jackrabbit.oak.plugins.index.search.IndexDefinition;
@@ -37,22 +36,20 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.BitSet;
 import java.util.List;
-import java.util.Set;
-
-import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
 
 /**
  * Generic implementation of an {@link IndexEditor} which supports index time 
aggregation.
  */
 public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateRoot {
 
-    private static final Logger log =
-            LoggerFactory.getLogger(FulltextIndexEditor.class);
+    private static final Logger log = 
LoggerFactory.getLogger(FulltextIndexEditor.class);
 
     public static final String TEXT_EXTRACTION_ERROR = "TextExtractionError";
 
+    private static final List<Aggregate.Matcher> EMPTY_AGGREGATE_MATCHER_LIST 
= List.of();
+
     private final FulltextIndexEditorContext<D> context;
 
     /* Name of this node, or {@code null} for the root node. */
@@ -75,10 +72,12 @@ public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateR
 
     private IndexDefinition.IndexingRule indexingRule;
 
-    private List<Aggregate.Matcher> currentMatchers = Collections.emptyList();
+    private List<Aggregate.Matcher> currentMatchers = List.of();
 
     private final MatcherState matcherState;
 
+    private final PathFilter pathFilter;
+
     private final PathFilter.Result pathFilterResult;
 
     public FulltextIndexEditor(FulltextIndexEditorContext<D> context) {
@@ -88,11 +87,13 @@ public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateR
         this.context = context;
         this.isDeleted = false;
         this.matcherState = MatcherState.NONE;
-        this.pathFilterResult = 
context.getDefinition().getPathFilter().filter(PathUtils.ROOT_PATH);
+        this.pathFilter = context.getDefinition().getPathFilter();
+        this.pathFilterResult = this.pathFilter.filter(PathUtils.ROOT_PATH);
     }
 
     public FulltextIndexEditor(FulltextIndexEditor<D> parent, String name,
                                MatcherState matcherState,
+                               PathFilter pathFilter,
                                PathFilter.Result pathFilterResult,
                                boolean isDeleted) {
         this.parent = parent;
@@ -101,12 +102,13 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
         this.context = parent.context;
         this.isDeleted = isDeleted;
         this.matcherState = matcherState;
+        this.pathFilter = pathFilter;
         this.pathFilterResult = pathFilterResult;
     }
 
     public String getPath() {
         if (path == null) { // => parent != null
-            path = concat(parent.getPath(), name);
+            path = PathUtils.concat(parent.getPath(), name);
         }
         return path;
     }
@@ -118,8 +120,7 @@ public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateR
         }
 
         //Only check for indexing if the result is include.
-        //In case like TRAVERSE nothing needs to be indexed for those
-        //path
+        //In case like TRAVERSE nothing needs to be indexed for those paths
         if (pathFilterResult == PathFilter.Result.INCLUDE) {
             //For traversal in deleted sub tree before state has to be used
             NodeState current = after.exists() ? after : before;
@@ -144,7 +145,9 @@ public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateR
             }
         }
 
-        for (Aggregate.Matcher m : matcherState.affectedMatchers) {
+        BitSet bitSet = matcherState.affectedMatchers;
+        for (int i = bitSet.nextSetBit(0); i != -1; i = bitSet.nextSetBit(i + 
1)) {
+            Aggregate.Matcher m = matcherState.matched.get(i);
             m.markRootDirty();
         }
 
@@ -178,7 +181,9 @@ public class FulltextIndexEditor<D> implements IndexEditor, 
Aggregate.AggregateR
     @Override
     public void propertyChanged(PropertyState before, PropertyState after) {
         markPropertyChanged(before.getName());
-        propertiesModified.add(before);
+        if (isIndexable()) {
+            propertiesModified.add(before);
+        }
         checkAggregates(before.getName());
         propertyUpdated(before, after);
     }
@@ -186,45 +191,48 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
     @Override
     public void propertyDeleted(PropertyState before) {
         markPropertyChanged(before.getName());
-        propertiesModified.add(before);
+        if (isIndexable()) {
+            propertiesModified.add(before);
+        }
         checkAggregates(before.getName());
         propertyUpdated(before, null);
     }
 
     @Override
     public Editor childNodeAdded(String name, NodeState after) {
-        PathFilter.Result filterResult = getPathFilterResult(name);
+        PathFilter.Result filterResult = 
pathFilter.filter(PathUtils.concat(getPath(), name));
         if (filterResult != PathFilter.Result.EXCLUDE) {
-            return new FulltextIndexEditor<>(this, name, getMatcherState(name, 
after), filterResult, false);
+            return new FulltextIndexEditor<>(this, name, getMatcherState(name, 
after), pathFilter, filterResult, false);
+        } else {
+            return null;
         }
-        return null;
     }
 
     @Override
-    public Editor childNodeChanged(
-            String name, NodeState before, NodeState after) {
-        PathFilter.Result filterResult = getPathFilterResult(name);
+    public Editor childNodeChanged(String name, NodeState before, NodeState 
after) {
+        PathFilter.Result filterResult = 
pathFilter.filter(PathUtils.concat(getPath(), name));
         if (filterResult != PathFilter.Result.EXCLUDE) {
-            return new FulltextIndexEditor<>(this, name, getMatcherState(name, 
after), filterResult, false);
+            return new FulltextIndexEditor<>(this, name, getMatcherState(name, 
after), pathFilter, filterResult, false);
+        } else {
+            return null;
         }
-        return null;
     }
 
     @Override
     public Editor childNodeDeleted(String name, NodeState before)
             throws CommitFailedException {
-        PathFilter.Result filterResult = getPathFilterResult(name);
+        String childPath = PathUtils.concat(getPath(), name);
+        PathFilter.Result filterResult = pathFilter.filter(childPath);
         if (filterResult == PathFilter.Result.EXCLUDE) {
             return null;
         }
 
         if (!isDeleted) {
             // tree deletion is handled on the parent node
-            String path = concat(getPath(), name);
             try {
                 FulltextIndexWriter<D> writer = context.getWriter();
                 // Remove all index entries in the removed subtree
-                writer.deleteDocuments(path);
+                writer.deleteDocuments(childPath);
                 this.context.indexUpdate();
             } catch (IOException e) {
                 CommitFailedException ce = new 
CommitFailedException("Fulltext", 5, "Failed to remove the index entries of"
@@ -235,10 +243,11 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
         }
 
         MatcherState ms = getMatcherState(name, before);
-        if (!ms.isEmpty()) {
-            return new FulltextIndexEditor<>(this, name, ms, filterResult, 
true);
+        if (ms.isEmpty()) {
+            return null; // no need to recurse down the removed subtree
+        } else {
+            return new FulltextIndexEditor<>(this, name, ms, pathFilter, 
filterResult, true);
         }
-        return null; // no need to recurse down the removed subtree
     }
 
     public FulltextIndexEditorContext<D> getContext() {
@@ -270,10 +279,11 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
     }
 
     private D makeDocument(String path, NodeState state, boolean isUpdate) 
throws IOException {
-        if (!isIndexable()) {
+        if (isIndexable()) {
+            return context.newDocumentMaker(indexingRule, 
path).makeDocument(state, isUpdate, propertiesModified);
+        } else {
             return null;
         }
-        return context.newDocumentMaker(indexingRule, 
path).makeDocument(state, isUpdate, propertiesModified);
     }
 
 
@@ -285,23 +295,34 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
     }
 
     private MatcherState getMatcherState(String name, NodeState after) {
-        List<Aggregate.Matcher> matched = new ArrayList<>();
-        List<Aggregate.Matcher> inherited = new ArrayList<>();
+        // Short circuit if there are no matchers to avoid creating the 
iterator over these two lists
+        if (matcherState.inherited.isEmpty() && currentMatchers.isEmpty()) {
+            return MatcherState.NONE;
+        }
+        List<Aggregate.Matcher> matched = EMPTY_AGGREGATE_MATCHER_LIST;
+        List<Aggregate.Matcher> inherited = EMPTY_AGGREGATE_MATCHER_LIST;
         for (Aggregate.Matcher m : 
IterableUtils.chainedIterable(matcherState.inherited, currentMatchers)) {
             Aggregate.Matcher result = m.match(name, after);
             if (result.getStatus() == Aggregate.Matcher.Status.MATCH_FOUND) {
+                if (matched == EMPTY_AGGREGATE_MATCHER_LIST) {
+                    matched = new ArrayList<>(4);
+                }
                 matched.add(result);
             }
 
             if (result.getStatus() != Aggregate.Matcher.Status.FAIL) {
+                if (inherited == EMPTY_AGGREGATE_MATCHER_LIST) {
+                    inherited = new ArrayList<>(4);
+                }
                 inherited.addAll(result.nextSet());
             }
         }
 
-        if (!matched.isEmpty() || !inherited.isEmpty()) {
+        if (matched.isEmpty() && inherited.isEmpty()) {
+            return MatcherState.NONE;
+        } else {
             return new MatcherState(matched, inherited);
         }
-        return MatcherState.NONE;
     }
 
 
@@ -311,33 +332,30 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
      * @param name modified property name
      */
     private void checkAggregates(String name) {
-        for (Aggregate.Matcher m : matcherState.matched) {
-            if (!matcherState.affectedMatchers.contains(m)
-                    && m.aggregatesProperty(name)) {
-                matcherState.affectedMatchers.add(m);
+        // Performance critical code, iterate using an index to avoid 
allocating an iterator
+        for (int i = 0; i < matcherState.matched.size(); i++) {
+            if (!matcherState.affectedMatchers.get(i)) {
+                Aggregate.Matcher m = matcherState.matched.get(i);
+                if (m.aggregatesProperty(name)) {
+                    matcherState.affectedMatchers.set(i);
+                }
             }
         }
     }
 
-    static class MatcherState {
+    public static class MatcherState {
+        private final static BitSet EMPTY_BITSET = new BitSet(0);
         final static MatcherState NONE = new MatcherState(List.of(), 
List.of());
 
         final List<Aggregate.Matcher> matched;
         final List<Aggregate.Matcher> inherited;
-        final Set<Aggregate.Matcher> affectedMatchers;
+        final BitSet affectedMatchers;
 
-        public MatcherState(List<Aggregate.Matcher> matched,
-                            List<Aggregate.Matcher> inherited) {
+        public MatcherState(List<Aggregate.Matcher> matched, 
List<Aggregate.Matcher> inherited) {
             this.matched = matched;
             this.inherited = inherited;
-
-            //Affected matches would only be used when there are
-            //some matched matchers
-            if (matched.isEmpty()) {
-                affectedMatchers = Collections.emptySet();
-            } else {
-                affectedMatchers = SetUtils.newIdentityHashSet();
-            }
+            // Affected matches would only be used when there are some matched 
matchers
+            this.affectedMatchers = matched.isEmpty() ? EMPTY_BITSET : new 
BitSet(matched.size());
         }
 
         public boolean isEmpty() {
@@ -370,13 +388,14 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
             }
         }
 
-        for (Aggregate.Matcher m : matcherState.matched) {
+        // Performance critical code, iterate using an index to avoid 
allocating an iterator
+        for (int i = 0; i < matcherState.matched.size(); i++) {
+            Aggregate.Matcher m = matcherState.matched.get(i);
             if (m.aggregatesProperty(propertyName)) {
-                Aggregate.Include i = m.getCurrentInclude();
-                if (i instanceof Aggregate.PropertyInclude) {
-                    PropertyDefinition pd = ((Aggregate.PropertyInclude) 
i).getPropertyDefinition();
+                Aggregate.Include aggregateInclude = m.getCurrentInclude();
+                if (aggregateInclude instanceof Aggregate.PropertyInclude) {
+                    PropertyDefinition pd = ((Aggregate.PropertyInclude) 
aggregateInclude).getPropertyDefinition();
                     String propertyRelativePath = 
PathUtils.concat(m.getMatchedPath(), propertyName);
-
                     callback.propertyUpdated(m.getRootPath(), 
propertyRelativePath, pd, before, after);
                 }
             }
@@ -391,10 +410,6 @@ public class FulltextIndexEditor<D> implements 
IndexEditor, Aggregate.AggregateR
         return indexingRule != null;
     }
 
-    private PathFilter.Result getPathFilterResult(String childNodeName) {
-        return 
context.getDefinition().getPathFilter().filter(concat(getPath(), 
childNodeName));
-    }
-
     private String getIndexName() {
         return context.getDefinition().getIndexName();
     }

Reply via email to