Skip to content

Commit

Permalink
Reduce the overhead of IndexInput#prefetch when data is cached in R…
Browse files Browse the repository at this point in the history
…AM. (apache#13381)

As Robert pointed out and benchmarks confirmed, there is some (small) overhead
to calling `madvise` via the foreign function API, benchmarks suggest it is in
the order of 1-2us. This is not much for a single call, but may become
non-negligible across many calls. Until now, we only looked into using
prefetch() for terms, skip data and postings start pointers which are a single
prefetch() operation per segment per term.

But we may want to start using it in cases that could result into more calls to
`madvise`, e.g. if we start using it for stored fields and a user requests 10k
documents. In apache#13337, Robert wondered if we could take advantage of `mincore()`
to reduce the overhead of `IndexInput#prefetch()`, which is what this PR is
doing via `MemorySegment#isLoaded()`.

`IndexInput#prefetch` tracks consecutive hits on the page cache and calls
`madvise` less and less frequently under the hood as the number of consecutive
cache hits increases.
  • Loading branch information
jpountz authored and shatejas committed Nov 17, 2024
1 parent befa844 commit 2c379c2
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
8 changes: 8 additions & 0 deletions lucene/core/src/java/org/apache/lucene/util/BitUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -59,6 +60,7 @@ abstract class MemorySegmentIndexInput extends IndexInput
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,
Expand Down Expand Up @@ -327,13 +329,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 {
Expand All @@ -357,7 +368,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) {
Expand Down
37 changes: 37 additions & 0 deletions lucene/core/src/test/org/apache/lucene/util/TestBitUtil.java
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit 2c379c2

Please sign in to comment.