You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by us...@apache.org on 2025/08/25 20:58:32 UTC
(lucene) branch main updated: Rewrite of the GroupVInt optimization without lambdas, varhandles and no code in subclasses (#15116)
This is an automated email from the ASF dual-hosted git repository.
uschindler 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 ebfd863ac72 Rewrite of the GroupVInt optimization without lambdas, varhandles and no code in subclasses (#15116)
ebfd863ac72 is described below
commit ebfd863ac728f9abfcaa42fec7fce90b62900df5
Author: Uwe Schindler <us...@apache.org>
AuthorDate: Mon Aug 25 22:58:28 2025 +0200
Rewrite of the GroupVInt optimization without lambdas, varhandles and no code in subclasses (#15116)
* Rewrite of the GroupVInt optimization without lamdas, varhandles and no code in subclasses
* Remove optimized flag and duplicate code
* apply suggestions/fixes: fix maximum size; cleanup baseline method and hide internal details
* Apply suggestions from code review
Co-authored-by: Adrien Grand <jp...@gmail.com>
---------
Co-authored-by: Adrien Grand <jp...@gmail.com>
---
.../lucene/benchmark/jmh/GroupVIntBenchmark.java | 33 +++----
.../apache/lucene/store/BufferedIndexInput.java | 11 ---
.../apache/lucene/store/ByteBuffersDataInput.java | 20 ----
.../apache/lucene/store/ByteBuffersIndexInput.java | 6 --
.../java/org/apache/lucene/store/DataInput.java | 11 ---
.../lucene/store/MemorySegmentIndexInput.java | 18 ----
.../java/org/apache/lucene/util/GroupVIntUtil.java | 106 ++++++++++-----------
7 files changed, 65 insertions(+), 140 deletions(-)
diff --git a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/GroupVIntBenchmark.java b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/GroupVIntBenchmark.java
index 6b40870c375..2b41fa262e3 100644
--- a/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/GroupVIntBenchmark.java
+++ b/lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/GroupVIntBenchmark.java
@@ -23,9 +23,8 @@ import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.store.ByteArrayDataInput;
import org.apache.lucene.store.ByteArrayDataOutput;
-import org.apache.lucene.store.ByteBuffersDataInput;
import org.apache.lucene.store.ByteBuffersDataOutput;
-import org.apache.lucene.store.DataInput;
+import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
@@ -50,8 +49,8 @@ import org.openjdk.jmh.infra.Blackhole;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
-@Warmup(iterations = 3, time = 3)
-@Measurement(iterations = 5, time = 5)
+@Warmup(iterations = 4, time = 8)
+@Measurement(iterations = 5, time = 20)
@Fork(
value = 1,
jvmArgsPrepend = {"--add-modules=jdk.unsupported"})
@@ -92,7 +91,7 @@ public class GroupVIntBenchmark {
IndexInput mmapGVIntIn;
IndexInput nioGVIntIn;
IndexInput mmapVIntIn;
- ByteBuffersDataInput byteBuffersGVIntIn;
+ IndexInput byteBuffersGVIntIn;
ByteArrayDataInput byteArrayVIntIn;
ByteArrayDataInput byteArrayGVIntIn;
@@ -125,9 +124,11 @@ public class GroupVIntBenchmark {
}
void initByteBuffersInput(int[] docs) throws Exception {
- ByteBuffersDataOutput buffer = new ByteBuffersDataOutput();
- buffer.writeGroupVInts(docs, docs.length);
- byteBuffersGVIntIn = buffer.toDataInput();
+ Directory dir = new ByteBuffersDirectory();
+ IndexOutput out = dir.createOutput("gvint", IOContext.DEFAULT);
+ out.writeGroupVInts(docs, docs.length);
+ out.close();
+ byteBuffersGVIntIn = dir.openInput("gvint", IOContext.DEFAULT);
}
void initMMapInput(int[] docs) throws Exception {
@@ -145,16 +146,6 @@ public class GroupVIntBenchmark {
mmapVIntIn = dir.openInput("vint", IOContext.DEFAULT);
}
- private void readGroupVIntsBaseline(DataInput in, int[] dst, int limit) throws IOException {
- int i;
- for (i = 0; i <= limit - 4; i += 4) {
- GroupVIntUtil.readGroupVInt(in, dst, i);
- }
- for (; i < limit; ++i) {
- dst[i] = in.readVInt();
- }
- }
-
@Setup(Level.Trial)
public void init() throws Exception {
Random r = new Random(0);
@@ -193,7 +184,7 @@ public class GroupVIntBenchmark {
@Benchmark
public void benchMMapDirectoryInputs_readGroupVIntBaseline(Blackhole bh) throws IOException {
mmapGVIntIn.seek(0);
- this.readGroupVIntsBaseline(mmapGVIntIn, values, size);
+ GroupVIntUtil.readGroupVInts$Baseline(mmapGVIntIn, values, size);
bh.consume(values);
}
@@ -223,7 +214,7 @@ public class GroupVIntBenchmark {
@Benchmark
public void benchNIOFSDirectoryInputs_readGroupVIntBaseline(Blackhole bh) throws IOException {
nioGVIntIn.seek(0);
- this.readGroupVIntsBaseline(nioGVIntIn, values, size);
+ GroupVIntUtil.readGroupVInts$Baseline(nioGVIntIn, values, size);
bh.consume(values);
}
@@ -237,7 +228,7 @@ public class GroupVIntBenchmark {
@Benchmark
public void benchByteBuffersIndexInput_readGroupVIntBaseline(Blackhole bh) throws IOException {
byteBuffersGVIntIn.seek(0);
- this.readGroupVIntsBaseline(byteBuffersGVIntIn, values, size);
+ GroupVIntUtil.readGroupVInts$Baseline(byteBuffersGVIntIn, values, size);
bh.consume(values);
}
diff --git a/lucene/core/src/java/org/apache/lucene/store/BufferedIndexInput.java b/lucene/core/src/java/org/apache/lucene/store/BufferedIndexInput.java
index cd47500a2df..6ac73d78d29 100644
--- a/lucene/core/src/java/org/apache/lucene/store/BufferedIndexInput.java
+++ b/lucene/core/src/java/org/apache/lucene/store/BufferedIndexInput.java
@@ -20,7 +20,6 @@ import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import org.apache.lucene.util.GroupVIntUtil;
/** Base implementation class for buffered {@link IndexInput}. */
public abstract class BufferedIndexInput extends IndexInput implements RandomAccessInput {
@@ -150,16 +149,6 @@ public abstract class BufferedIndexInput extends IndexInput implements RandomAcc
}
}
- @Override
- public void readGroupVInt(int[] dst, int offset) throws IOException {
- final int len =
- GroupVIntUtil.readGroupVInt(
- this, buffer.remaining(), p -> buffer.getInt((int) p), buffer.position(), dst, offset);
- if (len > 0) {
- buffer.position(buffer.position() + len);
- }
- }
-
@Override
public final long readLong() throws IOException {
if (Long.BYTES <= buffer.remaining()) {
diff --git a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDataInput.java b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDataInput.java
index dee5c8e3a73..3213ee373e5 100644
--- a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDataInput.java
+++ b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersDataInput.java
@@ -29,7 +29,6 @@ import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import org.apache.lucene.util.Accountable;
-import org.apache.lucene.util.GroupVIntUtil;
import org.apache.lucene.util.RamUsageEstimator;
/**
@@ -203,25 +202,6 @@ public final class ByteBuffersDataInput extends DataInput
}
}
- @Override
- public void readGroupVInt(int[] dst, int offset) throws IOException {
- final ByteBuffer block = blocks[blockIndex(pos)];
- final int blockOffset = blockOffset(pos);
- // We MUST save the return value to local variable, could not use pos += readGroupVInt(...).
- // because `pos +=` in java will move current value(not address) of pos to register first,
- // then call the function, but we will update pos value in function via readByte(), then
- // `pos +=` will use an old pos value plus return value, thereby missing 1 byte.
- final int len =
- GroupVIntUtil.readGroupVInt(
- this,
- block.limit() - blockOffset,
- p -> block.getInt((int) p),
- blockOffset,
- dst,
- offset);
- pos += len;
- }
-
@Override
public long length() {
return length;
diff --git a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersIndexInput.java b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersIndexInput.java
index 959f429ecfc..839636e7ed7 100644
--- a/lucene/core/src/java/org/apache/lucene/store/ByteBuffersIndexInput.java
+++ b/lucene/core/src/java/org/apache/lucene/store/ByteBuffersIndexInput.java
@@ -205,12 +205,6 @@ public final class ByteBuffersIndexInput extends IndexInput implements RandomAcc
in.readLongs(dst, offset, length);
}
- @Override
- public void readGroupVInt(int[] dst, int offset) throws IOException {
- ensureOpen();
- in.readGroupVInt(dst, offset);
- }
-
@Override
public IndexInput clone() {
ensureOpen();
diff --git a/lucene/core/src/java/org/apache/lucene/store/DataInput.java b/lucene/core/src/java/org/apache/lucene/store/DataInput.java
index e70ad295cfd..55bd781aee6 100644
--- a/lucene/core/src/java/org/apache/lucene/store/DataInput.java
+++ b/lucene/core/src/java/org/apache/lucene/store/DataInput.java
@@ -27,7 +27,6 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.lucene.util.BitUtil;
-import org.apache.lucene.util.GroupVIntUtil;
/**
* Abstract base class for performing read operations of Lucene's low-level data types.
@@ -99,16 +98,6 @@ public abstract class DataInput implements Cloneable {
return ((b4 & 0xFF) << 24) | ((b3 & 0xFF) << 16) | ((b2 & 0xFF) << 8) | (b1 & 0xFF);
}
- /**
- * Override if you have an efficient implementation. In general this is when the input supports
- * random access.
- *
- * @lucene.experimental
- */
- public void readGroupVInt(int[] dst, int offset) throws IOException {
- GroupVIntUtil.readGroupVInt(this, dst, offset);
- }
-
/**
* Reads an int stored in variable-length format. Reads between one and five bytes. Smaller values
* take fewer bytes. Negative numbers are supported, but should be avoided.
diff --git a/lucene/core/src/java/org/apache/lucene/store/MemorySegmentIndexInput.java b/lucene/core/src/java/org/apache/lucene/store/MemorySegmentIndexInput.java
index e9bb1dc817a..11a1a7015eb 100644
--- a/lucene/core/src/java/org/apache/lucene/store/MemorySegmentIndexInput.java
+++ b/lucene/core/src/java/org/apache/lucene/store/MemorySegmentIndexInput.java
@@ -31,7 +31,6 @@ import java.util.function.Function;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BitUtil;
import org.apache.lucene.util.Constants;
-import org.apache.lucene.util.GroupVIntUtil;
import org.apache.lucene.util.IOConsumer;
/**
@@ -429,23 +428,6 @@ abstract class MemorySegmentIndexInput extends IndexInput implements MemorySegme
}
}
- @Override
- public void readGroupVInt(int[] dst, int offset) throws IOException {
- try {
- final int len =
- GroupVIntUtil.readGroupVInt(
- this,
- curSegment.byteSize() - curPosition,
- p -> curSegment.get(LAYOUT_LE_INT, p),
- curPosition,
- dst,
- offset);
- curPosition += len;
- } catch (NullPointerException | IllegalStateException e) {
- throw alreadyClosed(e);
- }
- }
-
@Override
public void readBytes(long pos, byte[] b, int offset, int len) throws IOException {
try {
diff --git a/lucene/core/src/java/org/apache/lucene/util/GroupVIntUtil.java b/lucene/core/src/java/org/apache/lucene/util/GroupVIntUtil.java
index a1917d87a17..f3b19c519b1 100644
--- a/lucene/core/src/java/org/apache/lucene/util/GroupVIntUtil.java
+++ b/lucene/core/src/java/org/apache/lucene/util/GroupVIntUtil.java
@@ -19,6 +19,8 @@ package org.apache.lucene.util;
import java.io.IOException;
import org.apache.lucene.store.DataInput;
import org.apache.lucene.store.DataOutput;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.RandomAccessInput;
/**
* This class contains utility methods and constants for group varint
@@ -26,8 +28,8 @@ import org.apache.lucene.store.DataOutput;
* @lucene.internal
*/
public final class GroupVIntUtil {
- // the maximum length of a single group-varint is 4 integers + 1 byte flag.
- public static final int MAX_LENGTH_PER_GROUP = 17;
+ // the maximum length of a single group-varint is 1 byte flag and 4 integers.
+ public static final int MAX_LENGTH_PER_GROUP = Byte.BYTES + 4 * Integer.BYTES;
private static final int[] INT_MASKS = new int[] {0xFF, 0xFFFF, 0xFFFFFF, ~0};
@@ -41,7 +43,7 @@ public final class GroupVIntUtil {
public static void readGroupVInts(DataInput in, int[] dst, int limit) throws IOException {
int i;
for (i = 0; i <= limit - 4; i += 4) {
- in.readGroupVInt(dst, i);
+ readGroupVInt(in, dst, i);
}
for (; i < limit; ++i) {
dst[i] = in.readVInt();
@@ -56,7 +58,54 @@ public final class GroupVIntUtil {
* @param dst the array to read ints into.
* @param offset the offset in the array to start storing ints.
*/
- public static void readGroupVInt(DataInput in, int[] dst, int offset) throws IOException {
+ private static void readGroupVInt(DataInput in, int[] dst, int offset) throws IOException {
+ final int flag = in.readByte() & 0xFF;
+
+ final int n1Minus1 = flag >> 6;
+ final int n2Minus1 = (flag >> 4) & 0x03;
+ final int n3Minus1 = (flag >> 2) & 0x03;
+ final int n4Minus1 = flag & 0x03;
+
+ // if our DataInput implements RandomAccessInput for absolute access and IndexInput for seeking,
+ // we use a branch-less implementation:
+ if (in instanceof RandomAccessInput rin && in instanceof IndexInput iin) {
+ long pos = iin.getFilePointer();
+ if (iin.length() - pos >= 4 * Integer.BYTES) {
+ dst[offset] = rin.readInt(pos) & INT_MASKS[n1Minus1];
+ pos += 1 + n1Minus1;
+ dst[offset + 1] = rin.readInt(pos) & INT_MASKS[n2Minus1];
+ pos += 1 + n2Minus1;
+ dst[offset + 2] = rin.readInt(pos) & INT_MASKS[n3Minus1];
+ pos += 1 + n3Minus1;
+ dst[offset + 3] = rin.readInt(pos) & INT_MASKS[n4Minus1];
+ pos += 1 + n4Minus1;
+
+ iin.seek(pos);
+ return;
+ }
+ }
+
+ // fall-through: default impl
+ dst[offset] = readIntInGroup(in, n1Minus1);
+ dst[offset + 1] = readIntInGroup(in, n2Minus1);
+ dst[offset + 2] = readIntInGroup(in, n3Minus1);
+ dst[offset + 3] = readIntInGroup(in, n4Minus1);
+ }
+
+ /** DO not use! Only visible for benchmarking purposes! */
+ public static void readGroupVInts$Baseline(DataInput in, int[] dst, int limit)
+ throws IOException {
+ int i;
+ for (i = 0; i <= limit - 4; i += 4) {
+ readGroupVInt$Baseline(in, dst, i);
+ }
+ for (; i < limit; ++i) {
+ dst[i] = in.readVInt();
+ }
+ }
+
+ private static void readGroupVInt$Baseline(DataInput in, int[] dst, int offset)
+ throws IOException {
final int flag = in.readByte() & 0xFF;
final int n1Minus1 = flag >> 6;
@@ -83,55 +132,6 @@ public final class GroupVIntUtil {
}
}
- /**
- * Provides an abstraction for read int values, so that decoding logic can be reused in different
- * DataInput.
- */
- @FunctionalInterface
- public static interface IntReader {
- int read(long v);
- }
-
- /**
- * Faster implementation of read single group, It read values from the buffer that would not cross
- * boundaries.
- *
- * @param in the input to use to read data.
- * @param remaining the number of remaining bytes allowed to read for current block/segment.
- * @param reader the supplier of read int.
- * @param pos the start pos to read from the reader.
- * @param dst the array to read ints into.
- * @param offset the offset in the array to start storing ints.
- * @return the number of bytes read excluding the flag. this indicates the number of positions
- * should to be increased for caller, it is 0 or positive number and less than {@link
- * #MAX_LENGTH_PER_GROUP}
- */
- public static int readGroupVInt(
- DataInput in, long remaining, IntReader reader, long pos, int[] dst, int offset)
- throws IOException {
- if (remaining < MAX_LENGTH_PER_GROUP) {
- readGroupVInt(in, dst, offset);
- return 0;
- }
- final int flag = in.readByte() & 0xFF;
- final long posStart = ++pos; // exclude the flag bytes, the position has updated via readByte().
- final int n1Minus1 = flag >> 6;
- final int n2Minus1 = (flag >> 4) & 0x03;
- final int n3Minus1 = (flag >> 2) & 0x03;
- final int n4Minus1 = flag & 0x03;
-
- // This code path has fewer conditionals and tends to be significantly faster in benchmarks
- dst[offset] = reader.read(pos) & INT_MASKS[n1Minus1];
- pos += 1 + n1Minus1;
- dst[offset + 1] = reader.read(pos) & INT_MASKS[n2Minus1];
- pos += 1 + n2Minus1;
- dst[offset + 2] = reader.read(pos) & INT_MASKS[n3Minus1];
- pos += 1 + n3Minus1;
- dst[offset + 3] = reader.read(pos) & INT_MASKS[n4Minus1];
- pos += 1 + n4Minus1;
- return (int) (pos - posStart);
- }
-
private static int numBytes(int v) {
// | 1 to return 1 when v = 0
return Integer.BYTES - (Integer.numberOfLeadingZeros(v | 1) >> 3);