diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 96b4bf5eab75..4bc5cd95e684 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -30,6 +30,10 @@ Improvements system property to increase the set of Java versions that Panama Vectorization will provide optimized implementations for. (Chris Hegarty) +* GITHUB#266: TieredMergePolicy now allows merging up to maxMergeAtOnce + segments for merges below the floor segment size, even if maxMergeAtOnce is + bigger than segsPerTier. (Adrien Grand) + Optimizations --------------------- @@ -93,6 +97,12 @@ Optimizations * GITHUB#14023: Make JVM inlining decisions more predictable in our main queries. (Adrien Grand) +* GITHUB#14032: Speed up PostingsEnum when positions are requested. + (Adrien Grand) + +* GITHUB#14031: Ensure Panama float vector distance impls inlinable. + (Robert Muir, Chris Hegarty) + * GITHUB#14011: Reduce allocation rate in HNSW concurrent merge. (Viliam Durina) Bug Fixes diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene101/Lucene101PostingsReader.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene101/Lucene101PostingsReader.java index 9e79aaf71e15..d879a58b4ab7 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene101/Lucene101PostingsReader.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene101/Lucene101PostingsReader.java @@ -638,9 +638,13 @@ final class EverythingEnum extends AbstractPostingsEnum { final boolean indexHasPayloads; final boolean indexHasOffsetsOrPayloads; - private int freq; // freq we last read + private long freqFP; // offset of the freq block + private int position; // current position + // value of docBufferUpto on the last doc ID when positions have been read + private int posDocBufferUpto; + // how many positions "behind" we are; nextPosition must // skip these to "catch up": private int posPendingCount; @@ -662,6 +666,7 @@ final class EverythingEnum extends AbstractPostingsEnum { private boolean needsOffsets; // true if we actually need offsets private boolean needsPayloads; // true if we actually need payloads + private boolean needsPayloadsOrOffsets; public EverythingEnum(FieldInfo fieldInfo) throws IOException { super(fieldInfo); @@ -745,8 +750,11 @@ public PostingsEnum reset(IntBlockTermState termState, int flags) throws IOExcep lastPosBlockFP = posTermStartFP + termState.lastPosBlockOffset; } - this.needsOffsets = PostingsEnum.featureRequested(flags, PostingsEnum.OFFSETS); - this.needsPayloads = PostingsEnum.featureRequested(flags, PostingsEnum.PAYLOADS); + this.needsOffsets = + indexHasOffsets && PostingsEnum.featureRequested(flags, PostingsEnum.OFFSETS); + this.needsPayloads = + indexHasPayloads && PostingsEnum.featureRequested(flags, PostingsEnum.PAYLOADS); + this.needsPayloadsOrOffsets = this.needsPayloads || this.needsOffsets; level1BlockPosUpto = 0; level1BlockPayUpto = 0; @@ -758,8 +766,13 @@ public PostingsEnum reset(IntBlockTermState termState, int flags) throws IOExcep } @Override - public int freq() { - return freq; + public int freq() throws IOException { + if (freqFP != -1) { + docIn.seek(freqFP); + pforUtil.decode(docInUtil, freqBuffer); + freqFP = -1; + } + return freqBuffer[docBufferUpto - 1]; } private void refillDocs() throws IOException { @@ -768,11 +781,13 @@ private void refillDocs() throws IOException { if (left >= BLOCK_SIZE) { forDeltaUtil.decodeAndPrefixSum(docInUtil, prevDocID, docBuffer); - pforUtil.decode(docInUtil, freqBuffer); + freqFP = docIn.getFilePointer(); + PForUtil.skip(docIn); docCountUpto += BLOCK_SIZE; } else if (docFreq == 1) { docBuffer[0] = singletonDocID; freqBuffer[0] = (int) totalTermFreq; + freqFP = -1; docBuffer[1] = NO_MORE_DOCS; docCountUpto++; docBufferSize = 1; @@ -781,11 +796,13 @@ private void refillDocs() throws IOException { PostingsUtil.readVIntBlock(docIn, docBuffer, freqBuffer, left, indexHasFreq, true); prefixSum(docBuffer, left, prevDocID); docBuffer[left] = NO_MORE_DOCS; + freqFP = -1; docCountUpto += left; docBufferSize = left; } prevDocID = docBuffer[BLOCK_SIZE - 1]; docBufferUpto = 0; + posDocBufferUpto = 0; assert docBuffer[docBufferSize] == NO_MORE_DOCS; } @@ -846,6 +863,8 @@ private void moveToNextLevel0Block() throws IOException { payloadByteUpto = level0BlockPayUpto; } posBufferUpto = BLOCK_SIZE; + } else { + posPendingCount += sumOverRange(freqBuffer, posDocBufferUpto, BLOCK_SIZE); } if (docFreq - docCountUpto >= BLOCK_SIZE) { @@ -875,34 +894,23 @@ public int nextDoc() throws IOException { } this.doc = docBuffer[docBufferUpto]; - this.freq = freqBuffer[docBufferUpto]; docBufferUpto++; - posPendingCount += freq; - position = 0; - lastStartOffset = 0; return doc; } private void skipLevel0To(int target) throws IOException { + long posFP; + int posUpto; + long payFP; + int payUpto; + while (true) { prevDocID = level0LastDocID; - // If nextBlockPosFP is less than the current FP, it means that the block of positions for - // the first docs of the next block are already decoded. In this case we just accumulate - // frequencies into posPendingCount instead of seeking backwards and decoding the same pos - // block again. - if (level0PosEndFP >= posIn.getFilePointer()) { - posIn.seek(level0PosEndFP); - posPendingCount = level0BlockPosUpto; - if (indexHasOffsetsOrPayloads) { - assert level0PayEndFP >= payIn.getFilePointer(); - payIn.seek(level0PayEndFP); - payloadByteUpto = level0BlockPayUpto; - } - posBufferUpto = BLOCK_SIZE; - } else { - posPendingCount += sumOverRange(freqBuffer, docBufferUpto, BLOCK_SIZE); - } + posFP = level0PosEndFP; + posUpto = level0BlockPosUpto; + payFP = level0PayEndFP; + payUpto = level0BlockPayUpto; if (docFreq - docCountUpto >= BLOCK_SIZE) { docIn.readVLong(); // skip0 num bytes @@ -931,6 +939,23 @@ private void skipLevel0To(int target) throws IOException { break; } } + + // If nextBlockPosFP is less than the current FP, it means that the block of positions for + // the first docs of the next block are already decoded. In this case we just accumulate + // frequencies into posPendingCount instead of seeking backwards and decoding the same pos + // block again. + if (posFP >= posIn.getFilePointer()) { + posIn.seek(posFP); + posPendingCount = posUpto; + if (indexHasOffsetsOrPayloads) { + assert level0PayEndFP >= payIn.getFilePointer(); + payIn.seek(payFP); + payloadByteUpto = payUpto; + } + posBufferUpto = BLOCK_SIZE; + } else { + posPendingCount += sumOverRange(freqBuffer, posDocBufferUpto, BLOCK_SIZE); + } } @Override @@ -947,16 +972,12 @@ public int advance(int target) throws IOException { } int next = VectorUtil.findNextGEQ(docBuffer, target, docBufferUpto, docBufferSize); - posPendingCount += sumOverRange(freqBuffer, docBufferUpto, next + 1); - this.freq = freqBuffer[next]; this.docBufferUpto = next + 1; - position = 0; - lastStartOffset = 0; return this.doc = docBuffer[next]; } - private void skipPositions() throws IOException { + private void skipPositions(int freq) throws IOException { // Skip positions now: int toSkip = posPendingCount - freq; // if (DEBUG) { @@ -1003,41 +1024,45 @@ private void skipPositions() throws IOException { lastStartOffset = 0; } - private void refillPositions() throws IOException { - if (posIn.getFilePointer() == lastPosBlockFP) { - final int count = (int) (totalTermFreq % BLOCK_SIZE); - int payloadLength = 0; - int offsetLength = 0; - payloadByteUpto = 0; - for (int i = 0; i < count; i++) { - int code = posIn.readVInt(); - if (indexHasPayloads) { - if ((code & 1) != 0) { - payloadLength = posIn.readVInt(); - } - payloadLengthBuffer[i] = payloadLength; - posDeltaBuffer[i] = code >>> 1; - if (payloadLength != 0) { - if (payloadByteUpto + payloadLength > payloadBytes.length) { - payloadBytes = ArrayUtil.grow(payloadBytes, payloadByteUpto + payloadLength); - } - posIn.readBytes(payloadBytes, payloadByteUpto, payloadLength); - payloadByteUpto += payloadLength; + private void refillLastPositionBlock() throws IOException { + final int count = (int) (totalTermFreq % BLOCK_SIZE); + int payloadLength = 0; + int offsetLength = 0; + payloadByteUpto = 0; + for (int i = 0; i < count; i++) { + int code = posIn.readVInt(); + if (indexHasPayloads) { + if ((code & 1) != 0) { + payloadLength = posIn.readVInt(); + } + payloadLengthBuffer[i] = payloadLength; + posDeltaBuffer[i] = code >>> 1; + if (payloadLength != 0) { + if (payloadByteUpto + payloadLength > payloadBytes.length) { + payloadBytes = ArrayUtil.grow(payloadBytes, payloadByteUpto + payloadLength); } - } else { - posDeltaBuffer[i] = code; + posIn.readBytes(payloadBytes, payloadByteUpto, payloadLength); + payloadByteUpto += payloadLength; } + } else { + posDeltaBuffer[i] = code; + } - if (indexHasOffsets) { - int deltaCode = posIn.readVInt(); - if ((deltaCode & 1) != 0) { - offsetLength = posIn.readVInt(); - } - offsetStartDeltaBuffer[i] = deltaCode >>> 1; - offsetLengthBuffer[i] = offsetLength; + if (indexHasOffsets) { + int deltaCode = posIn.readVInt(); + if ((deltaCode & 1) != 0) { + offsetLength = posIn.readVInt(); } + offsetStartDeltaBuffer[i] = deltaCode >>> 1; + offsetLengthBuffer[i] = offsetLength; } - payloadByteUpto = 0; + } + payloadByteUpto = 0; + } + + private void refillPositions() throws IOException { + if (posIn.getFilePointer() == lastPosBlockFP) { + refillLastPositionBlock(); } else { pforUtil.decode(posInUtil, posDeltaBuffer); @@ -1054,8 +1079,7 @@ private void refillPositions() throws IOException { // this works, because when writing a vint block we always force the first length to be // written PForUtil.skip(payIn); // skip over lengths - int numBytes = payIn.readVInt(); // read length of payloadBytes - payIn.seek(payIn.getFilePointer() + numBytes); // skip over payloadBytes + payIn.skipBytes(payIn.readVInt()); // skip over payloadBytes } payloadByteUpto = 0; } @@ -1074,13 +1098,40 @@ private void refillPositions() throws IOException { } } + private void accumulatePayloadAndOffsets() { + if (needsPayloads) { + payloadLength = payloadLengthBuffer[posBufferUpto]; + payload.bytes = payloadBytes; + payload.offset = payloadByteUpto; + payload.length = payloadLength; + payloadByteUpto += payloadLength; + } + + if (needsOffsets) { + startOffset = lastStartOffset + offsetStartDeltaBuffer[posBufferUpto]; + endOffset = startOffset + offsetLengthBuffer[posBufferUpto]; + lastStartOffset = startOffset; + } + } + @Override public int nextPosition() throws IOException { - assert posPendingCount > 0; + if (posDocBufferUpto != docBufferUpto) { + int freq = freq(); // triggers lazy decoding of freqs + + // First position that is being read on this doc. + posPendingCount += sumOverRange(freqBuffer, posDocBufferUpto, docBufferUpto); + posDocBufferUpto = docBufferUpto; + + assert posPendingCount > 0; + + if (posPendingCount > freq) { + skipPositions(freq); + posPendingCount = freq; + } - if (posPendingCount > freq) { - skipPositions(); - posPendingCount = freq; + position = 0; + lastStartOffset = 0; } if (posBufferUpto == BLOCK_SIZE) { @@ -1089,18 +1140,8 @@ public int nextPosition() throws IOException { } position += posDeltaBuffer[posBufferUpto]; - if (indexHasPayloads) { - payloadLength = payloadLengthBuffer[posBufferUpto]; - payload.bytes = payloadBytes; - payload.offset = payloadByteUpto; - payload.length = payloadLength; - payloadByteUpto += payloadLength; - } - - if (indexHasOffsets) { - startOffset = lastStartOffset + offsetStartDeltaBuffer[posBufferUpto]; - endOffset = startOffset + offsetLengthBuffer[posBufferUpto]; - lastStartOffset = startOffset; + if (needsPayloadsOrOffsets) { + accumulatePayloadAndOffsets(); } posBufferUpto++; @@ -1110,17 +1151,23 @@ public int nextPosition() throws IOException { @Override public int startOffset() { + if (needsOffsets == false) { + return -1; + } return startOffset; } @Override public int endOffset() { + if (needsOffsets == false) { + return -1; + } return endOffset; } @Override public BytesRef getPayload() { - if (payloadLength == 0) { + if (needsPayloads == false || payloadLength == 0) { return null; } else { return payload; @@ -1466,9 +1513,13 @@ final class BlockImpactsPostingsEnum extends BlockImpactsEnum { final boolean indexHasPayloads; final boolean indexHasOffsetsOrPayloads; - private int freq; // freq we last read + private long freqFP; // offset of the freq block + private int position; // current position + // value of docBufferUpto on the last doc ID when positions have been read + private int posDocBufferUpto; + // how many positions "behind" we are; nextPosition must // skip these to "catch up": private int posPendingCount; @@ -1516,8 +1567,13 @@ public BlockImpactsPostingsEnum(FieldInfo fieldInfo, IntBlockTermState termState } @Override - public int freq() { - return freq; + public int freq() throws IOException { + if (freqFP != -1) { + docIn.seek(freqFP); + pforUtil.decode(docInUtil, freqBuffer); + freqFP = -1; + } + return freqBuffer[docBufferUpto - 1]; } private void refillDocs() throws IOException { @@ -1526,24 +1582,30 @@ private void refillDocs() throws IOException { if (left >= BLOCK_SIZE) { forDeltaUtil.decodeAndPrefixSum(docInUtil, prevDocID, docBuffer); - pforUtil.decode(docInUtil, freqBuffer); + freqFP = docIn.getFilePointer(); + PForUtil.skip(docIn); docCountUpto += BLOCK_SIZE; } else if (docFreq == 1) { docBuffer[0] = singletonDocID; freqBuffer[0] = (int) totalTermFreq; + freqFP = -1; docBuffer[1] = NO_MORE_DOCS; docCountUpto++; docBufferSize = 1; + } else { // Read vInts: PostingsUtil.readVIntBlock(docIn, docBuffer, freqBuffer, left, indexHasFreq, true); prefixSum(docBuffer, left, prevDocID); docBuffer[left] = NO_MORE_DOCS; + freqFP = -1; docCountUpto += left; docBufferSize = left; + freqFP = -1; } prevDocID = docBuffer[BLOCK_SIZE - 1]; docBufferUpto = 0; + posDocBufferUpto = 0; assert docBuffer[docBufferSize] == NO_MORE_DOCS; } @@ -1585,20 +1647,14 @@ private void skipLevel1To(int target) throws IOException { } private void skipLevel0To(int target) throws IOException { + long posFP; + int posUpto; + while (true) { prevDocID = level0LastDocID; - // If nextBlockPosFP is less than the current FP, it means that the block of positions for - // the first docs of the next block are already decoded. In this case we just accumulate - // frequencies into posPendingCount instead of seeking backwards and decoding the same pos - // block again. - if (level0PosEndFP >= posIn.getFilePointer()) { - posIn.seek(level0PosEndFP); - posPendingCount = level0BlockPosUpto; - posBufferUpto = BLOCK_SIZE; - } else { - posPendingCount += sumOverRange(freqBuffer, docBufferUpto, BLOCK_SIZE); - } + posFP = level0PosEndFP; + posUpto = level0BlockPosUpto; if (docFreq - docCountUpto >= BLOCK_SIZE) { docIn.readVLong(); // skip0 num bytes @@ -1631,6 +1687,18 @@ private void skipLevel0To(int target) throws IOException { break; } } + + // If nextBlockPosFP is less than the current FP, it means that the block of positions for + // the first docs of the next block are already decoded. In this case we just accumulate + // frequencies into posPendingCount instead of seeking backwards and decoding the same pos + // block again. + if (posFP >= posIn.getFilePointer()) { + posIn.seek(posFP); + posPendingCount = posUpto; + posBufferUpto = BLOCK_SIZE; + } else { + posPendingCount += sumOverRange(freqBuffer, posDocBufferUpto, BLOCK_SIZE); + } } @Override @@ -1660,30 +1728,25 @@ public int nextDoc() throws IOException { } doc = docBuffer[docBufferUpto]; - freq = freqBuffer[docBufferUpto]; - posPendingCount += freq; docBufferUpto++; - position = 0; return this.doc; } @Override public int advance(int target) throws IOException { - advanceShallow(target); - if (needsRefilling) { + if (target > level0LastDocID || needsRefilling) { + advanceShallow(target); + assert needsRefilling; refillDocs(); needsRefilling = false; } int next = VectorUtil.findNextGEQ(docBuffer, target, docBufferUpto, docBufferSize); - posPendingCount += sumOverRange(freqBuffer, docBufferUpto, next + 1); - freq = freqBuffer[next]; docBufferUpto = next + 1; - position = 0; return this.doc = docBuffer[next]; } - private void skipPositions() throws IOException { + private void skipPositions(int freq) throws IOException { // Skip positions now: int toSkip = posPendingCount - freq; // if (DEBUG) { @@ -1703,8 +1766,6 @@ private void skipPositions() throws IOException { refillPositions(); posBufferUpto = toSkip; } - - position = 0; } private void refillPositions() throws IOException { @@ -1739,11 +1800,21 @@ private void refillPositions() throws IOException { @Override public int nextPosition() throws IOException { - assert posPendingCount > 0; + if (posDocBufferUpto != docBufferUpto) { + int freq = freq(); // triggers lazy decoding of freqs + + // First position that is being read on this doc. + posPendingCount += sumOverRange(freqBuffer, posDocBufferUpto, docBufferUpto); + posDocBufferUpto = docBufferUpto; + + assert posPendingCount > 0; + + if (posPendingCount > freq) { + skipPositions(freq); + posPendingCount = freq; + } - if (posPendingCount > freq) { - skipPositions(); - posPendingCount = freq; + position = 0; } if (posBufferUpto == BLOCK_SIZE) { diff --git a/lucene/core/src/java/org/apache/lucene/index/TieredMergePolicy.java b/lucene/core/src/java/org/apache/lucene/index/TieredMergePolicy.java index 2fb0c0783a2e..6447b7305ef2 100644 --- a/lucene/core/src/java/org/apache/lucene/index/TieredMergePolicy.java +++ b/lucene/core/src/java/org/apache/lucene/index/TieredMergePolicy.java @@ -85,7 +85,7 @@ public class TieredMergePolicy extends MergePolicy { public static final double DEFAULT_NO_CFS_RATIO = 0.1; // User-specified maxMergeAtOnce. In practice we always take the min of its - // value and segsPerTier to avoid suboptimal merging. + // value and segsPerTier for segments above the floor size to avoid suboptimal merging. private int maxMergeAtOnce = 10; private long maxMergedSegmentBytes = 5 * 1024 * 1024 * 1024L; @@ -100,7 +100,13 @@ public TieredMergePolicy() { super(DEFAULT_NO_CFS_RATIO, MergePolicy.DEFAULT_MAX_CFS_SEGMENT_SIZE); } - /** Maximum number of segments to be merged at a time during "normal" merging. Default is 10. */ + /** + * Maximum number of segments to be merged at a time during "normal" merging. Default is 10. + * + *
NOTE: Merges above the {@link #setFloorSegmentMB(double) floor segment size} also
+ * bound the number of merged segments by {@link #setSegmentsPerTier(double) the number of
+ * segments per tier}.
+ */
public TieredMergePolicy setMaxMergeAtOnce(int v) {
if (v < 2) {
throw new IllegalArgumentException("maxMergeAtOnce must be > 1 (got " + v + ")");
@@ -557,46 +563,46 @@ private MergeSpecification doFindMerges(
for (int startIdx = 0; startIdx < sortedEligible.size(); startIdx++) {
- long totAfterMergeBytes = 0;
-
final List