You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2024/03/29 06:23:07 UTC

(lucene) branch main updated: Use singleton for all-zeros DirectMonotonicReader.Meta (#13224)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 6cba7733184 Use singleton for all-zeros DirectMonotonicReader.Meta (#13224)
6cba7733184 is described below

commit 6cba773318445d1ecd349d42ff85508b58be1752
Author: Armin Braun <me...@obrown.io>
AuthorDate: Fri Mar 29 07:23:00 2024 +0100

    Use singleton for all-zeros DirectMonotonicReader.Meta (#13224)
    
    Having a single block of all zeros is a fairly common case that is using
    a lot of heap for duplicate instances in some use-cases in ES.
    => read a singleton for it to save the duplication
---
 lucene/CHANGES.txt                                 |  2 ++
 .../lucene/util/packed/DirectMonotonicReader.java  | 30 ++++++++++------
 .../lucene/util/packed/TestDirectMonotonic.java    | 41 ++++++++++++++++++++++
 3 files changed, 63 insertions(+), 10 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index fbd9c36e1a4..dcce957ff54 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -254,6 +254,8 @@ Optimizations
 
 * GITHUB#13203: Speed up writeGroupVInts (Zhang Chao)
 
+* GITHUB#13224: Use singleton for all-zeros DirectMonotonicReader.Meta (Armin Braun)
+
 Bug Fixes
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/util/packed/DirectMonotonicReader.java b/lucene/core/src/java/org/apache/lucene/util/packed/DirectMonotonicReader.java
index 1f5954ec05f..66a7db9ab03 100644
--- a/lucene/core/src/java/org/apache/lucene/util/packed/DirectMonotonicReader.java
+++ b/lucene/core/src/java/org/apache/lucene/util/packed/DirectMonotonicReader.java
@@ -39,15 +39,19 @@ public final class DirectMonotonicReader extends LongValues implements Accountab
    * from disk.
    */
   public static class Meta implements Accountable {
+
+    // Use a shift of 63 so that there would be a single block regardless of the number of values.
+    private static final Meta SINGLE_ZERO_BLOCK = new Meta(1L, 63);
+
     private static final long BASE_RAM_BYTES_USED =
         RamUsageEstimator.shallowSizeOfInstance(Meta.class);
 
-    final int blockShift;
-    final int numBlocks;
-    final long[] mins;
-    final float[] avgs;
-    final byte[] bpvs;
-    final long[] offsets;
+    private final int blockShift;
+    private final int numBlocks;
+    private final long[] mins;
+    private final float[] avgs;
+    private final byte[] bpvs;
+    private final long[] offsets;
 
     Meta(long numValues, int blockShift) {
       this.blockShift = blockShift;
@@ -79,14 +83,20 @@ public final class DirectMonotonicReader extends LongValues implements Accountab
    */
   public static Meta loadMeta(IndexInput metaIn, long numValues, int blockShift)
       throws IOException {
+    boolean allValuesZero = true;
     Meta meta = new Meta(numValues, blockShift);
     for (int i = 0; i < meta.numBlocks; ++i) {
-      meta.mins[i] = metaIn.readLong();
-      meta.avgs[i] = Float.intBitsToFloat(metaIn.readInt());
+      long min = metaIn.readLong();
+      meta.mins[i] = min;
+      int avgInt = metaIn.readInt();
+      meta.avgs[i] = Float.intBitsToFloat(avgInt);
       meta.offsets[i] = metaIn.readLong();
-      meta.bpvs[i] = metaIn.readByte();
+      byte bpvs = metaIn.readByte();
+      meta.bpvs[i] = bpvs;
+      allValuesZero = allValuesZero && min == 0L && avgInt == 0 && bpvs == 0;
     }
-    return meta;
+    // save heap in case all values are zero
+    return allValuesZero ? Meta.SINGLE_ZERO_BLOCK : meta;
   }
 
   /** Retrieves a non-merging instance from the specified slice. */
diff --git a/lucene/core/src/test/org/apache/lucene/util/packed/TestDirectMonotonic.java b/lucene/core/src/test/org/apache/lucene/util/packed/TestDirectMonotonic.java
index ff7c3ccce22..62a8f41b011 100644
--- a/lucene/core/src/test/org/apache/lucene/util/packed/TestDirectMonotonic.java
+++ b/lucene/core/src/test/org/apache/lucene/util/packed/TestDirectMonotonic.java
@@ -154,6 +154,47 @@ public class TestDirectMonotonic extends LuceneTestCase {
     dir.close();
   }
 
+  public void testZeroValuesSmallBlobShift() throws IOException {
+    Directory dir = newDirectory();
+    final int numValues = TestUtil.nextInt(random(), 8, 1 << 20);
+    // use blockShift < log2(numValues)
+    final int blockShift =
+        TestUtil.nextInt(
+            random(),
+            DirectMonotonicWriter.MIN_BLOCK_SHIFT,
+            Math.toIntExact(Math.round(Math.log(numValues) / Math.log(2))) - 1);
+
+    final long dataLength;
+    try (IndexOutput metaOut = dir.createOutput("meta", IOContext.DEFAULT);
+        IndexOutput dataOut = dir.createOutput("data", IOContext.DEFAULT)) {
+      DirectMonotonicWriter w =
+          DirectMonotonicWriter.getInstance(metaOut, dataOut, numValues, blockShift);
+      for (int i = 0; i < numValues; i++) {
+        w.add(0);
+      }
+      w.finish();
+      dataLength = dataOut.getFilePointer();
+    }
+
+    try (IndexInput metaIn = dir.openInput("meta", IOContext.READONCE);
+        IndexInput dataIn = dir.openInput("data", IOContext.DEFAULT)) {
+      DirectMonotonicReader.Meta meta =
+          DirectMonotonicReader.loadMeta(metaIn, numValues, blockShift);
+      assertEquals(metaIn.length(), metaIn.getFilePointer());
+      // read meta again and assert singleton Meta#SINGLE_ZERO_BLOCK instance is read every time
+      metaIn.seek(0L);
+      assertSame(meta, DirectMonotonicReader.loadMeta(metaIn, numValues, blockShift));
+      LongValues values =
+          DirectMonotonicReader.getInstance(meta, dataIn.randomAccessSlice(0, dataLength));
+      for (int i = 0; i < numValues; ++i) {
+        assertEquals(0, values.get(i));
+      }
+      assertEquals(0, dataIn.getFilePointer());
+    }
+
+    dir.close();
+  }
+
   public void testRandom() throws IOException {
     doTestRandom(false);
   }