diff --git a/lucene/core/src/java/org/apache/lucene/util/BitUtil.java b/lucene/core/src/java/org/apache/lucene/util/BitUtil.java index 850cb2618093..2389e3b0485d 100644 --- a/lucene/core/src/java/org/apache/lucene/util/BitUtil.java +++ b/lucene/core/src/java/org/apache/lucene/util/BitUtil.java @@ -303,4 +303,12 @@ public static int zigZagDecode(int i) { public static long zigZagDecode(long l) { return ((l >>> 1) ^ -(l & 1)); } + + /** + * Return true if, and only if, the provided integer - treated as an unsigned integer - is either + * 0 or a power of two. + */ + public static boolean isZeroOrPowerOfTwo(int x) { + return (x & (x - 1)) == 0; + } } diff --git a/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInput.java b/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInput.java index a0e7e60e99e6..e9f650e6be69 100644 --- a/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInput.java +++ b/lucene/core/src/java21/org/apache/lucene/store/MemorySegmentIndexInput.java @@ -26,6 +26,7 @@ import java.util.Objects; import java.util.Optional; import org.apache.lucene.util.ArrayUtil; +import org.apache.lucene.util.BitUtil; import org.apache.lucene.util.GroupVIntUtil; /** @@ -57,6 +58,7 @@ abstract class MemorySegmentIndexInput extends IndexInput implements RandomAcces MemorySegment curSegment; // redundant for speed: segments[curSegmentIndex], also marker if closed! long curPosition; // relative to curSegment, not globally + int consecutivePrefetchHitCount; public static MemorySegmentIndexInput newInstance( String resourceDescription, @@ -314,13 +316,22 @@ public void seek(long pos) throws IOException { @Override public void prefetch(long offset, long length) throws IOException { + if (NATIVE_ACCESS.isEmpty()) { + return; + } + ensureOpen(); Objects.checkFromIndexSize(offset, length, length()); - if (NATIVE_ACCESS.isEmpty()) { + if (BitUtil.isZeroOrPowerOfTwo(consecutivePrefetchHitCount++) == false) { + // We've had enough consecutive hits on the page cache that this number is neither zero nor a + // power of two. There is a good chance that a good chunk of this index input is cached in + // physical memory. Let's skip the overhead of the madvise system call, we'll be trying again + // on the next power of two of the counter. return; } + final NativeAccess nativeAccess = NATIVE_ACCESS.get(); try { @@ -344,7 +355,11 @@ public void prefetch(long offset, long length) throws IOException { } final MemorySegment prefetchSlice = segment.asSlice(offset, length); - nativeAccess.madviseWillNeed(prefetchSlice); + if (prefetchSlice.isLoaded() == false) { + // We have a cache miss on at least one page, let's reset the counter. + consecutivePrefetchHitCount = 0; + nativeAccess.madviseWillNeed(prefetchSlice); + } } catch ( @SuppressWarnings("unused") IndexOutOfBoundsException e) { diff --git a/lucene/core/src/test/org/apache/lucene/util/TestBitUtil.java b/lucene/core/src/test/org/apache/lucene/util/TestBitUtil.java new file mode 100644 index 000000000000..c6c6823fcf8f --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/util/TestBitUtil.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.lucene.util; + +import org.apache.lucene.tests.util.LuceneTestCase; + +public class TestBitUtil extends LuceneTestCase { + + public void testIsZeroOrPowerOfTwo() { + assertTrue(BitUtil.isZeroOrPowerOfTwo(0)); + for (int shift = 0; shift <= 31; ++shift) { + assertTrue(BitUtil.isZeroOrPowerOfTwo(1 << shift)); + } + assertFalse(BitUtil.isZeroOrPowerOfTwo(3)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(5)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(6)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(7)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(9)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(Integer.MAX_VALUE)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(Integer.MAX_VALUE + 2)); + assertFalse(BitUtil.isZeroOrPowerOfTwo(-1)); + } +}