You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@lucene.apache.org by "neoremind (via GitHub)" <gi...@apache.org> on 2026/06/20 15:54:48 UTC
[PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
neoremind opened a new pull request, #16279:
URL: https://github.com/apache/lucene/pull/16279
Adds JMH benchmarks to compare read I/O strategies in memory constrained scenario, related to #16044.
I/O strategies tested:
- mmap no madvise
- mmap + MADV_RANDOM
- mmap + MADV_RANDOM + MADV_WILLNEED
- FFI pread(2) via Panama
- FileChannel + DirectByteBuffer (simulates `NIOFSDirectory`)
- FileChannel + HeapByteBuffer
- O_DIRECT
Thread counts: 1, 4, 8, 16.
How to run
```
dd if=/dev/urandom of=/path/to/pread-bench-16G.dat bs=1M count=16384
./gradlew -p lucene/benchmark-jmh assemble
BENCH_FILE=/path/to/pread-bench-16G.dat BENCH_FILE_SIZE_MIB=16384 BENCH_DROP_CACHES=false \
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-*.jar \
RandomReadIOBenchmark
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "neoremind (via GitHub)" <gi...@apache.org>.
neoremind commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-4913556772
Thank you @mikemccand! I've updated the benchmark results and analysis above, please see my comments inline below.
> I love that we are building tooling for benchmarking cold/warm cases -- they are so tricky to properly test because at a "whole search system" level you should replay query traffic with accurate actual production arrival times (ideally, or simulate w/ Poisson process) for real measures. I'd love to see Lucene eventually be able to saturate modern NVMe SSDs when executing a single query on all CPU cores on a larger-than-RAM index but I don't think we are there yet (we don't have enough async/concurrent/prefetch IO? and we don't strongly separate dependent IO into their own paths so non-dependent IOPs don't block one another).
I fully agree on the vision. I think it's genuinely possible to saturate I/O as long as we interleave computation and I/O with parallelization. I've practiced this strategy before, during the 1st PolarDB database performance competition in 2018, I implemented a KV engine saturating an Intel Optane SSD with 2.2 GB/s write and 2.5 GB/s random read (see [write-up](https://github.com/neoremind/2018-polar-race/blob/master/building-a-kv-engine-to-saturate-optane-ssd-java.md)). I also built a [fast InnoDB checksum tool](https://github.com/neoremind/fast_innochecksum) in C++ that fully exploits hardware by separating I/O from computation, using big-block I/O to amortize kernel overhead, and making full use of multi-core. But indeed, these are either contest simplified workloads or easy utilities, Lucene's query path is much much more complex. One thing I learned from @jimczi is how `MMapDirectory`'s prefetch is already working good now in `StoredFields#prefetch(docID)` prefetching, and `K
nnVectorValues#prefetch(int[] ords, int numOrds)` in `PrefetchableFlatVectorScorer` does the same for vector search, and more.
> I also want to test if Lucene is anywhere near the linux kernel bottleneck limit on page faults / sec. This is a potential risk we face with memory-mapped IO (described here, but I think there's been good progress in very modern kernels, something about maple trees -- aha, here!). I think this benchmark should be able to tease out that bottleneck by comparing mmap IO vs NIO cold concurrent IOPs.
Thanks for sharing these. The per-VMA lock optimization should be available after Linux 6.4, but my EC2 hosts are still running old kernels. I will give it try once available.
With the data shows above, mmap is the most powerful one when data is hot in RAM, but pread/NIOFS outperforms plain mmap under memory pressure with some degree of cold load (case 2: file size nearly RAM, case 3: 50% cold reads, case 4: almost all cold). On NVME at T16 in the all-cold case: plain mmap can only do 0.94 ops/ms while pread reaches 4.65 ops/ms, however, mmap (RANDOM) + batched WILLNEED prefetch (simulate QD=16) goes to 6.64 ops/ms, beating both pread and NIOFS. This somehow shows that I could hit the mmap bottleneck (page table contention, TLB shootdowns, mmap lock contention) on the cold path, and batched prefetch makes mmap more performant by pipelining I/O and computation.
> How do you simulate the memory pressure? I see you drop OS's IO cache (at the start of each run?). Is it just that the user is expected to test on a large enough file exceeding their free RAM. You could also use @rmuir's awesome ramhog.c in luceneutil: https://github.com/mikemccand/luceneutil/blob/main/src/python/ramhog.c ... it just allocates and pins that amount of RAM so OS cannot use it anymore.
I use files that go beyond available RAM, 32G/64G file on a 32G host. Just for the "almost all cold" case 4, I drop page cache before each JMH iteration, others no, just let program contended on memory. The dropping page cache is a switch as param. I also tried using cgroup to limit memory, but ran into a tricky issue, dropping page cache within the cgroup doesn't fully remove pages from host memory, anyway, I didn't go deeper the why. So, I ended up using a host with less RAM instead. I think @rmuir's `ramhog.c` would be a cleaner approach for memory control, I will take a look.
> Have you tried comparing this benchy to other IO benchy tools e.g. fio? Then we could gain some confidence in our benchy... (or, maybe lose some confidence in fio!).
Yes, please see the fio section in the results above. In short: JMH numbers match fio with tiny overhead.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-5053084439
This PR has not had activity in the past 2 weeks, labeling it as stale. If the PR is waiting for review, notify the dev@lucene.apache.org list. Thank you for your contribution!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "mikemccand (via GitHub)" <gi...@apache.org>.
mikemccand commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-4903092429
I love that we are building tooling for benchmarking cold/warm cases -- they are so tricky to properly test because at a "whole search system" level you should replay query traffic with accurate actual production arrival times (ideally, or simulate w/ [Poisson process](https://en.wikipedia.org/wiki/Poisson_point_process)) for real measures. I'd love to see Lucene eventually be able to saturate modern NVMe SSDs when executing a single query on all CPU cores on a larger-than-RAM index but I don't think we are there yet (we don't have enough async/concurrent/prefetch IO? and we don't strongly separate dependent IO into their own paths so non-dependent IOPs don't block one another).
I also want to test if Lucene is anywhere near the linux kernel bottleneck limit on page faults / sec. This is a potential risk we face with memory-mapped IO ([described here](https://lwn.net/Articles/906852/), but I think there's been good progress in very modern kernels, [something about maple trees -- aha, here](https://kernel-internals.org/mm/per-vma-locks/)!). I think this benchmark should be able to tease out that bottleneck by comparing mmap IO vs NIO cold concurrent IOPs.
How do you simulate the memory pressure? I see you drop OS's IO cache (at the start of each run?). Is it just that the user is expected to test on a large enough file exceeding their free RAM. You could also use @rmuir's awesome `ramhog.c` in luceneutil: https://github.com/mikemccand/luceneutil/blob/main/src/python/ramhog.c ... it just allocates ans pins that amount of RAM so OS cannot use it anymore.
Have you tried comparing this benchy to other IO benchy tools e.g. [`fio`](https://fio.readthedocs.io/en/latest/fio_doc.html)? Then we could gain some confidence in our benchy... (or, maybe lose some confidence in `fio`!).
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "neoremind (via GitHub)" <gi...@apache.org>.
neoremind commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-4913504832
I ran the benchmark across three different hardware profiles to get quantitative measurements of how different I/O strategies work under different memory pressure. Here are the results.
## Platforms
| Platform | CPU | RAM | Storage | 4K random read latency (QD=1) | Saturated random read throughput |
|---|---|---|---|---|---|
| Mac (M3 Pro) | 12 CPU | 36G | 512G Apple SSD | ~77us | ~840 MB/s |
| Linux EBS (EC2 c5.4xlarge) | 16 vCPU | 32G | EBS io2, 200G, 20K provisioned IOPS | ~334us | ~80 MB/s |
| Linux NVME (EC2 c6id.4xlarge) | 16 vCPU | 32G | NVME SSD | ~103us | ~1.25 GB/s |
*The latency and throughput numbers are calculated via `fio` with rand read 16k + different iodepth/numjobs to find the saturation point.*
## Benchmark Setup
16k reads (4 pages) x 16 reads/op = 256KB per op.
Throughput (MB/s) = ops/ms × 256k x 1000. For example, with O_DIRECT on c6id.4xlarge (Linux NVME SSD), JMH at 16 threads yields 4.9 ops/ms → `4.9 × 256k x 1000 ≈ 1250 MB/s`, matching the peak random read throughput from `fio`.
I removed the O_DIRECT results from the table charts below to focus on mmap (with `MMapDirectory`), pread (via FFI), and `NIOFSDirectory` (backed by FileChannel).
## Random Read Results
### CASE 1: Fully warm (all data in page cache)
16G file fits in RAM, pre-warmed with `cat file > /dev/null`.
<img width="672" height="378" alt="warm-mac" src="https://github.com/user-attachments/assets/6aef0f7f-65e5-47a6-b7b0-47544783ef72" />
<img width="662" height="395" alt="warm-linux-ebs" src="https://github.com/user-attachments/assets/f4e25b59-3913-40d2-8116-6c99fbf92937" />
<img width="636" height="387" alt="warm-linux-nvme" src="https://github.com/user-attachments/assets/b84a59a6-4f81-49f7-8345-399fd21413e6" />
- When everything is in RAM, mmap removes syscall overhead, it's pure page-table lookup with direct pointer to cached page, also no intermediate offheap buffer copy in NIO FileChannel. At T8, mmap is 1.5-2x faster than pread and nio FileChannel.
- Observed that batched WILLNEED prefetch adds ~25–30% overhead since the `posix_madvise` syscall is not necessary when pages are already in RAM (see [pure mmap w/o using `MMapDirectory` in Lucene](https://github.com/apache/lucene/issues/16044#issuecomment-4830551502)). But here `MemorySegmentIndexInput#prefetch` uses a power-of-two backoff counter to skip prefetch when pages are loaded, this avoids the slightly `posix_madvise` penalty for hot data and high IOPS scenarios, so we can see there is no difference in performance between all variants of mmap.
- `NIOFSDirectory` (backed by FileChannel) hits a thread-scaling wall, past ~4 threads, the `NativeThreadSet` monitor contention bottlenecked performance as found in #16044. Pread doesn't suffer from this.
<details>
<summary>Detailed JMH results (ops/ms, higher is better)</summary>
#### Mac M3 Pro (36G RAM, unified memory, Apple SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 77.98 | 224.69 | 285.09 | 296.12 |
| mmap (RANDOM) | 77.99 | 224.72 | 285.42 | 295.74 |
| mmap + batchedPrefetch | 77.62 | 223.26 | 284.43 | 294.45 |
| mmap (RANDOM) + batchedPrefetch | 77.78 | 222.51 | 284.75 | 293.86 |
| ffiPread | 37.43 | 93.84 | 73.71 | 65.64 |
| fileChannelNIOFS | 28.05 | 80.32 | 67.77 | 66.43 |
#### Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 37.46 | 141.00 | 261.55 | 282.78 |
| mmap (RANDOM) | 36.93 | 141.52 | 261.13 | 282.63 |
| mmap + batchedPrefetch | 37.00 | 138.04 | 257.00 | 280.10 |
| mmap (RANDOM) + batchedPrefetch | 36.54 | 138.23 | 256.77 | 280.09 |
| ffiPread | 22.38 | 77.67 | 134.53 | 185.46 |
| fileChannelNIOFS | 18.38 | 58.24 | 82.17 | 77.16 |
| ffiPreadDirectIO (O_DIRECT) | 0.15 | 0.63 | 1.27 | 1.25 |
#### Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 45.16 | 174.57 | 316.09 | 332.71 |
| mmap (RANDOM) | 46.53 | 173.58 | 316.22 | 332.91 |
| mmap + batchedPrefetch | 46.22 | 171.98 | 312.48 | 330.43 |
| mmap (RANDOM) + batchedPrefetch | 45.25 | 171.93 | 312.14 | 330.34 |
| ffiPread | 31.53 | 114.05 | 204.93 | 261.52 |
| fileChannelNIOFS | 24.57 | 78.74 | 102.25 | 97.01 |
| ffiPreadDirectIO (O_DIRECT) | 0.66 | 2.47 | 4.46 | 4.80 |
</details>
### CASE 2: Memory pressure (working set close to RAM)
32G file barely exceeds RAM, pre-warmed.
<img width="673" height="380" alt="mem-pressure-mac" src="https://github.com/user-attachments/assets/b4a3f0ab-9a40-4afe-baa5-90a034651d0f" />
<img width="665" height="401" alt="mem-pressure-linux-ebs" src="https://github.com/user-attachments/assets/5f7c27c5-5b65-48bb-83c9-77b7a533daac" />
<img width="637" height="386" alt="mem-pressure-linux-nvme" src="https://github.com/user-attachments/assets/1a3aa38d-e7fe-488b-8d6e-257b9a16377b" />
- On Mac, mmap is the best strategy.
- `NIOFSDirectory` thread contention disappears when there are quite amount of I/O, since the I/O latency dwarfs the monitor overhead, so threads are no longer hot-contending on `NativeThreadSet`.
- On Linux, pread beats plain mmap at all thread counts with memory pressure not matter how big. I think mmap's page-fault path is more expensive than pread's syscall on Linux, especially under memory pressure. This paper from Andy Pavlo ["Are You Sure You Want to Use MMAP in Your Database Management System?"](https://db.cs.cmu.edu/papers/2022/cidr2022-p13-crotty.pdf) argues mmap is not the best option for larger-than-memory DBMS workloads if not used carefully. It notes InfluxDB, MongoDB, and SingleStore deprecated mmap in their latest releases. Section 3.4 (Problem-4: Performance Issues) in the paper points to OS page eviction mechanisms that don't scale beyond a few threads for larger-than-memory workloads on high-bandwidth storage with bottlenecks like page table contention, page eviction overhead, and TLB shootdowns. @mikemccand also pointed out the mmap lock bottleneck above. There's also a [counter-argument](https://www.scylladb.com/2022/05/19/mmap-is-not-the-best-option-fo
r-dbms-is-it/) worth reading for balance.
- This is a case where we still hit hot pages most of the time, but whenever we encounter I/O, mmap is not as good as pread, and what's worse, the backoff mechanism weakens mmap + prefetch. Batched prefetch should theoretically be the most competitive here (as demonstrated in [#16044](https://github.com/apache/lucene/issues/16044#issuecomment-4830551502)'s benchmarks using customized mmap + prefetch without using `MMapDirectory`), but the power-of-two backoff counter causes it to skip most `madvise` calls, so its benefit diminishes or gets emptied away. All in all, without prefetch, mmap alone is not competitive with pread under memory pressure on Linux, prefetch is the silver bullet that makes mmap performant in this scenario.
<details>
<summary>Detailed JMH results (ops/ms, higher is better)</summary>
#### Mac M3 Pro (36G RAM, unified memory, Apple SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 4.76 | 15.73 | 23.50 | 34.65 |
| mmap (RANDOM) | 4.80 | 15.02 | 24.42 | 33.40 |
| mmap + batchedPrefetch | 2.54 | 19.52 | 28.09 | 38.22 |
| mmap (RANDOM) + batchedPrefetch | 5.28 | 16.54 | 23.17 | 32.46 |
| ffiPread | 2.20 | 7.60 | 12.10 | 16.94 |
| fileChannelNIOFS | 1.87 | 6.86 | 11.11 | 16.02 |
#### Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.85 | 3.46 | 3.62 | 3.82 |
| mmap (RANDOM) | 0.50 | 2.03 | 3.38 | 3.35 |
| mmap + batchedPrefetch | 0.93 | 3.81 | 4.50 | 4.33 |
| mmap (RANDOM) + batchedPrefetch | 0.50 | 2.00 | 3.33 | 3.38 |
| ffiPread | 1.48 | 5.15 | 5.48 | 5.12 |
| fileChannelNIOFS | 1.12 | 4.40 | 4.84 | 4.78 |
| ffiPreadDirectIO (O_DIRECT) | 0.15 | 0.63 | 1.28 | 1.25 |
#### Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 3.29 | 11.51 | 18.86 | 24.14 |
| mmap (RANDOM) | 2.14 | 8.45 | 15.29 | 26.36 |
| mmap + batchedPrefetch | 3.96 | 13.21 | 16.49 | 20.07 |
| mmap (RANDOM) + batchedPrefetch | 2.25 | 8.58 | 15.52 | 25.87 |
| ffiPread | 6.04 | 19.05 | 31.47 | 33.87 |
| fileChannelNIOFS | 4.19 | 15.32 | 26.14 | 29.54 |
| ffiPreadDirectIO (O_DIRECT) | 0.66 | 2.46 | 4.45 | 4.80 |
</details>
### CASE 3: Many cold reads (~50% cold reads)
64G file (2x available RAM), ~50% cold reads, pre-warmed.
<img width="672" height="381" alt="half-cold-read-mac" src="https://github.com/user-attachments/assets/bc612e43-2f86-4347-9c77-256c46364373" />
<img width="665" height="400" alt="half-cold-read-linux-ebs" src="https://github.com/user-attachments/assets/a9191135-0c86-4cce-8f9b-1415432bafd1" />
<img width="637" height="384" alt="half-cold-read-linux-nvme" src="https://github.com/user-attachments/assets/9c33dff0-9102-4467-b9b0-d3cf2b65824a" />
- mmap + batched prefetch catches up or surpasses pread here. With ~50% of pages cold, there's a much higher chance for willneed prefetch to actually trigger async I/O operations that deepen iodepth and fill the I/O queue. Even at T01, the prefetch benefit is already very obvious, throughput climbs up high with just one thread due to batched async I/O.
- Interestingly, RANDOM is particularly useful under high-concurrency on fast NVME, likely because it avoids wasted readahead pages that would otherwise be evicted before use. On EBS however, NORMAL wins, the kernel's sequential readahead amortizes the high per-request latency of remote block fetches.
<details>
<summary>Detailed JMH results (ops/ms, higher is better)</summary>
#### Mac M3 Pro (36G RAM, unified memory, Apple SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.45 | 1.55 | 2.58 | 3.27 |
| mmap (RANDOM) | 0.45 | 1.97 | 3.17 | 4.28 |
| mmap + batchedPrefetch | 1.07 | 2.72 | 3.66 | 4.32 |
| mmap (RANDOM) + batchedPrefetch | 0.98 | 2.83 | 3.84 | 4.61 |
| ffiPread | 0.58 | 2.53 | 4.16 | 5.53 |
| fileChannelNIOFS | 0.78 | 2.64 | 4.02 | 5.44 |
#### Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.14 | 0.49 | 0.49 | 0.52 |
| mmap (RANDOM) | 0.07 | 0.28 | 0.46 | 0.46 |
| mmap + batchedPrefetch | 0.89 | 1.51 | 1.52 | 1.50 |
| mmap (RANDOM) + batchedPrefetch | 0.66 | 1.50 | 1.49 | 1.48 |
| ffiPread | 0.25 | 0.96 | 1.67 | 1.52 |
| fileChannelNIOFS | 0.22 | 0.88 | 1.42 | 1.38 |
| ffiPreadDirectIO (O_DIRECT) | 0.15 | 0.62 | 1.27 | 1.25 |
#### Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.64 | 1.21 | 1.22 | 1.23 |
| mmap (RANDOM) | 0.28 | 1.09 | 2.01 | 3.41 |
| mmap + batchedPrefetch | 3.63 | 5.75 | 6.14 | 6.26 |
| mmap (RANDOM) + batchedPrefetch | 3.99 | 7.21 | 7.20 | 7.21 |
| ffiPread | 1.06 | 3.59 | 5.39 | 5.15 |
| fileChannelNIOFS | 0.88 | 3.23 | 5.15 | 4.98 |
| ffiPreadDirectIO (O_DIRECT) | 0.66 | 2.47 | 4.45 | 4.80 |
</details>
### CASE 4: Almost all cold reads
64G file (2x available RAM), clear page cache before each iteration, cold start.
<img width="673" height="381" alt="all-cold-mac" src="https://github.com/user-attachments/assets/13e0718b-3360-4bf3-a81a-c169077c4f2c" />
<img width="663" height="400" alt="all-cold-linux-ebs" src="https://github.com/user-attachments/assets/a053bc8f-8fd9-46a6-8513-2e6a2ec0f3e7" />
<img width="639" height="389" alt="all-cold-linux-nvme" src="https://github.com/user-attachments/assets/c66bb3fe-b5da-4b79-b70a-17cce0fc8009" />
- Same story as Case 3, but the gap widens since mmap + batchedPrefetch wins more on both EBS and NVME.
- The current `MMapDirectory` + prefetch backoff strategy is well-designed for the fully warm and almost all code reads, but when data is mix of warm and cold, the backoff counter ramps up from hot-page hits and skips the batched `WILLNEED` hints diminishing the effect. So whether to enable prefetch all the time is not one-size-fits-all, it depends on query pattern like warm/cold read ratio and what if warm loads are accessed first. This aligns with my finding in https://github.com/apache/lucene/pull/16145#issuecomment-4594402925.
<details>
<summary>Detailed JMH results (ops/ms, higher is better)</summary>
#### Mac M3 Pro (36G RAM, unified memory, Apple SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.30 | 0.67 | 1.97 | 2.29 |
| mmap (RANDOM) | 0.19 | 0.98 | 1.84 | 2.85 |
| mmap + batchedPrefetch | 0.40 | 1.49 | 2.56 | 3.45 |
| mmap (RANDOM) + batchedPrefetch | 0.42 | 1.51 | 2.54 | 3.38 |
| ffiPread | 0.41 | 1.58 | 2.61 | 3.38 |
| fileChannelNIOFS | 0.38 | 1.55 | 2.56 | 3.41 |
| ffiPreadDirectIO (F_NOCACHE) | 0.44 | 1.97 | 3.56 | 5.68 |
#### Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.08 | 0.33 | 0.32 | 0.33 |
| mmap (RANDOM) | 0.04 | 0.16 | 0.26 | 0.26 |
| mmap + batchedPrefetch | 1.36 | 1.35 | 1.35 | 1.38 |
| mmap (RANDOM) + batchedPrefetch | 1.34 | 1.35 | 1.35 | 1.37 |
| ffiPread | 0.14 | 0.57 | 1.15 | 1.36 |
| fileChannelNIOFS | 0.14 | 0.58 | 1.15 | 1.36 |
| ffiPreadDirectIO (O_DIRECT) | 0.16 | 0.63 | 1.27 | 1.28 |
#### Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
| Benchmark | T01 | T04 | T08 | T16 |
|-----------|-----|-----|-----|-----|
| mmap (NORMAL) | 0.45 | 0.87 | 0.87 | 0.94 |
| mmap (RANDOM) | 0.16 | 0.62 | 1.18 | 2.11 |
| mmap + batchedPrefetch | 4.60 | 6.07 | 6.08 | 6.17 |
| mmap (RANDOM) + batchedPrefetch | 4.53 | 6.26 | 6.26 | 6.64 |
| ffiPread | 0.63 | 2.35 | 4.24 | 4.65 |
| fileChannelNIOFS | 0.62 | 2.32 | 4.20 | 4.65 |
| ffiPreadDirectIO (O_DIRECT) | 0.66 | 2.47 | 4.45 | 4.95 |
</details>
## Sequential Read Results
64G file, page cache dropped before each iteration. 128 sequential reads per op at varying read sizes (16KB, 64KB, 128KB).
Look at Linux, mmap (NORMAL) with default kernel readahead performs well across the board for sequential access. mmap (RANDOM) + batchedPrefetch is as good as mmap (NORMAL) with readahead, again, the power-of-two backoff diminishes the effect.
<img width="533" height="279" alt="seq-mac" src="https://github.com/user-attachments/assets/4c2d5ca8-758e-42cb-83ac-e23a90701397" />
<img width="532" height="278" alt="seq-linux-ebs" src="https://github.com/user-attachments/assets/45027548-2c07-4faf-99db-e6bf3a3ace9a" />
<img width="533" height="277" alt="seq-linux-nvme" src="https://github.com/user-attachments/assets/d992edf8-7174-4ff2-9087-daa5a927fdc0" />
<details>
<summary>Detailed JMH results (ops/ms, higher is better)</summary>
#### Mac M3 Pro (36G RAM, unified memory, Apple SSD)
| Benchmark | 16KB | 64KB | 128KB |
|-----------|------|------|-------|
| mmap (NORMAL) | 4.032 | 0.993 | 0.503 |
| mmap (SEQUENTIAL) | 2.292 | 0.601 | 0.288 |
| mmap (RANDOM) | 1.839 | 0.452 | 0.231 |
| mmap (RANDOM) + batchedPrefetch | 5.391 | 1.546 | 0.871 |
| ffiPread | 2.009 | 0.661 | 0.577 |
| fileChannelNIOFS | 2.123 | 0.510 | 0.261 |
| ffiPreadDirectIO (F_NOCACHE) | 0.860 | 0.218 | 0.152 |
#### Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
| Benchmark | 16KB | 64KB | 128KB |
|-----------|------|------|-------|
| mmap (NORMAL) | 2.223 | 0.601 | 0.272 |
| mmap (SEQUENTIAL) | 2.236 | 0.563 | 0.281 |
| mmap (RANDOM) | 0.101 | 0.024 | 0.011 |
| mmap (RANDOM) + batchedPrefetch | 2.205 | 1.136 | 0.565 |
| ffiPread | 1.922 | 0.553 | 0.275 |
| fileChannelNIOFS | 2.276 | 0.491 | 0.266 |
| ffiPreadDirectIO (O_DIRECT) | 0.163 | 0.072 | 0.035 |
#### Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
| Benchmark | 16KB | 64KB | 128KB |
|-----------|------|------|-------|
| mmap (NORMAL) | 9.437 | 2.374 | 1.189 |
| mmap (SEQUENTIAL) | 7.405 | 1.848 | 0.925 |
| mmap (RANDOM) | 0.722 | 0.180 | 0.090 |
| mmap (RANDOM) + batchedPrefetch | 9.816 | 2.454 | 1.224 |
| ffiPread | 9.694 | 2.388 | 1.200 |
| fileChannelNIOFS | 11.027 | 2.720 | 1.386 |
| ffiPreadDirectIO (O_DIRECT) | 0.703 | 0.153 | 0.076 |
</details>
## fio vs. JMH benchmarks
I cross-validated against fio on c6id.4xlarge (NVME). The JMH numbers match fio within about 4% overhead (like JVM indirections, jmh blackhole?):
- Sequential 1MB reads: JMH pread/NIOFS/mmap 12.01K IOPS vs. fio (sync/psync/libaio/mmap) 12.5K IOPS, both saturate disk bandwidth
<details>
<summary>Command used</summary>
```
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "SequentialReadIOBenchmark\.(fileChannelNIOFS)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=1048576 -p readsPerOp=1 -r 30
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "SequentialReadIOBenchmark\.(ffiPread)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=1048576 -p readsPerOp=1 -r 30
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "SequentialReadIOBenchmark\.(mmap)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=1048576 -p readsPerOp=1 -r 30
fio --name=seqread \
--rw=read \
--bs=1m \
--size=64g \
--numjobs=1 \
--ioengine=psync \
--direct=1 \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
fio --name=seqread \
--rw=read \
--bs=1m \
--size=64g \
--numjobs=1 \
--ioengine=mmap \
--direct=1 \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
fio --name=seqread \
--rw=read \
--bs=1m \
--size=64g \
--numjobs=1 \
--ioengine=libaio \
--direct=1 \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
```
</details>
- Random 16KB reads (4 threads, QD=1): JMH pread 40.5K IOPS vs. fio libaio 42.8K IOPS
<details>
<summary>Command used</summary>
```
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "RandomReadIOBenchmark\.(ffiPread_T04)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=16384 -p readsPerOp=1 -f 1 -wi 1 -w 3 -i 1 -r 30
## no direct set, so some page caches hit
fio --name=randread \
--rw=randread \
--bs=16k \
--size=64g \
--iodepth=x \
--numjobs=4 \
--ioengine=libaio \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
```
</details>
- Random 16KB with mmap: JMH and fio mmap (QD=1) can both achieve 12K IOPS
<details>
<summary>Command used</summary>
```
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "RandomReadIOBenchmark\.(mmap_T04)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=16384 -p readsPerOp=1 -f 1 -wi 1 -w 3 -i 1 -r 10
fio --name=randread \
--rw=randread \
--bs=16k \
--size=64g \
--iodepth=1 \
--numjobs=4 \
--ioengine=mmap \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
```
</details>
- Random 16KB with mmap: JMH (mmap + batched prefetch) and fio libaio (QD=16, numjobs=4) can both saturate I/O with 80K IOPS and 1.25G throughput.
<details>
<summary>Command used</summary>
```
# convert result by 5.0 ops/ms x 16 ops x 1000 = 80K IOPS
# JMH can go even higher if we extend running time from 10 seconds to more because of more hot page cache hit
sync && echo 3 | sudo tee /proc/sys/vm/drop_caches
java -jar lucene/benchmark-jmh/build/benchmarks/lucene-benchmark-jmh-11.0.0-SNAPSHOT.jar "RandomReadIOBenchmark\.(mmapRandom_T04)" \
-jvmArgs "--enable-native-access=ALL-UNNAMED -Xms2g -Xmx2g -Dbench.file=/mnt/local/bench-64G.dat -Dbench.fileSizeMB=65536" \
-p readSize=16384 -p readsPerOp=16 -f 1 -wi 1 -w 1 -i 1 -r 10
fio --name=randread \
--rw=randread \
--bs=16k \
--size=64g \
--iodepth=16 \
--numjobs=4 \
--ioengine=libaio \
--direct=1 \
--runtime=30 \
--time_based \
--group_reporting \
--filename=/mnt/local/bench-64G.dat
```
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "neoremind (via GitHub)" <gi...@apache.org>.
neoremind commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-6338121542
Hi @jimczi, I'd like to revive this PR. As time goes, I find this benchmark useful for multiple other PRs, and it can also be seen as a Java version of `fio`. I run each strategy (buffered io, pread, direct io, mmap with various hint and batched prefetch) vs. `fio` doing the same job, and the results match, it basically mirrors `fio` close and be value to vet Lucene's I/O primitives. Given that, I think it's worth doing.
New revision:
- Direct I/O now uses Lucene's DirectIODirectory instead of raw FFI O_DIRECT.
- Some small cleanups.
- A more complete javadoc that explains what each strategy does, how to run it, parameters, and what to watch out.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "jimczi (via GitHub)" <gi...@apache.org>.
jimczi merged PR #16279:
URL: https://github.com/apache/lucene/pull/16279
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "jimczi (via GitHub)" <gi...@apache.org>.
jimczi commented on code in PR #16279:
URL: https://github.com/apache/lucene/pull/16279#discussion_r3486602086
##########
lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/RandomReadIOBenchmark.java:
##########
@@ -0,0 +1,358 @@
+/*
+ * 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.benchmark.jmh;
+
+import java.io.IOException;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+/** Benchmark comparing random read I/O strategies under varying concurrency and memory pressure. */
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Warmup(iterations = 2, time = 3)
+@Measurement(iterations = 3, time = 5)
+@Fork(
+ value = 2,
+ jvmArgsPrepend = {"--enable-native-access=ALL-UNNAMED", "-Xms2g", "-Xmx2g"})
+public class RandomReadIOBenchmark extends AbstractReadIOBenchmark {
+
+ @Param({"4096"})
+ public int readSize;
+
+ @Param({"16"})
+ public int readsPerOp;
+
+ private long maxOffset;
+ private long maxAlignedOffset;
+
+ @Setup(Level.Trial)
+ public void validateParams() {
+ validateReadSize(readSize);
+ maxOffset = FILE_SIZE - readSize;
+ maxAlignedOffset = (maxOffset / ALIGNMENT) * ALIGNMENT;
+ }
+
+ // ======== mmap NORMAL ========
+
+ @Benchmark
+ @Threads(1)
+ public void mmap_T01(ThreadBuffers tb, Blackhole bh) {
+ doMmapNormalReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void mmap_T04(ThreadBuffers tb, Blackhole bh) {
+ doMmapNormalReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void mmap_T08(ThreadBuffers tb, Blackhole bh) {
+ doMmapNormalReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void mmap_T16(ThreadBuffers tb, Blackhole bh) {
+ doMmapNormalReads(tb, bh);
+ }
+
+ // ======== mmap + MADV_RANDOM ========
+
+ @Benchmark
+ @Threads(1)
+ public void mmapMadvRandom_T01(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void mmapMadvRandom_T04(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void mmapMadvRandom_T08(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void mmapMadvRandom_T16(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomReads(tb, bh);
+ }
+
+ // ======== mmap NORMAL + batched MADV_WILLNEED ========
+
+ @Benchmark
+ @Threads(1)
+ public void mmapBatchedPrefetch_T01(ThreadBuffers tb, Blackhole bh) {
+ doMmapBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void mmapBatchedPrefetch_T04(ThreadBuffers tb, Blackhole bh) {
+ doMmapBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void mmapBatchedPrefetch_T08(ThreadBuffers tb, Blackhole bh) {
+ doMmapBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void mmapBatchedPrefetch_T16(ThreadBuffers tb, Blackhole bh) {
+ doMmapBatchedPrefetch(tb, bh);
+ }
+
+ // ======== mmap RANDOM + batched MADV_WILLNEED ========
+
+ @Benchmark
+ @Threads(1)
+ public void mmapMadvRandomBatchedPrefetch_T01(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void mmapMadvRandomBatchedPrefetch_T04(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void mmapMadvRandomBatchedPrefetch_T08(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomBatchedPrefetch(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void mmapMadvRandomBatchedPrefetch_T16(ThreadBuffers tb, Blackhole bh) {
+ doMmapMadvRandomBatchedPrefetch(tb, bh);
+ }
+
+ // ======== FFI pread ========
+
+ @Benchmark
+ @Threads(1)
+ public void ffiPread_T01(ThreadBuffers tb, Blackhole bh) {
+ doFfiReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void ffiPread_T04(ThreadBuffers tb, Blackhole bh) {
+ doFfiReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void ffiPread_T08(ThreadBuffers tb, Blackhole bh) {
+ doFfiReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void ffiPread_T16(ThreadBuffers tb, Blackhole bh) {
+ doFfiReads(tb, bh);
+ }
+
+ // ======== FileChannel + DirectByteBuffer ========
+
+ @Benchmark
+ @Threads(1)
+ public void fileChannelDirectBuffer_T01(ThreadBuffers tb, Blackhole bh) throws IOException {
+ doFileChannelDirectReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(4)
+ public void fileChannelDirectBuffer_T04(ThreadBuffers tb, Blackhole bh) throws IOException {
+ doFileChannelDirectReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(8)
+ public void fileChannelDirectBuffer_T08(ThreadBuffers tb, Blackhole bh) throws IOException {
+ doFileChannelDirectReads(tb, bh);
+ }
+
+ @Benchmark
+ @Threads(16)
+ public void fileChannelDirectBuffer_T16(ThreadBuffers tb, Blackhole bh) throws IOException {
+ doFileChannelDirectReads(tb, bh);
+ }
+
+ // ======== FFI pread + O_DIRECT ========
+
+ // @Benchmark
+ @Threads(1)
+ public void ffiPreadDirectIO_T01(ThreadBuffers tb, Blackhole bh) {
+ doFfiDirectIoReads(tb, bh);
+ }
+
+ // @Benchmark
+ @Threads(4)
+ public void ffiPreadDirectIO_T04(ThreadBuffers tb, Blackhole bh) {
+ doFfiDirectIoReads(tb, bh);
+ }
+
+ // @Benchmark
+ @Threads(8)
+ public void ffiPreadDirectIO_T08(ThreadBuffers tb, Blackhole bh) {
+ doFfiDirectIoReads(tb, bh);
+ }
+
+ // @Benchmark
+ @Threads(16)
+ public void ffiPreadDirectIO_T16(ThreadBuffers tb, Blackhole bh) {
+ doFfiDirectIoReads(tb, bh);
+ }
+
+ // ======== Implementation ========
+
+ private void doMmapNormalReads(ThreadBuffers tb, Blackhole bh) {
+ ThreadLocalRandom rng = ThreadLocalRandom.current();
+ byte[] dst = tb.heapBuf.array();
+ for (int i = 0; i < readsPerOp; i++) {
+ long offset = rng.nextLong(maxOffset);
+ MemorySegment.copy(mmapSegmentNormal, ValueLayout.JAVA_BYTE, offset, dst, 0, readSize);
+ bh.consume(dst[0]);
+ }
+ }
+
+ private void doMmapMadvRandomReads(ThreadBuffers tb, Blackhole bh) {
+ ThreadLocalRandom rng = ThreadLocalRandom.current();
+ byte[] dst = tb.heapBuf.array();
+ for (int i = 0; i < readsPerOp; i++) {
+ long offset = rng.nextLong(maxOffset);
+ MemorySegment.copy(mmapSegmentMadvRandom, ValueLayout.JAVA_BYTE, offset, dst, 0, readSize);
+ bh.consume(dst[0]);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void doMmapBatchedPrefetch(ThreadBuffers tb, Blackhole bh) {
+ ThreadLocalRandom rng = ThreadLocalRandom.current();
+ byte[] dst = tb.heapBuf.array();
+ long[] offsets = new long[readsPerOp];
+ try {
+ for (int i = 0; i < readsPerOp; i++) {
+ offsets[i] = rng.nextLong(maxOffset);
+ }
+ for (int i = 0; i < readsPerOp; i++) {
+ MemorySegment slice = mmapSegmentNormal.asSlice(offsets[i], readSize);
+ int rc = (int) POSIX_MADVISE.invokeExact(slice, (long) readSize, MADV_WILLNEED);
Review Comment:
`madvise` needs a **page-aligned** start address, so passing the raw random offset here makes it return `EINVAL` and do nothing — and since `rc` is discarded, it fails silently. On a c6id.4xlarge `strace` shows ~5117/5181 of these calls returning `-1 EINVAL`, so the prefetch never actually runs and the prefetch rows end up identical to plain mmap. The real Directory avoids this because `MemorySegmentIndexInput#advise` rounds the start down to the page first.
Suggested fix (mirrors what the Directory does):
```suggestion
// madvise needs a page-aligned start address, otherwise it returns EINVAL and is a no-op.
long offsetInPage = (mmapSegmentNormal.address() + offsets[i]) % ALIGNMENT;
long aoff = offsets[i] - offsetInPage;
long alen = readSize + offsetInPage;
MemorySegment slice = mmapSegmentNormal.asSlice(aoff, alen);
int rc = (int) POSIX_MADVISE.invokeExact(slice, alen, MADV_WILLNEED);
assert rc == 0 : "posix_madvise failed: " + rc;
```
Same change is needed in `doMmapMadvRandomBatchedPrefetch` (against `mmapSegmentMadvRandom`). With this, single-threaded mmap+prefetch goes from ~0.15 → ~4.2 ops/ms cold (≈7× pread at T01, ~device saturation). Might be worth asserting on `rc` at the other madvise call sites too so this can't silently regress again.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "github-advanced-security[bot] (via GitHub)" <gi...@apache.org>.
github-advanced-security[bot] commented on code in PR #16279:
URL: https://github.com/apache/lucene/pull/16279#discussion_r3446916840
##########
lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/AbstractReadIOBenchmark.java:
##########
@@ -0,0 +1,289 @@
+/*
+ * 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.benchmark.jmh;
+
+import java.io.IOException;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Abstract base for read I/O benchmarks. Provides shared FFI handles (pread, open, close,
+ * posix_madvise), thread-local buffers, file/mmap setup, and configuration parsing.
+ *
+ * <p>Subclasses define their own offset bounds and benchmark methods.
+ */
+@State(Scope.Benchmark)
+@SuppressWarnings("restricted")
+public abstract class AbstractReadIOBenchmark {
+
+ protected static final int READ_SIZE = 4 * 1024; // 4 KiB
+ protected static final int READS_PER_OP = 16;
+ protected static final long ALIGNMENT = 4096;
+
+ protected static final long FILE_SIZE =
+ Long.parseLong(envOrProp("BENCH_FILE_SIZE_MIB", "bench.fileSizeMiB", "1024")) * 1024L * 1024L;
+
+ protected static final String BENCH_FILE =
+ envOrProp("BENCH_FILE", "bench.file", "/tmp/pread-bench.dat");
+
+ protected static final boolean DROP_CACHES =
+ Boolean.parseBoolean(envOrProp("BENCH_DROP_CACHES", "bench.dropCaches", "false"));
+
+ protected static final int MADV_NORMAL = 0;
+ protected static final int MADV_RANDOM = 1;
+ protected static final int MADV_WILLNEED = 3;
+
+ // FFI handles
+ protected static final MethodHandle PREAD;
+ protected static final MethodHandle OPEN;
+ protected static final MethodHandle CLOSE;
+ protected static final MethodHandle POSIX_MADVISE;
+
+ static {
+ Linker linker = Linker.nativeLinker();
+ SymbolLookup lookup = linker.defaultLookup();
+
+ PREAD =
+ linker.downcallHandle(
+ lookup.find("pread").orElseThrow(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_LONG));
+
+ OPEN =
+ linker.downcallHandle(
+ lookup.find("open").orElseThrow(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
+
+ CLOSE =
+ linker.downcallHandle(
+ lookup.find("close").orElseThrow(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
+
+ POSIX_MADVISE =
+ linker.downcallHandle(
+ lookup.find("posix_madvise").orElseThrow(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_INT));
+ }
+
+ /** Per-thread pre-allocated buffers to avoid allocation noise in the measured path. */
+ @State(Scope.Thread)
+ public static class ThreadBuffers {
+ public ByteBuffer directBuf;
+ public ByteBuffer heapBuf;
+ public Arena ffiArena;
+ public MemorySegment ffiBuf;
+ public MemorySegment ffiDirectIoBuf;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ directBuf = ByteBuffer.allocateDirect(READ_SIZE);
+ heapBuf = ByteBuffer.allocate(READ_SIZE);
+ ffiArena = Arena.ofConfined();
+ ffiBuf = ffiArena.allocate(READ_SIZE);
+ ffiDirectIoBuf = ffiArena.allocate(READ_SIZE, 4096);
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() {
+ ffiArena.close();
+ }
+ }
+
+ protected Path tempFile;
+ protected FileChannel fileChannel;
+ protected MemorySegment mmapSegmentNormal;
+ protected MemorySegment mmapSegmentMadvRandom;
+ protected int nativeFd;
+ protected int directIoFd;
+ protected Arena arena;
+
+ /** Subclasses return a name for logging (e.g. "RandomReadIOBenchmark"). */
+ protected abstract String benchmarkName();
+
+ /** Subclasses may print extra config lines. */
+ protected void printExtraConfig() {}
+
+ @Setup(Level.Trial)
+ public void setup() throws Exception {
+ System.out.println("[bench] ===== " + benchmarkName() + " Configuration =====");
+ System.out.println("[bench] file: " + BENCH_FILE);
+ System.out.println("[bench] fileSizeMiB: " + (FILE_SIZE / (1024 * 1024)));
+ System.out.println("[bench] dropCaches: " + DROP_CACHES);
+ System.out.println("[bench] readSize: " + READ_SIZE + " bytes");
+ System.out.println("[bench] readsPerOp: " + READS_PER_OP);
+ printExtraConfig();
+ System.out.println("[bench] ===============================================");
+
+ tempFile = Path.of(BENCH_FILE);
+ if (!Files.exists(tempFile)) {
+ throw new IOException(
+ "Benchmark file not found: "
+ + tempFile
+ + "\nCreate it with: dd if=/dev/urandom of="
+ + BENCH_FILE
+ + " bs=1M count="
+ + (FILE_SIZE / (1024 * 1024)));
+ }
+ long size = Files.size(tempFile);
+ if (size < FILE_SIZE) {
+ throw new IOException(
+ "Benchmark file too small: "
+ + size
+ + " bytes, expected at least "
+ + FILE_SIZE
+ + "\nRecreate with: dd if=/dev/urandom of="
+ + BENCH_FILE
+ + " bs=1M count="
+ + (FILE_SIZE / (1024 * 1024)));
+ }
+
+ fileChannel = FileChannel.open(tempFile, StandardOpenOption.READ);
+
+ arena = Arena.ofShared();
+
+ mmapSegmentNormal = fileChannel.map(MapMode.READ_ONLY, 0, FILE_SIZE, arena);
+
+ mmapSegmentMadvRandom = fileChannel.map(MapMode.READ_ONLY, 0, FILE_SIZE, arena);
+ try {
+ int rc = (int) POSIX_MADVISE.invokeExact(mmapSegmentMadvRandom, FILE_SIZE, MADV_RANDOM);
+ if (rc != 0) {
+ System.err.println("WARNING: posix_madvise(MADV_RANDOM) returned " + rc);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("posix_madvise(MADV_RANDOM) failed", t);
+ }
+
+ MemorySegment pathStr = arena.allocateFrom(tempFile.toString());
+ int O_RDONLY = 0;
+ try {
+ nativeFd = (int) OPEN.invokeExact(pathStr, O_RDONLY);
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to open file via FFI", t);
+ }
+ if (nativeFd < 0) {
+ throw new IOException("FFI open() returned " + nativeFd);
+ }
+
+ // Open native fd with O_DIRECT for Direct I/O (Linux only, bypasses page cache)
+ int O_DIRECT = 0x4000;
+ try {
+ directIoFd = (int) OPEN.invokeExact(pathStr, O_RDONLY | O_DIRECT);
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to open file with O_DIRECT via FFI", t);
+ }
+ if (directIoFd < 0) {
+ System.err.println(
+ "WARNING: O_DIRECT open failed (fd="
+ + directIoFd
+ + "). "
+ + "Direct I/O benchmarks will fail. Use a filesystem that supports O_DIRECT.");
+ directIoFd = -1;
+ }
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() throws Exception {
+ fileChannel.close();
+ try {
+ int rc = (int) CLOSE.invokeExact(nativeFd);
+ if (directIoFd >= 0) {
+ rc = (int) CLOSE.invokeExact(directIoFd);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ arena.close();
+ }
+
+ /**
+ * Drops page caches before each iteration (warmup and measurement). This ensures each iteration
+ * starts with a cold page cache. JIT still warms up across iterations since the JVM persists
+ * across the fork.
+ */
+ @Setup(Level.Iteration)
+ public void setupIteration() throws IOException {
+ if (DROP_CACHES) {
+ dropPageCaches();
+ }
+ }
+
+ /**
+ * Drops the kernel page cache to simulate cold-cache / memory-constrained scenarios. Requires
+ * running as root or with passwordless sudo. Uses: sync && echo 3 > /proc/sys/vm/drop_caches
+ */
+ private static void dropPageCaches() throws IOException {
+ Process sync = new ProcessBuilder("sync").inheritIO().start();
Review Comment:
## CodeQL / Executing a command with a relative path
Command with a relative path 'sync' is executed.
[Show more details](https://github.com/apache/lucene/security/code-scanning/265)
##########
lucene/benchmark-jmh/src/java/org/apache/lucene/benchmark/jmh/AbstractReadIOBenchmark.java:
##########
@@ -0,0 +1,289 @@
+/*
+ * 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.benchmark.jmh;
+
+import java.io.IOException;
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Abstract base for read I/O benchmarks. Provides shared FFI handles (pread, open, close,
+ * posix_madvise), thread-local buffers, file/mmap setup, and configuration parsing.
+ *
+ * <p>Subclasses define their own offset bounds and benchmark methods.
+ */
+@State(Scope.Benchmark)
+@SuppressWarnings("restricted")
+public abstract class AbstractReadIOBenchmark {
+
+ protected static final int READ_SIZE = 4 * 1024; // 4 KiB
+ protected static final int READS_PER_OP = 16;
+ protected static final long ALIGNMENT = 4096;
+
+ protected static final long FILE_SIZE =
+ Long.parseLong(envOrProp("BENCH_FILE_SIZE_MIB", "bench.fileSizeMiB", "1024")) * 1024L * 1024L;
+
+ protected static final String BENCH_FILE =
+ envOrProp("BENCH_FILE", "bench.file", "/tmp/pread-bench.dat");
+
+ protected static final boolean DROP_CACHES =
+ Boolean.parseBoolean(envOrProp("BENCH_DROP_CACHES", "bench.dropCaches", "false"));
+
+ protected static final int MADV_NORMAL = 0;
+ protected static final int MADV_RANDOM = 1;
+ protected static final int MADV_WILLNEED = 3;
+
+ // FFI handles
+ protected static final MethodHandle PREAD;
+ protected static final MethodHandle OPEN;
+ protected static final MethodHandle CLOSE;
+ protected static final MethodHandle POSIX_MADVISE;
+
+ static {
+ Linker linker = Linker.nativeLinker();
+ SymbolLookup lookup = linker.defaultLookup();
+
+ PREAD =
+ linker.downcallHandle(
+ lookup.find("pread").orElseThrow(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_LONG));
+
+ OPEN =
+ linker.downcallHandle(
+ lookup.find("open").orElseThrow(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
+
+ CLOSE =
+ linker.downcallHandle(
+ lookup.find("close").orElseThrow(),
+ FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
+
+ POSIX_MADVISE =
+ linker.downcallHandle(
+ lookup.find("posix_madvise").orElseThrow(),
+ FunctionDescriptor.of(
+ ValueLayout.JAVA_INT,
+ ValueLayout.ADDRESS,
+ ValueLayout.JAVA_LONG,
+ ValueLayout.JAVA_INT));
+ }
+
+ /** Per-thread pre-allocated buffers to avoid allocation noise in the measured path. */
+ @State(Scope.Thread)
+ public static class ThreadBuffers {
+ public ByteBuffer directBuf;
+ public ByteBuffer heapBuf;
+ public Arena ffiArena;
+ public MemorySegment ffiBuf;
+ public MemorySegment ffiDirectIoBuf;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ directBuf = ByteBuffer.allocateDirect(READ_SIZE);
+ heapBuf = ByteBuffer.allocate(READ_SIZE);
+ ffiArena = Arena.ofConfined();
+ ffiBuf = ffiArena.allocate(READ_SIZE);
+ ffiDirectIoBuf = ffiArena.allocate(READ_SIZE, 4096);
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() {
+ ffiArena.close();
+ }
+ }
+
+ protected Path tempFile;
+ protected FileChannel fileChannel;
+ protected MemorySegment mmapSegmentNormal;
+ protected MemorySegment mmapSegmentMadvRandom;
+ protected int nativeFd;
+ protected int directIoFd;
+ protected Arena arena;
+
+ /** Subclasses return a name for logging (e.g. "RandomReadIOBenchmark"). */
+ protected abstract String benchmarkName();
+
+ /** Subclasses may print extra config lines. */
+ protected void printExtraConfig() {}
+
+ @Setup(Level.Trial)
+ public void setup() throws Exception {
+ System.out.println("[bench] ===== " + benchmarkName() + " Configuration =====");
+ System.out.println("[bench] file: " + BENCH_FILE);
+ System.out.println("[bench] fileSizeMiB: " + (FILE_SIZE / (1024 * 1024)));
+ System.out.println("[bench] dropCaches: " + DROP_CACHES);
+ System.out.println("[bench] readSize: " + READ_SIZE + " bytes");
+ System.out.println("[bench] readsPerOp: " + READS_PER_OP);
+ printExtraConfig();
+ System.out.println("[bench] ===============================================");
+
+ tempFile = Path.of(BENCH_FILE);
+ if (!Files.exists(tempFile)) {
+ throw new IOException(
+ "Benchmark file not found: "
+ + tempFile
+ + "\nCreate it with: dd if=/dev/urandom of="
+ + BENCH_FILE
+ + " bs=1M count="
+ + (FILE_SIZE / (1024 * 1024)));
+ }
+ long size = Files.size(tempFile);
+ if (size < FILE_SIZE) {
+ throw new IOException(
+ "Benchmark file too small: "
+ + size
+ + " bytes, expected at least "
+ + FILE_SIZE
+ + "\nRecreate with: dd if=/dev/urandom of="
+ + BENCH_FILE
+ + " bs=1M count="
+ + (FILE_SIZE / (1024 * 1024)));
+ }
+
+ fileChannel = FileChannel.open(tempFile, StandardOpenOption.READ);
+
+ arena = Arena.ofShared();
+
+ mmapSegmentNormal = fileChannel.map(MapMode.READ_ONLY, 0, FILE_SIZE, arena);
+
+ mmapSegmentMadvRandom = fileChannel.map(MapMode.READ_ONLY, 0, FILE_SIZE, arena);
+ try {
+ int rc = (int) POSIX_MADVISE.invokeExact(mmapSegmentMadvRandom, FILE_SIZE, MADV_RANDOM);
+ if (rc != 0) {
+ System.err.println("WARNING: posix_madvise(MADV_RANDOM) returned " + rc);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException("posix_madvise(MADV_RANDOM) failed", t);
+ }
+
+ MemorySegment pathStr = arena.allocateFrom(tempFile.toString());
+ int O_RDONLY = 0;
+ try {
+ nativeFd = (int) OPEN.invokeExact(pathStr, O_RDONLY);
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to open file via FFI", t);
+ }
+ if (nativeFd < 0) {
+ throw new IOException("FFI open() returned " + nativeFd);
+ }
+
+ // Open native fd with O_DIRECT for Direct I/O (Linux only, bypasses page cache)
+ int O_DIRECT = 0x4000;
+ try {
+ directIoFd = (int) OPEN.invokeExact(pathStr, O_RDONLY | O_DIRECT);
+ } catch (Throwable t) {
+ throw new RuntimeException("Failed to open file with O_DIRECT via FFI", t);
+ }
+ if (directIoFd < 0) {
+ System.err.println(
+ "WARNING: O_DIRECT open failed (fd="
+ + directIoFd
+ + "). "
+ + "Direct I/O benchmarks will fail. Use a filesystem that supports O_DIRECT.");
+ directIoFd = -1;
+ }
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDown() throws Exception {
+ fileChannel.close();
+ try {
+ int rc = (int) CLOSE.invokeExact(nativeFd);
+ if (directIoFd >= 0) {
+ rc = (int) CLOSE.invokeExact(directIoFd);
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ arena.close();
+ }
+
+ /**
+ * Drops page caches before each iteration (warmup and measurement). This ensures each iteration
+ * starts with a cold page cache. JIT still warms up across iterations since the JVM persists
+ * across the fork.
+ */
+ @Setup(Level.Iteration)
+ public void setupIteration() throws IOException {
+ if (DROP_CACHES) {
+ dropPageCaches();
+ }
+ }
+
+ /**
+ * Drops the kernel page cache to simulate cold-cache / memory-constrained scenarios. Requires
+ * running as root or with passwordless sudo. Uses: sync && echo 3 > /proc/sys/vm/drop_caches
+ */
+ private static void dropPageCaches() throws IOException {
+ Process sync = new ProcessBuilder("sync").inheritIO().start();
+ try {
+ if (sync.waitFor() != 0) {
+ throw new IOException("sync failed with exit code " + sync.exitValue());
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Interrupted during sync", e);
+ }
+
+ Process drop =
+ new ProcessBuilder("sudo", "bash", "-c", "echo 3 > /proc/sys/vm/drop_caches")
Review Comment:
## CodeQL / Executing a command with a relative path
Command with a relative path 'sudo' is executed.
[Show more details](https://github.com/apache/lucene/security/code-scanning/266)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org
Re: [PR] Add JMH benchmarks comparing read I/O strategies under memory pressure [lucene]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #16279:
URL: https://github.com/apache/lucene/pull/16279#issuecomment-6205295786
This PR has not had activity in the past 2 weeks, labeling it as stale. If the PR is waiting for review, notify the dev@lucene.apache.org list. Thank you for your contribution!
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscribe@lucene.apache.org
For additional commands, e-mail: issues-help@lucene.apache.org