Add JMH benchmarks comparing read I/O strategies under memory pressure - #16279
Add JMH benchmarks comparing read I/O strategies under memory pressure#16279neoremind wants to merge 13 commits into
Conversation
Refactor benchmarks
|
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 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. 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 Have you tried comparing this benchy to other IO benchy tools e.g. |
|
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
The latency and throughput numbers are calculated via Benchmark Setup16k 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 → I removed the O_DIRECT results from the table charts below to focus on mmap (with Random Read ResultsCASE 1: Fully warm (all data in page cache)16G file fits in RAM, pre-warmed with
Detailed JMH results (ops/ms, higher is better)Mac M3 Pro (36G RAM, unified memory, Apple SSD)
Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
CASE 2: Memory pressure (working set close to RAM)32G file barely exceeds RAM, pre-warmed.
Detailed JMH results (ops/ms, higher is better)Mac M3 Pro (36G RAM, unified memory, Apple SSD)
Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
CASE 3: Many cold reads (~50% cold reads)64G file (2x available RAM), ~50% cold reads, pre-warmed.
Detailed JMH results (ops/ms, higher is better)Mac M3 Pro (36G RAM, unified memory, Apple SSD)
Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
CASE 4: Almost all cold reads64G file (2x available RAM), clear page cache before each iteration, cold start.
Detailed JMH results (ops/ms, higher is better)Mac M3 Pro (36G RAM, unified memory, Apple SSD)
Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
Sequential Read Results64G 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.
Detailed JMH results (ops/ms, higher is better)Mac M3 Pro (36G RAM, unified memory, Apple SSD)
Linux c5.4xlarge (16 vCPU, 32G RAM, EBS io2 20K IOPS)
Linux c6id.4xlarge (16 vCPU, 32G RAM, local NVME SSD)
fio vs. JMH benchmarksI cross-validated against fio on c6id.4xlarge (NVME). The JMH numbers match fio within about 4% overhead (like JVM indirections, jmh blackhole?):
Command used
Command used
Command used
Command used |
|
Thank you @mikemccand! I've updated the benchmark results and analysis above, please see my comments inline below.
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). I also built a fast InnoDB checksum tool 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
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.
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
Yes, please see the fio section in the results above. In short: JMH numbers match fio with tiny overhead. |
|
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! |















Adds JMH benchmarks to compare read I/O strategies in memory constrained scenario, related to #16044.
I/O strategies tested:
NIOFSDirectory)Thread counts: 1, 4, 8, 16.
How to run