<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://richardstartin.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://richardstartin.github.io/" rel="alternate" type="text/html" /><updated>2023-11-13T10:24:21+00:00</updated><id>https://richardstartin.github.io/feed.xml</id><title type="html">Richard Startin’s Blog</title><entry><title type="html">How Async-Profiler’s crash handler works</title><link href="https://richardstartin.github.io/posts/async-profiler-crash-handler" rel="alternate" type="text/html" title="How Async-Profiler’s crash handler works" /><published>2023-11-12T00:00:00+00:00</published><updated>2023-11-12T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/async-profiler-crash-handler</id><content type="html" xml:base="https://richardstartin.github.io/posts/async-profiler-crash-handler">&lt;p&gt;Raw pointers in C/C++ open up entire classes of error that are practically unimaginable in higher level languages.
So why does anybody use them at all?
Unfortunately, it’s impossible to write a profiler without getting close to some of the sharp edges of unsafe memory. 
The async-profiler code base contains a lot of low level tricks, and it’s worth studying how some of them work.&lt;/p&gt;

&lt;p&gt;This post is about how async-profiler safely dereferences arbitrary pointers without crashing the JVM, and why it needs to do that sometimes.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#signals-and-signal-handlers&quot; id=&quot;markdown-toc-signals-and-signal-handlers&quot;&gt;Signals and signal handlers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#frame-pointer-unwinding&quot; id=&quot;markdown-toc-frame-pointer-unwinding&quot;&gt;Frame pointer unwinding&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#safeaccessload&quot; id=&quot;markdown-toc-safeaccessload&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SafeAccess::load&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#replacing-crash-handlers&quot; id=&quot;markdown-toc-replacing-crash-handlers&quot;&gt;Replacing crash handlers&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;signals-and-signal-handlers&quot;&gt;Signals and signal handlers&lt;/h1&gt;

&lt;p&gt;Linux allows threads to be interrupted by signals, which are identified by a signal number.
Signals can be sent to threads via a number of syscalls, such as &lt;a href=&quot;https://man7.org/linux/man-pages/man2/tgkill.2.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tgkill&lt;/code&gt;&lt;/a&gt;, or indirectly via profiling services like &lt;a href=&quot;https://man7.org/linux/man-pages/man3/setitimer.3p.html&quot;&gt;itimer&lt;/a&gt; or &lt;a href=&quot;https://man7.org/linux/man-pages/man2/perf_event_open.2.html&quot;&gt;perf_event_open&lt;/a&gt;.
When a thread transitions between the kernel and user space, such as when the thread is scheduled to run on the CPU, the kernel checks if there are any pending signals for the thread.
If there is a pending signal, and the signal isn’t blocked by a &lt;a href=&quot;https://man7.org/linux/man-pages/man2/sigprocmask.2.html&quot;&gt;signal mask&lt;/a&gt;, and there is a &lt;em&gt;signal handler&lt;/em&gt; installed for the signal, then the thread is halted and the signal handler is invoked.
A signal based sampling profiler consists of a mechanism for choosing threads and sending signals (usually &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGPROF&lt;/code&gt;) to them, then a signal handler for collecting information about the state of the thread (e.g. unwinding the stack).
Signals and signal handlers are used for a number of other things, such as killing the process (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGKILL&lt;/code&gt;), or signalling that something has gone wrong (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGABRT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGBUS&lt;/code&gt;).
If you just want your program not to crash when performing memory unsafe operations, you can install signal handlers for error signals and swallow them, though this will cause other problems without care.&lt;/p&gt;

&lt;h1 id=&quot;frame-pointer-unwinding&quot;&gt;Frame pointer unwinding&lt;/h1&gt;

&lt;p&gt;You don’t need to handle segfaults if your code can’t segfault, so why not just use a programming language with a compiler which verifies memory safety?
Unfortunately, you wouldn’t have much of a profiler if you did that (without unsafe blocks, anyway).
Consider the simplest stack unwinding approach using frame pointers.&lt;/p&gt;

&lt;p&gt;Signal handlers are invoked with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ucontext&lt;/code&gt; which consists of a program counter, a frame pointer, and a stack pointer.&lt;/p&gt;

&lt;p&gt;The program counter (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt;) is the address of the code currently being executed.
It can be used to find the currently executing function by binary searching a compiled binary’s text section (where the code is found) or the JVM’s code heap for JIT compiled code.
It is enough to determine the currently executing instruction (which is how instruction profiling works).
However, the program counter is not enough to determine the caller of the interrupted function.&lt;/p&gt;

&lt;p&gt;The frame pointer (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fp&lt;/code&gt;) determines the start address of the stack frame, which contains function parameters and local variables.
The stack frame is like a snapshot of the registers before calling a function, so that when the called function returns, the calling context can be restored.
Obtaining the frame pointer to the calling frame from the callee frame is known as &lt;em&gt;unwinding&lt;/em&gt;.
&lt;em&gt;If&lt;/em&gt; a binary is compiled with frame pointers, it’s really simple: on x64, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbp&lt;/code&gt; register contains the caller’s frame pointer.
So frame pointer unwinding consists of the following (on x64, the registers differ on aarch64):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Record the program counter from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ucontext&lt;/code&gt; (this can be used later to determine things like function name, line number, and the instruction)&lt;/li&gt;
  &lt;li&gt;Read the caller’s frame pointer from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbp&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Check termination (this could be if the frame pointer is zero, if the frame pointer is outside the bounds of the stack, or in async-profiler if a Java frame has been found).&lt;/li&gt;
  &lt;li&gt;Restore the caller frame from the caller’s frame pointer (dereference the frame pointer).&lt;/li&gt;
  &lt;li&gt;Record the program counter (see point 1)&lt;/li&gt;
  &lt;li&gt;Go to step 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is about as fast and simple as stack unwinding gets, but the problem is lots of binaries are compiled without frame pointers.
If a binary is compiled without frame pointers, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbp&lt;/code&gt; contains arbitrary data and isn’t safe to dereference.
Depending on the exact value stored in the register, dereferencing it may produce a bogus frame but will probably segfault (generate a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; signal).
I am unaware of any way to determine that an arbitrary binary was compiled with frame pointers or not, so the only way to determine whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbp&lt;/code&gt; is a frame pointer or not is to dereference it and hopefuly get a frame.
This puts frame pointer unwinding outside the realm of compile time memory safety checks: either mitigate at runtime or don’t do it.&lt;/p&gt;

&lt;h1 id=&quot;safeaccessload&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SafeAccess::load&lt;/code&gt;&lt;/h1&gt;

&lt;p&gt;Async-profiler uses the following mechanism:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Always dereference untrusted data via a function called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SafeAccess::load&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Install a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; handler.&lt;/li&gt;
  &lt;li&gt;Check in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; handler if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; signal’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; came from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SafeAccess::load&lt;/code&gt;. If it did, patch things up as if the function returned zero, otherwise pass the signal on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are some details which are worth paying attention to in the function declaration (&lt;a href=&quot;https://github.com/async-profiler/async-profiler/blob/master/src/safeAccess.h#L33&quot;&gt;source&lt;/a&gt;):&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   &lt;span class=&quot;cp&quot;&gt;#ifdef __clang__
&lt;/span&gt;   &lt;span class=&quot;cp&quot;&gt;#  define NOINLINE __attribute__((noinline))
&lt;/span&gt;   &lt;span class=&quot;cp&quot;&gt;#else
&lt;/span&gt;   &lt;span class=&quot;cp&quot;&gt;#  define NOINLINE __attribute__((noinline,noclone))
&lt;/span&gt;   &lt;span class=&quot;cp&quot;&gt;#endif
&lt;/span&gt;   &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;NOINLINE&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__attribute__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aligned&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Firstly, the function can’t be inlined because of the compiler attributes applied to it, because if it were inlined, it wouldn’t be possible to determine whether the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; came from the function or not.
Next, the function is aligned so that a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; can be determined to be within the function’s code’s address range easily.&lt;/p&gt;

&lt;p&gt;In the signal handler installed when starting the profiler, the following code executes:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;uintptr_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SafeAccess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;skipLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Skip the fault instruction, as if it successfully loaded NULL&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;retval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code figures out how far to skip ahead to simulate returning from the function, having set the return value to zero.
The way the number of instructions to skip is determined is a little cryptic (only for x64 below):&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uintptr_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;skipLoad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uintptr_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x8b48&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// mov rax, [reg]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Checking if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; is in the address range of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load&lt;/code&gt; function is straightforward enough, but what on earth is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x8b48&lt;/code&gt;?
Dereferencing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; gives a sequence of bytes corresponding to the instructions. 
Casting to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;u16*&lt;/code&gt; truncates to the most significant 2 bytes, which has the effect of ignoring the register the value would be loaded into.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x8b48&lt;/code&gt; check verifies that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; is pointing at a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mov&lt;/code&gt; operation, otherwise something has gone wrong.
Finally, 3 bytes need to be skipped over: 2 for the load, and one for the destination register.&lt;/p&gt;

&lt;h1 id=&quot;replacing-crash-handlers&quot;&gt;Replacing crash handlers&lt;/h1&gt;

&lt;p&gt;The JVM has its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; handler already, in fact, it’s quite important because it’s part of the &lt;a href=&quot;https://shipilev.net/jvm/anatomy-quarks/29-uncommon-traps/&quot;&gt;uncommon trap mechanism&lt;/a&gt; for deoptimisation of speculative optimisations.
This means Async-profiler must delegate to the JVM’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; handler in order not to break the JVM.
It does this by &lt;a href=&quot;https://github.com/async-profiler/async-profiler/blob/master/src/os_linux.cpp#L255&quot;&gt;returning a function pointer to the old handler&lt;/a&gt; when &lt;a href=&quot;https://github.com/async-profiler/async-profiler/blob/master/src/profiler.cpp#L888&quot;&gt;installing its own handler&lt;/a&gt;, and then delegates to it if the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pc&lt;/code&gt; didn’t come from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SafeAccess::load&lt;/code&gt;.
The JVM doesn’t exactly like signals it handles being overridden like this, given the central importance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SIGSEGV&lt;/code&gt; signals to the lifecycle of JIT compiled code, and running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-Xcheck:jni&lt;/code&gt; will spit out angry warning messages about it. 
These messages are annoying but much better than simply dropping samples when the JVM is not executing Java code, because Java code can call native code, and this is sometimes where performance problems lie.&lt;/p&gt;</content><author><name></name></author><category term="profiling" /><summary type="html">Raw pointers in C/C++ open up entire classes of error that are practically unimaginable in higher level languages. So why does anybody use them at all? Unfortunately, it’s impossible to write a profiler without getting close to some of the sharp edges of unsafe memory. The async-profiler code base contains a lot of low level tricks, and it’s worth studying how some of them work.</summary></entry><entry><title type="html">Understanding Request Latency with Profiling</title><link href="https://richardstartin.github.io/posts/wallclock-profiler" rel="alternate" type="text/html" title="Understanding Request Latency with Profiling" /><published>2023-09-05T00:00:00+00:00</published><updated>2023-09-05T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/wallclock-profiler</id><content type="html" xml:base="https://richardstartin.github.io/posts/wallclock-profiler">&lt;p&gt;It can be hard to figure out why response times are high in Java applications.
In my experience, people either apply a process of elimination to a set of recent commits, or might sometimes use profiles of the system to explain changes in metrics.
Making guesses about recent commits can be frustrating for a number of reasons, but mostly because even if you pinpoint the causal change, you still might not know &lt;em&gt;why&lt;/em&gt; it was a bad change and are left in limbo.
In theory, using a profiler makes root cause analysis a part of the triage process, so adopting continuous profiling should make this whole process easier, but using profilers can be frustrating because you’re using the wrong &lt;em&gt;type&lt;/em&gt; of profile for analysis.
Lots of profiling data focuses on CPU time, but the cause of your latency problem may be related to time spent off CPU instead. 
This post is about Datadog’s Java wallclock profiler, which I worked on last year, and explores how to improve request latency without making any code changes, or even seeing the code for that matter.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;This is a personal blog and everything written below is my own personal opinion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#from-metrics-to-profiles&quot; id=&quot;markdown-toc-from-metrics-to-profiles&quot;&gt;From metrics to profiles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#async-profiler&quot; id=&quot;markdown-toc-async-profiler&quot;&gt;async-profiler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#wallclock-profiling&quot; id=&quot;markdown-toc-wallclock-profiling&quot;&gt;Wallclock profiling&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#sampling-strategy&quot; id=&quot;markdown-toc-sampling-strategy&quot;&gt;Sampling strategy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#tracer-managed-thread-filter&quot; id=&quot;markdown-toc-tracer-managed-thread-filter&quot;&gt;Tracer managed thread filter&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#latency-investigation&quot; id=&quot;markdown-toc-latency-investigation&quot;&gt;Latency investigation&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#increasing-the-heap-size&quot; id=&quot;markdown-toc-increasing-the-heap-size&quot;&gt;Increasing the heap size&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#increasing-the-number-of-vcpus&quot; id=&quot;markdown-toc-increasing-the-number-of-vcpus&quot;&gt;Increasing the number of vCPUs&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#what-about-cost&quot; id=&quot;markdown-toc-what-about-cost&quot;&gt;What about cost?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;from-metrics-to-profiles&quot;&gt;From metrics to profiles&lt;/h2&gt;

&lt;p&gt;Suppose you have metrics which show you that request latency is too high for a particular endpoint.
Here is the endpoint level latency timeseries for a slow endpoint &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /allocation-stalls&lt;/code&gt; (the name may create a sense of foreboding) in a very contrived demo Java application.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/latency-before.png&quot; alt=&quot;High request latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The response time is about 3-4s, which we would like to improve somehow without undertaking an R&amp;amp;D project.
Unless you have detailed tracing, you might start looking at profiles to do this.
OpenJDK has a built-in profiler called Java Flight Recorder (JFR), which Datadog has used historically for collecting CPU profiles from Java services.
Here’s what a flamegraph of JFR’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jdk.ExecutionSample&lt;/code&gt; events looks like after being processed by Datadog’s profiling backend:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/jfr-flamegraph.png&quot; alt=&quot;JFR flamegraph&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To the far left there is some JVM activity derived from JFR’s GC events. 
Next to that there are some stack traces for Jetty’s routing (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JettyHandler.doHandle()&lt;/code&gt;).
To the right are stack traces for handling the requests in another thread pool (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ThreadPoolExecutor$Worker.run()&lt;/code&gt;).
There’s not much context beyond the frame names, and the most we have to go on is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JavaServer.lambda$allocationStall$28()&lt;/code&gt; which we can probably guess is related to handling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /allocation-stalls&lt;/code&gt; requests by its name.
Java code can be very generic and it’s not always straightforward to map frames to context by frame names alone, though in this case it’s easy enough. 
Expanding it gets us to where I think most people turn away from profiling, assuming it was as easy to find the relevant frame as in this contrived example:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/jfr-flamegraph-expanded.png&quot; alt=&quot;JFR flamegraph expanded&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This points to random number generation being the bottleneck, which you aren’t likely to speed up.
You may be able to reduce the number of random numbers generated, but in this contrived example, this would require renegotiating requirements.
Part of the problem is that this flamegraph doesn’t show the complete picture. 
Without having the full picture, we can’t be sure we know what the bottleneck is, and might just be looking at the frame we happen to &lt;em&gt;measure&lt;/em&gt; the most.
This is where async-profiler comes in.&lt;/p&gt;

&lt;h2 id=&quot;async-profiler&quot;&gt;async-profiler&lt;/h2&gt;

&lt;p&gt;Some time ago, Datadog started experimenting with using &lt;a href=&quot;https://github.com/async-profiler/async-profiler&quot;&gt;async-profiler&lt;/a&gt; instead of JFR’s execution sampler in its continuous profiling product.
There were two primary motivations for doing this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It’s better
    &lt;ol&gt;
      &lt;li&gt;async-profiler has a much better CPU profiler than JFR, primarily because it can use CPU time (or other perf events) to schedule samples, whereas JFR uses wall time and a state filter.&lt;/li&gt;
      &lt;li&gt;async-profiler has several approaches to unwinding native parts of the stack, whereas JFR does not unwind stacks with native frames.&lt;/li&gt;
      &lt;li&gt;async-profiler reports failed samples so even if it’s not possible to get a full stack trace, it doesn’t distort the weights of other samples.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;We could modify the source code in a fork
    &lt;ol&gt;
      &lt;li&gt;It’s easy to make changes to the source code and get those changes shipped in a timely manner.&lt;/li&gt;
      &lt;li&gt;JFR doesn’t allow any contextualisation of samples, which makes presenting the data it reports in useful and engaging ways challenging. It was quite easy to extend async-profiler to support arbitrary, multi-dimensional labeling, whereas the process to get this functionality into JFR would be long-winded.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So async-profiler’s excellent codebase gave us a starting point for implementing some of the features we had in mind.&lt;/p&gt;

&lt;p&gt;Below is a flamegraph of the same application as above recorded with Datadog’s customisation of async-profiler. 
Notice the ZGC activity to the left and compare it to the GC activity we could infer from JFR above, there’s more detail and it’s more obvious that the service has a GC problem.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-flamegraph-cpu-time-all-activity.png&quot; alt=&quot;async-profiler all activity&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Having access to the source code made it possible to add new fields to event types, such as correlation identifiers.
Decorating samples with span ids makes it possible to link each sample to the endpoint being served, rather than guess based on the frame name (which only gets harder outside of contrived examples).
This makes it possible to filter the flamegraph by endpoint instead of guessing based on the frame names:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-flamegraph-cpu-time-endpoint-filter.png&quot; alt=&quot;async-profiler all activity&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The green frames to the left, which are JNI calls into the lz4-java library, are missing from the JFR flamegraph because JFR won’t unwind stacks when the JVM is executing native code.
In this particular case, this doesn’t actually distort the flamegraph &lt;em&gt;much&lt;/em&gt;, because LZ4 compression is very fast and doesn’t need as much CPU time as the random number generation we already know about, but if it were the other way around the JFR flamegraph would be very misleading.&lt;/p&gt;

&lt;p&gt;Even though the flamegraph now shows a complete picture of CPU time, it still points towards optimising random number generation, which we can’t do.
The problem is now that CPU time isn’t always enough to understand latency.&lt;/p&gt;

&lt;h2 id=&quot;wallclock-profiling&quot;&gt;Wallclock profiling&lt;/h2&gt;

&lt;p&gt;As used in Datadog’s profiler, async-profiler’s CPU profiler uses CPU time to schedule sampling, which means time spent off-CPU will not be sampled. 
In Java &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Thread&lt;/code&gt; terms, a CPU profiler can only sample threads in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RUNNABLE&lt;/code&gt; state, and ignores threads in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BLOCKED&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TIMED_WAITING&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WAITING&lt;/code&gt; states, but a &lt;em&gt;wallclock profiler&lt;/em&gt; can sample threads in any state.
async-profiler also has a wallclock profiler, but we found we needed to tailor it to our requirements.&lt;/p&gt;

&lt;h3 id=&quot;sampling-strategy&quot;&gt;Sampling strategy&lt;/h3&gt;

&lt;p&gt;The first issue we encountered was with overhead (for our own particular requirements, don’t be discouraged from using async-profiler’s wallclock profiler in a different context).
CPU profiling is always very lightweight because the overhead is proportional to the number of cores, whereas for wallclock profiling, overhead scales with the number of threads.
async-profiler’s wallclock profiler samples sets of up to 8 threads round-robin, and to keep the sampling interval consistent as the number of threads changes, will adjust the sampling interval to as low 100 microseconds.
Unless the user provides a thread filter expression to control how many threads to sample, the overhead of this approach can be quite high.
Using a predefined thread filter assumes the user knows where the problems are ahead of time, and they might do when doing offline/non-continuous profiling.
It can also produce quite large recordings: in 60s we might record as many as 4.8 million samples, which, at about 64 bytes per JFR event, corresponds to about 250MiB.&lt;/p&gt;

&lt;p&gt;We wanted smaller recordings and needed to be very conservative about overhead, so we changed the way threads are sampled. 
We implemented reservoir sampling of threads so we could take a uniform sample of a fixed number of threads at a constant sampling interval. 
This gives a much more manageable upper bound on overhead and recording size: in 60s we can sample 16 threads at 100Hz and record at most 96,000 samples, which is just under 6MiB of JFR events.
This approach reintroduces the problem async-profiler solves with its interval adjustment when the number of threads changes over time because this changes the probability of sampling a thread (the reservoir is fixed size), but this can be solved by recording the number of live threads each sample interval and upscaling later.&lt;/p&gt;

&lt;h3 id=&quot;tracer-managed-thread-filter&quot;&gt;Tracer managed thread filter&lt;/h3&gt;

&lt;p&gt;High thread counts also can also reduce sample relevance, without a thread filter. 
For latency analysis it would be ideal to bias towards threads on the critical path. 
There are two kinds of noncritical threads for typical applications:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;idle threads in over-provisioned thread pools, each waiting for items to appear in a queue.&lt;/li&gt;
  &lt;li&gt;background threads such as metrics reporters.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the screenshot below, at least half of the samples are of the 200 workers (which is too many for this application) waiting for work to arrive on a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LinkedBlockingQueue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/all-threads-datadog-flamegraph-all-activity.png&quot; alt=&quot;all threads&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This isn’t very useful information: while fine-tuning the size of this thread pool would be generally beneficial, the threads aren’t blocking the threads processing requests.&lt;/p&gt;

&lt;p&gt;What’s worse is when the samples are rendered as a timeline of a particular trace, the coverage is quite sparse (the grey bars are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PARKED&lt;/code&gt; walltime samples, the blue bars are CPU samples).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/all-threads-datadog-timeline-trace-filter.png&quot; alt=&quot;all timeline trace filter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Sampling idle or background activity reduces the coverage of traces.&lt;/p&gt;

&lt;p&gt;Both of these problems can be solved by only sampling threads which have an active trace context set up by the tracer.
Firstly, tracing is designed to record request latency, so if there is a trace context associated with a thread for a time period, there is a good chance the work is being done to complete a request.
Thanks to context propagation, which propagates trace context into thread pools, any thread which ends up working to complete the request can be sampled.
Secondly, biasing sampling towards traced activity helps &lt;em&gt;focus&lt;/em&gt; the profiler on explaining trace latency, so we have the situation where the tracer can tell you how long a request took, and there’s a good chance that there will be wallclock samples to explain why it took so long.
Whenever we have too much work to do, we can do a better job by prioritising some tasks over others, and it’s the same for sampling threads.&lt;/p&gt;

&lt;p&gt;This results in a flamegraph focused on latency-critical work:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/thread-filter-datadog-flamegraph-all-activity.png&quot; alt=&quot;traced threads&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and when we look at the timeline of a particular trace, we have much better coverage (i.e. more grey bars for more threads)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/thread-filter-datadog-timeline-trace-filter.png&quot; alt=&quot;filtered timeline trace filter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is implemented by repurposing async-profiler’s thread filter, so that it is only applied to the wallclock profiler, and is controlled by the tracer. 
The current thread is added to a bitset of thread ids whenever the current thread has a trace associated with it, and removed from the bitset whenever the thread no longer has a trace associated with it.
If you have used a tracing API, you will be familiar with &lt;em&gt;spans&lt;/em&gt; (logical operations within traces) and &lt;em&gt;scopes&lt;/em&gt; (activations of spans on a thread). 
The tracer pushes a scope to a stack whenever a span is activated on a thread, so that any subsequent spans can refer to the activated span as a parent.
Modifying the bitset each time a scope is pushed or popped from the scope stack is the most obvious implementation, but would lead to very high overhead for certain async workloads.
Instead, the bitset is updated only when the scope stack becomes empty or non-empty, which corresponds to whether there is some trace associated with the thread or not, which almost always reduces the number of bitset updates and never increases it.
Finally, the sampler thread implements reservoir sampling over the bitset of traced threads, rather than over all threads.&lt;/p&gt;

&lt;h2 id=&quot;latency-investigation&quot;&gt;Latency investigation&lt;/h2&gt;

&lt;p&gt;The wallclock profiler reports stack traces for any blocking off-CPU activity, which should tell you &lt;em&gt;where&lt;/em&gt; the blocking operation happened, and &lt;em&gt;why&lt;/em&gt; the blocking operation occurred by looking at the ancestor frames.
It should have been possible to figure out why the request latency is so high from the screenshots so far, but here’s the timeline for a particular request trace again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/thread-filter-datadog-timeline-trace-filter.png&quot; alt=&quot;filtered timeline trace filter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The long grey bars, with durations of about a second, all contain the frame &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ZPageAllocator::alloc_page_stall()&lt;/code&gt;, which blocks on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PosixSemaphore::wait()&lt;/code&gt;, with causal ancestor frame &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer.allocate(int)&lt;/code&gt;.
In the flamegraph scoped to the endpoint, we can see that about 50% of the samples under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JavaServer.lambda$allocationStall$29()&lt;/code&gt; are allocation stalls, so the CPU profile missed roughly half the activity:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-flamegraph-wall-time-endpoint-filter.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Allocation stalls happen with ZGC, when the application threads try to allocate more memory than is currently available.
The application thread blocks until the requested allocation can be performed, which incurs latency. 
ZGC is effectively applying backpressure on the application threads, throttling concurrent access to memory as if it were a bounded queue, and it does this rather than OOM which is the only alternative.
Allocation stalls are a symptom of an undersized heap, and there are two choices for mitigation: allocate a larger heap, or make the application allocate less.&lt;/p&gt;

&lt;p&gt;In this contrived case, there are 30 tasks allocating a pair of 50 MiB &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer&lt;/code&gt;s in a threadpool of 10 threads, which translates to 3GiB per request and 1000MiB allocated at any time during the request, with only 1GiB allocated to the heap.
Having identified and understood allocation stalls, we can see the heap is too small for the workload.
We can’t reduce allocation pressure by reducing the size of the buffers without renegotiating requirements, and we can’t reduce it by object pooling without blocking on the pool (we have less memory than we need) and our buffer pool might be less efficient than ZGC.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There is also a JFR event &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jdk.ZAllocationStall&lt;/code&gt; which could be used instead of a wallclock profiler to determine that the heap is too small, but it does not collect stacktraces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;increasing-the-heap-size&quot;&gt;Increasing the heap size&lt;/h3&gt;

&lt;p&gt;Solving this problem should be a simple case of increasing the heap size so more memory can be allocated without stalling.
The service should then become CPU bound, and it was already established that random number generation can’t be accelerated, so we wouldn’t be able to remove this bottleneck without redefining the workload.&lt;/p&gt;

&lt;p&gt;The heap size was increased to 6GiB.
Looking at the wallclock profile, we can see a change in thread state:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/thread-state-with-stalls.png&quot; alt=&quot;thread state with stalls&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/thread-state-without-stalls.png&quot; alt=&quot;thread state without stalls&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Before, in the top image, about half the time was in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RUNNABLE&lt;/code&gt; state and half in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PARKED&lt;/code&gt; state, afterwards the split is 3:1.
The allocation stalls have gone, and none of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PARKED&lt;/code&gt; time is under the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JavaServer.lambda$allocationStall$29()&lt;/code&gt; frame: the allocation stalls have disappeared.
Looking at a trace of a particular request, the grey bars which represented parking have gone, and we only see the blue CPU samples:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-timeline-bigger-heap.png&quot; alt=&quot;timeline bigger heap&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s clear the way the JVM executes the code has changed as a result of being given more resources, but it hasn’t actually reduced latency.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/latency-bigger-heap.png&quot; alt=&quot;latency bigger heap&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;increasing-the-number-of-vcpus&quot;&gt;Increasing the number of vCPUs&lt;/h3&gt;

&lt;p&gt;The clue about what to fix next is in the timeline for the request: the CPU samples are very sparse in time and across the threads.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-timeline-bigger-heap.png&quot; alt=&quot;timeline bigger heap&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can also see a strange discrepancy between the amount of wall time sampled in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RUNNABLE&lt;/code&gt; state and the amount of sampled CPU time:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-flamegraph-bigger-heap-endpoint-filter-cpu-time.png&quot; alt=&quot;cpu time&quot; /&gt;
&lt;img src=&quot;/assets/2023/09/wallclock-profiler/datadog-flamegraph-bigger-heap-endpoint-filter-wall-time.png&quot; alt=&quot;wall time&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If the workload is now CPU bound, as we expect it to be, &lt;em&gt;and&lt;/em&gt; it’s actually executing, then the CPU time and the wall time should be the same, but there’s 18 seconds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RUNNABLE&lt;/code&gt; wall time and less than 5s of CPU time.
Though we don’t have a direct way to show it, this indicates that the threads are competing to get scheduled on too few vCPUs, and if we could allocate more CPU to the container, the responses should become CPU bound.
As can be seen in the timeline, the workload is parallelised over 10 threads, but only 2 vCPUs were allocated. 
Increasing the number of vCPUs to 10, finally, reduces latency dramatically:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2023/09/wallclock-profiler/latency-after.png&quot; alt=&quot;latency more cpu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Though they will never correspond exactly, the sampled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RUNNABLE&lt;/code&gt; wall time and CPU times have converged:
&lt;img src=&quot;/assets/2023/09/wallclock-profiler/more-cpu-cpu-time.png&quot; alt=&quot;Wall time&quot; /&gt;
&lt;img src=&quot;/assets/2023/09/wallclock-profiler/more-cpu-wall-time.png&quot; alt=&quot;CPU time&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and the timeline for a particular trace shows 10 threads with denser blue CPU samples:
&lt;img src=&quot;/assets/2023/09/wallclock-profiler/more-cpu-timeline.png&quot; alt=&quot;timeline&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-about-cost&quot;&gt;What about cost?&lt;/h2&gt;

&lt;p&gt;This probably isn’t very satisfying: the cost of the running service has been increased dramatically.
However, the aim was to reduce request latency for a contrived workload that isn’t amenable to optimisations.
When code is at its efficient frontier (or &lt;em&gt;effectively&lt;/em&gt; at its efficient frontier without, say, incurring huge engineering costs), there is a tradeoff between latency and cost.
If you don’t have an SLA, you can just run this workload on 2vCPUs and a 1GiB heap for very little cost, and though ZGC will throttle allocations and it will be slow, things will basically work.
If you do have an SLA, and optimisation is impractical or impossible, and you can’t negotiate requirements, you’ll need to incur costs and allocate more compute resources.
It’s quite rare for a Java service to be at its efficient frontier, and there’s usually low hanging fruit surfaced by continuous profiling which can sometimes reduce latency and cost simultaneously.&lt;/p&gt;</content><author><name></name></author><category term="profiling" /><summary type="html">It can be hard to figure out why response times are high in Java applications. In my experience, people either apply a process of elimination to a set of recent commits, or might sometimes use profiles of the system to explain changes in metrics. Making guesses about recent commits can be frustrating for a number of reasons, but mostly because even if you pinpoint the causal change, you still might not know why it was a bad change and are left in limbo. In theory, using a profiler makes root cause analysis a part of the triage process, so adopting continuous profiling should make this whole process easier, but using profilers can be frustrating because you’re using the wrong type of profile for analysis. Lots of profiling data focuses on CPU time, but the cause of your latency problem may be related to time spent off CPU instead. This post is about Datadog’s Java wallclock profiler, which I worked on last year, and explores how to improve request latency without making any code changes, or even seeing the code for that matter.</summary></entry><entry><title type="html">Using JFR and JMC to root cause a performance bug in JMC caused by a typo in JFR</title><link href="https://richardstartin.github.io/posts/jmc-hanging" rel="alternate" type="text/html" title="Using JFR and JMC to root cause a performance bug in JMC caused by a typo in JFR" /><published>2022-12-18T00:00:00+00:00</published><updated>2022-12-18T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/jmc-hanging</id><content type="html" xml:base="https://richardstartin.github.io/posts/jmc-hanging">&lt;p&gt;When you open a profile in JMC, it normally takes a few seconds, but there are some profiles that JMC struggles to load.
This happens when a profile contains events which violate an assumption made in JMC’s parser: events on the same thread are almost always disjoint in their durations.
When JMC parses a JFR file, it splits the events of the same type emitted on the same thread into lanes so that events within a lane are disjoint.
JMC can handle overlapping events, but assumes this essentially never happens, and exhibits quadratic scaling when events overlap.&lt;/p&gt;

&lt;p&gt;The good news is that unless you hack it into the JFR file generation in your own customisation of async-profiler, it’s practically impossible to generate events that violate this assumption.
However, if you run this code:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;provide a directory to write the JFR file to&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Recording&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()){&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;slow&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDumpOnExit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDestination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getOrCreateDirectory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;slow.jfr&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;enable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;jdk.FileWrite&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;recording&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;generateFileEvents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;generateFileEvents&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;directory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createTempDirectory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;files&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RandomAccessFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RandomAccessFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;directory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;rw&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
               &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;data&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StandardCharsets&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;UTF_8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getOrCreateDirectory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Path&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Paths&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Files&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createDirectory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then open the .jfr file in JMC, you will see this for quite some time (well over 30s):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/slow.png&quot; alt=&quot;Slow&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Fortunately JMC itself can be profiled as it is loading a profile, using JFR with the command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jcmd &amp;lt;pid of JMC&amp;gt; JFR.start settings=profile filename=jmcrecording.jfr duration=60s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After 60s a profile will be dumped containing execution samples of whatever JMC spent its time doing.
Loading the profile of JMC itself and navigating to &lt;em&gt;Method Profiling&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/navigate.png&quot; alt=&quot;Method Profiling&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This shows you a flamegraph of CPU samples:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/before.png&quot; alt=&quot;Cause&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There are over 6000 samples, which is a good number for a JFR CPU profile, which takes samples at about 100Hz.
If most activity is during loading the profile is on the same thread, at 100Hz, having 6000 samples means we got one sample every 10ms so don’t have a lot of failed samples, which JFR doesn’t otherwise account for.
Assuming there is no JNI activity, this means the profile should be reasonably accurate.
It’s tempting to scroll to the bottom and look for the widest frame and optimise it.
The obvious candidate here are the calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ImpreciseScaleFactor.targetNumber&lt;/code&gt;, outlined in black below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/leaf.png&quot; alt=&quot;Red Herring&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Though there is a little fat to trim in this method, this is a red herring; optimising this method would be a waste of time and it would take an heroic effort to move the needle by focusing here.
CPU sampling can’t differentiate between a method being slow (requiring constant factors improvements) and being called too often (requiring algorithmic improvements).
Unfortunately, JFR offers no way to perform a differential diagnosis to rule out constant factors and focus on algorithmic issues.&lt;/p&gt;

&lt;p&gt;Once you have identified a bottleneck, the easiest way to differentiate between a constant factors and algorithmic issue is honestly just to read the code for a few of the frames towards the bottom of the stacktrace.
Sometimes, this is too complicated, and I have resorted to inserting counter probes in these methods with bytebuddy (but I recently discovered &lt;a href=&quot;https://github.com/jvm-profiling-tools/async-profiler#java-method-profiling&quot;&gt;async-profiler can do this&lt;/a&gt; and I have adopted this approach in preference to instrumentation during analyses).
If you have a superlinear algorithm, you see high hit counts at the leaves, and low hit counts in the interior nodes.
In any case, reading the code for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DisjointBuilder.add&lt;/code&gt; (outlined in black below):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/circled.png&quot; alt=&quot;DisjointBuilder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;reveals what looks like an optimisation:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;c1&quot;&gt;// at 77580fe1b483b4daf8f46938c297185b01f32304&lt;/span&gt;
	&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startAccessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endAccessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUnit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noLanes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;addToNewLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;changedLane&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addToOtherLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;sortLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changedLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sortLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Sorting the lanes by descending end time&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fromIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compareTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;nc&quot;&gt;DisjointArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addToOtherLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Try with the other existing lanes&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addToNewLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;addToNewLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noLanes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;lanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DisjointArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;noLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lanes[0].accept&lt;/code&gt; is obviously expected to be the common case (it happens whenever the events are disjoint).
Its implementation appears to be a constant time operation too, assuming the comparator isn’t bizarre:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IQuantity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newCapacity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newCapacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compareTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addToOtherLane&lt;/code&gt; shows up in the flamegraph (outlined in black):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/addToOtherLane.png&quot; alt=&quot;DisjointBuilder&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So we must be going down the other branch:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;changedLane&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addToOtherLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sortLanes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changedLane&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addToOtherLane&lt;/code&gt; does a linear search for the first lane for which the last event ends before the event being added.
This can obviously be improved by replacing the linear search with binary search, since the sort order of the lanes by end time is maintained.
This doesn’t describe all of the samples though, and it seems strange that so many samples should be in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MemberAccessorToolkit$2.getMember&lt;/code&gt;, and where is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sortLanes&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/getMember.png&quot; alt=&quot;MemberAccessToolkit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This only makes sense if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sortLanes&lt;/code&gt; (which is a quadratic bubble sort) has been inlined.
Knowing that the lanes are maintained in sort order, when a lane needs to move, it’s possible to find where it needs to move to in logarithmic time, and then perform a linear time copy.
This makes the algorithm linear in the number of lanes.&lt;/p&gt;

&lt;p&gt;These changes were implemented in &lt;a href=&quot;https://github.com/openjdk/jmc/pull/449&quot;&gt;JMC-7950&lt;/a&gt;, which will be released in as part of JMC 9, and the issue is more or less fixed.
If you open the profile generated by the code at the top of the post in JMC 9, it will still be slower to load than you might have hoped, but it will be several times faster.
Using JFR to take a profile of JMC loading the same profile after the change produces a much smaller number of samples (JFR also records much less CPU time) and shows different stack traces:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/12/jmc-hanging/after.png&quot; alt=&quot;Fixed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Erik Gahlin pointed out on the issue that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jdk.FileWrite&lt;/code&gt; events making profiles slow to load must be because of a bug in JFR.
It turned out there has been a bug in JFR since JDK15, introduced &lt;a href=&quot;https://hg.openjdk.java.net/jdk/jdk/rev/dd0caf00b05c&quot;&gt;here&lt;/a&gt;.
The problematic change is here:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     &lt;span class=&quot;nd&quot;&gt;@SuppressWarnings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;deprecation&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;nd&quot;&gt;@JIInstrumentationMethod&lt;/span&gt;
     &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[])&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;        &lt;span class=&quot;nc&quot;&gt;FileWriteEvent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileWriteEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;EVENT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;nc&quot;&gt;EventHandler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handlers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;FILE_WRITE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
             &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytesWritten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;        &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
             &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bytesWritten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;bytesWritten&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;            &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &amp;lt;-- bug here&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;shouldCommit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytesWritten&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The duration should have the start time subtracted from it. 
This means that these events will generally overlap because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end = start + duration&lt;/code&gt; makes a nonsense value. 
The problem is compounded because the faulty duration will always exceed the latency threshold, which means these events flood JFR files to the extent they stress the parsing code.&lt;/p&gt;</content><author><name></name></author><category term="java" /><category term="profiling" /><summary type="html">When you open a profile in JMC, it normally takes a few seconds, but there are some profiles that JMC struggles to load. This happens when a profile contains events which violate an assumption made in JMC’s parser: events on the same thread are almost always disjoint in their durations. When JMC parses a JFR file, it splits the events of the same type emitted on the same thread into lanes so that events within a lane are disjoint. JMC can handle overlapping events, but assumes this essentially never happens, and exhibits quadratic scaling when events overlap.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2022/12/jmc-hanging/slow.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2022/12/jmc-hanging/slow.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Evaluating Equality Predicates</title><link href="https://richardstartin.github.io/posts/range-bitmap-equality-queries" rel="alternate" type="text/html" title="Evaluating Equality Predicates" /><published>2022-12-18T00:00:00+00:00</published><updated>2022-12-18T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/range-bitmap-equality-queries</id><content type="html" xml:base="https://richardstartin.github.io/posts/range-bitmap-equality-queries">&lt;p&gt;I have just &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap/pull/606&quot;&gt;implemented support&lt;/a&gt; for (in)equality queries against a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, a succinct data structure in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; library which supports range queries.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; was designed to support range queries in Apache Pinot (more details &lt;a href=&quot;https://richardstartin.github.io/posts/range-bitmap-index&quot;&gt;here&lt;/a&gt;) but this enhancement would allow a range index to be used as a fallback for (in)equality queries in case nothing better is available.
Supporting (in)equality queries allows a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; to be used as a kind of compact inverted index, trading space for time, capable of supporting high cardinality gracefully.&lt;br /&gt;
Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; supports memory mapping from files, I think that it could be used for data engineering beyond Apache Pinot.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#how-to-use-it&quot; id=&quot;markdown-toc-how-to-use-it&quot;&gt;How to use it?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#how-does-it-work&quot; id=&quot;markdown-toc-how-does-it-work&quot;&gt;How does it work?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;how-to-use-it&quot;&gt;How to use it?&lt;/h3&gt;

&lt;p&gt;This post extends the example set up in &lt;a href=&quot;https://richardstartin.github.io/posts/range-predicates&quot;&gt;Evaluating Range Predicates&lt;/a&gt; and &lt;a href=&quot;https://richardstartin.github.io/posts/range-counts&quot;&gt;Counting over Range Predicates&lt;/a&gt;, which centre around a large collection of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt; objects:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s find all the transactions with a certain quantity in the most obvious way possible, using the stream API.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On my laptop, it takes about 3ms to select about 100 transactions from 1M.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2849.444527&lt;/td&gt;
        &lt;td&gt;30.581160&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;With a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, these same transactions can be found with the already existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;between&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;as explained in &lt;a href=&quot;https://richardstartin.github.io/posts/range-predicates&quot;&gt;Evaluating Range Predicates&lt;/a&gt;, the quantities in the index have been anchored to the smallest value in the population, as a size optimisation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This performs quite a lot better, selecting the same transactions in under 300us, which is a 10x improvement.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2849.444527&lt;/td&gt;
        &lt;td&gt;30.581160&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;between&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;296.488277&lt;/td&gt;
        &lt;td&gt;0.703309&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;This can be rewritten using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eq&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eq&lt;/code&gt; method doesn’t need to do as much work as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;between&lt;/code&gt;, which needs to maintain and combine two bitsets during the scan over the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, whereas &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eq&lt;/code&gt; only needs one.
This means we can get a good speedup from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eq&lt;/code&gt; to select the same transactions:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2849.444527&lt;/td&gt;
        &lt;td&gt;30.581160&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;between&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;296.488277&lt;/td&gt;
        &lt;td&gt;0.703309&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;eq&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;183.913329&lt;/td&gt;
        &lt;td&gt;1.994362&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;It’s worth making a comparison with an inverted index over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quantity&lt;/code&gt; (so a bitmap of transaction positions per quantity) now.
The inverted index would always win for speed on this query, but can take up a lot more space, and the inverted index would almost always lose for range queries.
A range encoded inverted index (a mapping from quantity to bitmap of the positions of all transactions with a smaller quantity) will generally beat &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; for speed at the cost of space.
This makes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; suitable for equality queries eiter on a high cardinality attribute or as a fallback better than scanning when range queries are more common.&lt;/p&gt;

&lt;p&gt;Inequality filters tend not to be very selective, so benchmarking the evaluation would be dominated by the time to scan the results, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neq&lt;/code&gt; method is present for API symmetry.&lt;/p&gt;

&lt;p&gt;In the same set of changes there are also methods to push a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; context down into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eq&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neq&lt;/code&gt; queries, which behaves like an intersection. 
Rather than producing a large bitmap and then intersecting it with a small context bitmap, the context bitmap is used to potentially skip over large sections of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;.
There are also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eqCardinality&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;neqCardinality&lt;/code&gt; methods, which produce counts rather than bitmaps, as described for &lt;a href=&quot;https://richardstartin.github.io/posts/range-counts&quot;&gt;range counts&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; has a simple two-dimensional structure.
The first dimension is the rows in the order the values were added in.
The second dimension is the binary representation of each value added, after range encoding, and there are only as many of these columns as there are significant bits in the largest value added.&lt;/p&gt;

&lt;p&gt;The layout is essentially the bit-transposition of the values added, striped in bands of $2^{16}$ rows, with a little bit of metadata to help traversals. 
Each $2^{16}$ rows are bucketed into a &lt;em&gt;horizontal slice&lt;/em&gt; which consists of $64 - clz(max)$ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; containers, one for each bit of the inputs, and a mask of size $64 - clz(max)$ bits, indicating the presence of a container. 
If the $n$th value added to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; does not have bit $i$ set, the $i$th container in the $n/2^{16}$th slice will have no bit set.
If all $2^{16}$ values in the slice have bit $i$ unset, the mask will have bit $i$ unset and no container, which is an optimisation to avoid storing empty containers.&lt;/p&gt;

&lt;p&gt;If the following values (with their binary representation in brackets) are added in sequence&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;42 (101010)
24 (011000)
9  (001001)
27 (011011)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The values are first negated to range encode them (see &lt;a href=&quot;https://richardstartin.github.io/posts/range-bitmap-index&quot;&gt;here&lt;/a&gt; for explanation).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;010101
100111
110110
100100
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There will be one horizontal slice with four rows in it, a 6 bit mask, and 5 containers per slice because the 4th bit is present in all the values in the slice.
This looks something like this, if stored as plain bitsets:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;110111 1100 0110 1111 1010 0110 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are three kinds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; containers for different densities - sparse, dense, and run length encoded.
Containers with only four values would always be sparse, for the sake of explanation, assume these are just the first four values in a much larger slice.
Encoded as containers, this looks like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;110111 [4,2] {2,4} [0,4] 1010 {2,4}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where the containers in braces have been represented as arrays of 16 bit values, the ones in square brackets are run length encoded, and the binary numbers are just bitsets.&lt;/p&gt;

&lt;p&gt;To evaluate queries against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, the slices need to be iterated over in ascending order.
For each slice, the containers need to be extracted and combined, using a different combination algorithm for each relation.
The equality combination algorithm is very simple:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;start with a bitset &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; with a set bit for each row in the slice&lt;/li&gt;
  &lt;li&gt;for each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; from 0 to the size of the slice’s mask,
    &lt;ul&gt;
      &lt;li&gt;if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; is set in the query value, remove the container &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;’s bits (or none, if container &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; is missing for the current slice) from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;If &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; is absent in the query value, intersect container &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;’s bits with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; (or just clear it id the container is missing for the slice)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For inequality, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; needs to be complemented at the end. 
Range queries are generally more complex to evaluate than this, but all queries are evaluated in this slice by slice in ascending row order fashion.&lt;/p&gt;</content><author><name></name></author><category term="java" /><category term="roaring" /><summary type="html">I have just implemented support for (in)equality queries against a RangeBitmap, a succinct data structure in the RoaringBitmap library which supports range queries. RangeBitmap was designed to support range queries in Apache Pinot (more details here) but this enhancement would allow a range index to be used as a fallback for (in)equality queries in case nothing better is available. Supporting (in)equality queries allows a RangeBitmap to be used as a kind of compact inverted index, trading space for time, capable of supporting high cardinality gracefully. Since RangeBitmap supports memory mapping from files, I think that it could be used for data engineering beyond Apache Pinot.</summary></entry><entry><title type="html">Counting over Range Predicates</title><link href="https://richardstartin.github.io/posts/range-counts" rel="alternate" type="text/html" title="Counting over Range Predicates" /><published>2022-03-27T00:00:00+00:00</published><updated>2022-03-27T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/range-counts</id><content type="html" xml:base="https://richardstartin.github.io/posts/range-counts">&lt;p&gt;This post follows on from my &lt;a href=&quot;/posts/range-predicates&quot;&gt;last post&lt;/a&gt; about selecting objects satisfying a range predicate, and instead looks at how to count the objects.
If you can select objects, you can count them too, but it’s a simpler problem so resources can be saved with a specialised solution.&lt;/p&gt;

&lt;p&gt;Suppose you want to count how many of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt; objects below satisfy complex filters, and don’t care about details about the objects themselves.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once again, this sounds like a task for a database, but imagine you don’t have one to rely on.&lt;/p&gt;

&lt;p&gt;You might use the streams API:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will inspect every transaction to check if it matches the constraints, and will count the number of matches.
For 1M transactions, with parameters which select about 250 transactions, this takes about 10ms on my laptop:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;It took less time to actually select the objects, which, without any knowledge of the implementation, suggests that the objects are probably selected in order to be counted.
The transactions are the same as those in the previous post, and sorted by time. 
This means reordering the conditions to the time filter is applied first is as effective as it was last time by reducing the number of branch misses.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3283.291345&lt;/td&gt;
        &lt;td&gt;44.566458&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Something easy to ignore in a Stream API benchmark is how much better the performance is when there is only one implementation of the filter, which allows the filter to be inlined.
This makes the code in the benchmark artificially simple, when really the whole point of the API is that many filters would be used, which is a triumph of modularity over efficiency (but you should use the right tool for the job and know when such a difference matters).
Artificially polluting the filter in the benchmark setup shows how artificial the result for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stream2&lt;/code&gt; was:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;
              &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3283.291345&lt;/td&gt;
        &lt;td&gt;44.566458&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2Polluted&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;7801.823477&lt;/td&gt;
        &lt;td&gt;41.229256&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-counts/range-count-time-streams.png&quot; alt=&quot;Results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Inlining or no inlining, it’s inefficient to use linear search if the transactions are sorted, which they are (by time).
As far as I’m aware there’s no way to communicate to the Stream API that a collection is already sorted and can be skipped over, and it doesn’t know how to crack filter predicates to exploit this anyway.
So avoiding the Stream API and writing old fashioned Java code is effective.
In the benchmark data, the time range predicate selects about 10% of the data, so the other two predicates should only apply to this much data.
Assume the timestamps are unique for simplicity’s sake:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; 
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3283.291345&lt;/td&gt;
        &lt;td&gt;44.566458&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2Polluted&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;7801.823477&lt;/td&gt;
        &lt;td&gt;41.229256&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;786.482285&lt;/td&gt;
        &lt;td&gt;1.994657&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Sorting is not included in the benchmark time, but data is often naturally sorted by time.
It’s not a lot more code but goes a lot faster, and easier to measure because it’s so simple.
JMH’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perfnorm&lt;/code&gt; profiler shows that there is still a good number of branches missed, despite shedding about 90% of the work.
This means the instructions per cycle (IPC) is low:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;786.482285&lt;/td&gt;
        &lt;td&gt;1.994657&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:IPC&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0.755879&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;insns/clk&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:branch-misses&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;50739.339319&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:branches&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;434330.541115&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;This can be fixed by complicating the filter in the scan somewhat.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; 
        &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If the transaction’s quantity is greater than the threshold, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Math.max(transaction.quantity - threshold, 0)&lt;/code&gt; will be positive, wrapping this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Math.min(1, x)&lt;/code&gt; means that the count will be one if the predicate holds and zero if it doesn’t.
This can be added to the other condition and divided by two so the count will be incremented by one when both conditions hold, and by zero when they don’t.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Math.max&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Math.min&lt;/code&gt; are special and get compiled to conditional move instructions which aren’t speculated so can’t miss.
They are annotated as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@IntrinsicCandidate&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nd&quot;&gt;@IntrinsicCandidate&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@IntrinsicCandidate&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means if you have a hot loop, they should be preferred to inlining the equivalent logic, which may or may not be compiled to the same code.
They’re also more readable, in my opinion.
The branch misses basically disappear, and the instructions per cycle is now much higher:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;786.482285&lt;/td&gt;
        &lt;td&gt;1.994657&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:IPC&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0.755879&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;insns/clk&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:branch-misses&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;50739.339319&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch:branches&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;434330.541115&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;299.725900&lt;/td&gt;
        &lt;td&gt;2.414697&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan:IPC&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;3.278388&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;insns/clk&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan:branch-misses&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;524.965594&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan:branches&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;403042.075820&lt;/td&gt;
        &lt;td&gt;NaN&lt;/td&gt;
        &lt;td&gt;#/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;However, I am fairly certain that no analytical database written in Java implements counting over predicates like this.
This is because fusing the predicate with aggregation is hostile to modularity, though generating code specialised to the problem is a good option if the implementation needs to be both fast and modular.
It also won’t work with arbitrary numbers of predicates and needs to be padded to the next power of 2 so a matching result can be shifted down to one or zero in order to avoid integer division.
If the JIT compiler were smarter and could inline the predicate evaluation into the count operator, I imagine it could transform modular code into this form, but it would probably get it wrong as often as it gets it right.
So this is probably an artificially good result, but it is the best so far.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3283.291345&lt;/td&gt;
        &lt;td&gt;44.566458&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2Polluted&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;7801.823477&lt;/td&gt;
        &lt;td&gt;41.229256&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;786.482285&lt;/td&gt;
        &lt;td&gt;1.994657&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;299.725900&lt;/td&gt;
        &lt;td&gt;2.414697&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-counts/range-count-time-streams-binarySearch.png&quot; alt=&quot;Results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In my last post I used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; - which is the data structure used in &lt;a href=&quot;http://github.com/apache/pinot&quot;&gt;Apache Pinot&lt;/a&gt;’s range index - to select transactions matching the predicate.
Combined with binary search to apply the time range filter, it was much faster than any other implementation I measured.&lt;/p&gt;

&lt;p&gt;I have recently &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap/pull/555&quot;&gt;implemented&lt;/a&gt; the ability to count values matching a predicate, instead of producing a bitmap of their offsets.
The API is symmetric with the selection API:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestampIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantityIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lteCardinality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  
  &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;binarySearchThenIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmapOfRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantityIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lteCardinality&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; is likely to be suboptimal for counting because the data structure is optimised for producing a sorted output set, which isn’t required for counting, but if you have a data stucture to support selection it ought to be able to provide a count without materialising intermediate results.
For selection, just using an index for all three attributes and combining the results was a lot faster than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binarySearch&lt;/code&gt; but for counting it’s a little disappointing at the moment.
I haven’t tried to optimise the counting methods yet so there is probably a lot of low-hanging fruit.
Cutting the range down with the binary search first produces a good result, even better than the hard to modularise &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;binarySearchBranchFreeScan&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;9721.806835&lt;/td&gt;
        &lt;td&gt;27.935884&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3283.291345&lt;/td&gt;
        &lt;td&gt;44.566458&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2Polluted&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;7801.823477&lt;/td&gt;
        &lt;td&gt;41.229256&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;786.482285&lt;/td&gt;
        &lt;td&gt;1.994657&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchBranchFreeScan&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;299.725900&lt;/td&gt;
        &lt;td&gt;2.414697&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;index&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;666.951907&lt;/td&gt;
        &lt;td&gt;14.861610&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchThenIndex&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;78.191544&lt;/td&gt;
        &lt;td&gt;17.903472&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-counts/range-count-time.png&quot; alt=&quot;Results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; was designed for use in &lt;a href=&quot;http://github.com/apache/pinot&quot;&gt;Apache Pinot&lt;/a&gt;, so is compressed and supports zero-copy mapping to and from disk.
There are more details about how it works in depth in &lt;a href=&quot;https://richardstartin.github.io/posts/range-bitmap-index&quot;&gt;RangeBitmap - How range indexes work in Apache Pinot&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The benchmark used in this post is not particularly scientific but is &lt;a href=&quot;https://github.com/richardstartin/range-benchmark/blob/master/src/main/java/io/github/richardstartin/range/CountTransactionsBenchmark.java&quot;&gt;here&lt;/a&gt;.
If you want to run it, you will get different numbers, but the rank of each benchmark score should not change.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="java" /><category term="roaring" /><summary type="html">This post follows on from my last post about selecting objects satisfying a range predicate, and instead looks at how to count the objects. If you can select objects, you can count them too, but it’s a simpler problem so resources can be saved with a specialised solution.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2022/03/range-counts/range-count-time.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2022/03/range-counts/range-count-time.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Evaluating Range Predicates</title><link href="https://richardstartin.github.io/posts/range-predicates" rel="alternate" type="text/html" title="Evaluating Range Predicates" /><published>2022-03-12T00:00:00+00:00</published><updated>2022-03-12T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/range-predicates</id><content type="html" xml:base="https://richardstartin.github.io/posts/range-predicates">&lt;p&gt;Suppose you are doing some kind of data analysis in Java, perhaps you are analysing transactions (as in sales made).
You have complex filters to evaluate before performing a calculation on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Transaction&lt;/code&gt; objects.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Suppose you want to find all transactions in a time range, and where the quantity exceeds a threshold but the price is lower than another threshold.
This is clearly work that could be pushed down into a database, but you may not be able to do this for a couple of possible reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You don’t have huge data volumes but your management may have been convinced not to procure a proper database with a SQL interface&lt;/li&gt;
  &lt;li&gt;You may not have a database because you have a lot of data, and it’s cheaper to store it in something like S3, you have processing jobs which filter the data programmatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For whatever reason, you don’t have a database with a SQL interface and have to do the filtering yourself in a Java program, how might you do it?&lt;/p&gt;

&lt;p&gt;You might use the streams API:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will inspect every transaction to check if it matches the constraints.
For 1M transactions, with parameters which select about 250 transactions, this takes about 10ms on my laptop:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Suppose the transactions are actually sorted by time, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt; conditions should be predictable but aren’t evaluated first.
This means the unpredictable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;price&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;quantity&lt;/code&gt; conditions are evaluated for every transaction. 
Reordering the conditions halves the runtime!&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3859.494033&lt;/td&gt;
        &lt;td&gt;42.012070&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;This is an interesting result, and JMH’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perfnorm&lt;/code&gt; profiler explains why:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1:branches&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;4537456.732&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1:branch-misses&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;505305.308&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3859.494033&lt;/td&gt;
        &lt;td&gt;42.012070&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2:branches&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;4648262.253&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2:branch-misses&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;51040.523&lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;There are about the same number of branches in each case, but ~10x fewer branch misses.
Branch misses are expensive!&lt;/p&gt;

&lt;p&gt;However, if the transactions are sorted by time, linear search will be wasteful unless the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt; range covers most of data.
In my benchmark, it covers about 10% of the data, so binary searching for the first and last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt;s in the range could correspond to up to ~10x improvement.
Assume the timestamps are unique for simplicity’s sake:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; 
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;quantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;price&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;price&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3859.494033&lt;/td&gt;
        &lt;td&gt;42.012070&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1204.366198&lt;/td&gt;
        &lt;td&gt;8.455373&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Sorting to ensure this is a valid application of binary search is not included in the benchmark time.&lt;/p&gt;

&lt;p&gt;If several filters need to be evaluated against the collection, it would make sense to perform the sort to benefit from this.&lt;/p&gt;

&lt;p&gt;This particular problem - applying range predicates to unsorted data - is solved by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; data structure in the &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap&quot;&gt;RoaringBitmap&lt;/a&gt; library.
The data structure is immutable and benefits from knowledge of the range of values in the data set, but if several filters need to be evaluated, building an index on each attribute could be worth it.
The data structure has a build-then-use lifecycle:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minTimestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxTimestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxQty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;minTimestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;maxTimestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;maxQty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestampAppender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxTimestamp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceAppender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyAppender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxQty&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;timestampAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;priceAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;qtyAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestampIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestampAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyIndex&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyAppender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whether the two passes over the data or the half page of code are worth it depends on how many filters you need to do and how fast they need to be.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; produces a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; of the indexes which satisfy a predicate, and can take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; parameters as inputs to skip over rows already filtered out.
The Streams API code used before is translated into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; API calls:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestampIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minTimeThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;maxTimeThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minQtyThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxPriceThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;matchesPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The anchoring by the minimum values for each attribute is a little convoluted but improves efficiency (unless the minimum value is zero anyway) and this would be better abstracted by a convenience class in a real application.
For the same data this is ~2x faster than the binary search approach:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3859.494033&lt;/td&gt;
        &lt;td&gt;42.012070&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1204.366198&lt;/td&gt;
        &lt;td&gt;8.455373&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;index&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;690.562119&lt;/td&gt;
        &lt;td&gt;25.725005&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Time spent building the index is not included in the benchmark time since it is assumed the filter will be applied several times and building the index will be amortised.&lt;/p&gt;

&lt;p&gt;Binary search is much faster than the algorithm &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; uses, and it’s a shame it can only be used on one attribute since it’s not possible to do a global sort on more than one attribute.
If the data is sorted by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt;, a hybrid approach can be taken:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; 
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Collections&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Transaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Comparator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;comparingLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;Transaction:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getTimestamp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmapOfRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;first&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;qtyIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;gte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxPriceThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inTimeRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesPrice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;priceIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;minQtyThreshold&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;minQty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matchesQuantity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;matchesPrice&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;IntConsumer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processTransaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transactions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is over 100x faster than the original Streams API code and takes under 100us, all else being equal.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: minPrice&lt;/th&gt;
        &lt;th&gt;Param: minQuantity&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;stream1&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8885.846120&lt;/td&gt;
        &lt;td&gt;41.192708&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;stream2&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3859.494033&lt;/td&gt;
        &lt;td&gt;42.012070&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearch&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1204.366198&lt;/td&gt;
        &lt;td&gt;8.455373&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;index&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;690.562119&lt;/td&gt;
        &lt;td&gt;25.725005&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;binarySearchThenIndex&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;82.893010&lt;/td&gt;
        &lt;td&gt;12.486953&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;100&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1000000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-predicates/range-filter-time.png&quot; alt=&quot;Results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; was designed to power the range indexes in &lt;a href=&quot;http://github.com/apache/pinot/pulls&quot;&gt;Apache Pinot&lt;/a&gt;, so is compressed and supports zero-copy mapping to and from disk.
There are more details about how it works in depth in &lt;a href=&quot;https://richardstartin.github.io/posts/range-bitmap-index&quot;&gt;RangeBitmap - How range indexes work in Apache Pinot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s likely you’re sensible and pushing your filters down to your database to evaluate, but this data structure may be useful in some applications.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The benchmark used in this post is not particularly scientific but is &lt;a href=&quot;https://github.com/richardstartin/range-benchmark/blob/master/src/main/java/io/github/richardstartin/range/FindTransactionsBenchmark.java&quot;&gt;here&lt;/a&gt;.
If you want to run it, you will get different numbers, but the rank of each benchmark score should not change.&lt;/p&gt;
&lt;/blockquote&gt;</content><author><name></name></author><category term="java" /><category term="roaring" /><summary type="html">Suppose you are doing some kind of data analysis in Java, perhaps you are analysing transactions (as in sales made). You have complex filters to evaluate before performing a calculation on Transaction objects.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2022/03/range-predicates/range-filter-time.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2022/03/range-predicates/range-filter-time.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">RangeBitmap - How range indexes work in Apache Pinot</title><link href="https://richardstartin.github.io/posts/range-bitmap-index" rel="alternate" type="text/html" title="RangeBitmap - How range indexes work in Apache Pinot" /><published>2022-03-07T00:00:00+00:00</published><updated>2022-03-07T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/range-bitmap-index</id><content type="html" xml:base="https://richardstartin.github.io/posts/range-bitmap-index">&lt;p&gt;Suppose you have an unsorted array of numeric values and need to find the set of indexes of all the values which are within a range.
The range predicate will be evaluated many times, so any time spent preprocessing will be amortised, and non-zero spatial overhead is expected.
If the data were sorted, this would be very easy, but the indexes of the values have meaning so the data cannot be sorted.
To complicate the problem slightly, the set of indexes must be produced in sorted order.&lt;/p&gt;

&lt;p&gt;These are the requirements for a range index in a column store, where the row indexes implicitly link values stored separately in different columns.
The purpose of a range index is to accelerate a query like the one below which identifies all spans in a time window where the duration exceeds 10ms (in nanoseconds):&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;traceId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;spanId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;operationName&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;spans&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10000000&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;between&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2022-03-06 02:00:00.000'&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2022-03-06 03:00:00.000'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;How can the spans be indexed to make this query as fast as it can be?
Different databases solve this problem in different ways, but the context of this post is a column store like &lt;a href=&quot;https://github.com/apache/pinot&quot;&gt;Apache Pinot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It’s unusual for there to be only one important class of query, so other constraints need to be taken into account.
The query above for slow spans isn’t the only query that needs to be fast, queries like the one below to compute the time spent in each operation of a trace also need to execute quickly:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt;  
  &lt;span class=&quot;n&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;operationName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;spans&lt;/span&gt; 
&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;traceId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1234578910&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;operationName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Apache Pinot tables consist of &lt;em&gt;segments&lt;/em&gt; which usually correspond to the data ingested from a stream within a time window, and will generally have the same implicit partitioning as the stream.
Segments have a columnar layout, and contain their indexes, metadata, and other data structures. 
Segments are independent of each other and all data structures are scoped to their segment; data is sorted within the scope of a segment and not globally, and so on.&lt;/p&gt;

&lt;p&gt;A Pinot cluster consists of four different classes of service which have different responsibilities, of which two are relevant to how to optimise this query.
&lt;em&gt;Servers&lt;/em&gt; are responsible for querying segments, they know how to read the segment format, load indexes, prune (avoid querying) and query the segments and merge segment-level query results into server-level query results.
&lt;em&gt;Brokers&lt;/em&gt; are responsible for routing queries to servers and merging server-level results, they can perform some amount of segment pruning to protect the servers’ resources.&lt;/p&gt;

&lt;p&gt;The second query needing to be fast imposes constraints on what can be done to optimise the first query.
Firstly, brokers can use knowledge of &lt;a href=&quot;https://docs.pinot.apache.org/operators/operating-pinot/tuning/routing#partitioning&quot;&gt;segment partitioning&lt;/a&gt; to optimise query routing.
If we need the second query to be fast, we should partition on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; which limits the number of servers the broker need to route the query to.
Unless the trace’s duration exceeds the time taken to produce one segment (which may happen when tracing batch systems), all the spans in a trace should fit in a single segment, so partitioning by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; should mean only a single segment need be queried.
This means that the first query (find all the slow spans in the last hour) will not be able to use partitioning to prune segments and the query will be routed to all servers.&lt;/p&gt;

&lt;p&gt;The second query will also need an index on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; to be efficient because a typical segment will contain millions of rows, and scanning all these rows will be too slow.
Choosing an appropriate index requires some knowledge of the data (though this decision can be automated given representative sample data).
In case you are unfamiliar with tracing, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spanId&lt;/code&gt; are high cardinality attributes and there is a hierarchical relationship between spans and traces: a trace is a collection of spans.
We should expect that traces last at most minutes and consist of fewer than 1000 spans in the typical case.&lt;/p&gt;

&lt;p&gt;Pinot has &lt;a href=&quot;https://www.startree.ai/blogs/what-makes-apache-pinot-fast-chapter-ii&quot;&gt;several types of index&lt;/a&gt;, but the choice here would be between a &lt;a href=&quot;https://docs.pinot.apache.org/basics/indexing/forward-index#sorted-forward-index-with-run-length-encoding&quot;&gt;sorted index&lt;/a&gt; and an &lt;a href=&quot;https://docs.pinot.apache.org/basics/indexing/inverted-index&quot;&gt;inverted index&lt;/a&gt;.
Sorted indexes offer the best space/time tradeoff of Pinot’s indexes but, unsurprisingly, a sorted index can only be applied to a sorted column, and only one column can be sorted.
This makes the choice of sorted column one of the biggest decisions when designing a Pinot table.&lt;/p&gt;

&lt;p&gt;Given the hierarchical relationship between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spanId&lt;/code&gt;, and an expectation that most traces will contain far fewer than 1000 spans, sorting on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; indexes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spanId&lt;/code&gt; for free because a scan for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spanId&lt;/code&gt; would inspect only spans within the same trace.
Creating an inverted index on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; is a possibility, but it would have quite high cardinality: if there are 10M spans in a segment, and 100 spans per trace, then there would be 100k postings lists.
Moreover, the performance of the aggregation in the second query depends on locality, which sorting improves.
In this case, the case for sorting by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceId&lt;/code&gt; probably trumps sorting by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;duration&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fortunately, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt; filter can be used to prune segments if it is configured as the &lt;a href=&quot;https://docs.pinot.apache.org/integrations/superset#configuring-time-column&quot;&gt;time column&lt;/a&gt;.
Segments have metadata including the minimum and maximum value of the time column, and these time boundary values can be used to eliminate segments before querying them.
The filter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp between '2022-03-06 02:00:00.000' and '2022-03-06 03:00:00.000'&lt;/code&gt; narrows the segments to query down to those with boundary values which intersect with the hour specified by the query.
Assuming that each segment corresponds to 3 hours of ingested events within its partition, pruning by time boundary cuts the number of segments which need to be pruned from all segments &lt;em&gt;ever&lt;/em&gt; to at most twice the number of partitions.
However, within each of the unpruned segments, there are still millions of records which need to be filtered to find the slow spans in the time range, and the rows within the segments aren’t sorted by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;timestamp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All of this is enough to justify the existence of a range index in a system like Pinot.
This post is about the design and implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, a data structure which powers Pinot’s new range indexes.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#implementing-a-range-index&quot; id=&quot;markdown-toc-implementing-a-range-index&quot;&gt;Implementing a Range Index&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#bit-slicing&quot; id=&quot;markdown-toc-bit-slicing&quot;&gt;Bit slicing&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#generalisation-to-higher-bases&quot; id=&quot;markdown-toc-generalisation-to-higher-bases&quot;&gt;Generalisation to higher bases&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#example&quot; id=&quot;markdown-toc-example&quot;&gt;Example&lt;/a&gt;        &lt;ol&gt;
          &lt;li&gt;&lt;a href=&quot;#initialise-state&quot; id=&quot;markdown-toc-initialise-state&quot;&gt;Initialise state&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#union-with-slice-0&quot; id=&quot;markdown-toc-union-with-slice-0&quot;&gt;Union with slice 0&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#intersection-with-slice-1&quot; id=&quot;markdown-toc-intersection-with-slice-1&quot;&gt;Intersection with slice 1&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#intersection-with-slice-2&quot; id=&quot;markdown-toc-intersection-with-slice-2&quot;&gt;Intersection with slice 2&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#union-with-slice-3-and-terminate&quot; id=&quot;markdown-toc-union-with-slice-3-and-terminate&quot;&gt;Union with slice 3 and terminate&lt;/a&gt;&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#optimisations&quot; id=&quot;markdown-toc-optimisations&quot;&gt;Optimisations&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#horizontal-and-vertical-evaluations&quot; id=&quot;markdown-toc-horizontal-and-vertical-evaluations&quot;&gt;Horizontal and Vertical Evaluations&lt;/a&gt;        &lt;ol&gt;
          &lt;li&gt;&lt;a href=&quot;#or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1&quot; id=&quot;markdown-toc-or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1&quot;&gt;OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 0, 1&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3&quot; id=&quot;markdown-toc-or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3&quot;&gt;OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 2, 3&lt;/a&gt;&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#rangebitmap-design&quot; id=&quot;markdown-toc-rangebitmap-design&quot;&gt;RangeBitmap design&lt;/a&gt;    &lt;ol&gt;
      &lt;li&gt;&lt;a href=&quot;#layout&quot; id=&quot;markdown-toc-layout&quot;&gt;Layout&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#construction-and-memory-mapping&quot; id=&quot;markdown-toc-construction-and-memory-mapping&quot;&gt;Construction and Memory Mapping&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#encoding&quot; id=&quot;markdown-toc-encoding&quot;&gt;Encoding&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#querying-a-rangebitmap&quot; id=&quot;markdown-toc-querying-a-rangebitmap&quot;&gt;Querying a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#performance-evaluation&quot; id=&quot;markdown-toc-performance-evaluation&quot;&gt;Performance Evaluation&lt;/a&gt;&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#usage-in-apache-pinot&quot; id=&quot;markdown-toc-usage-in-apache-pinot&quot;&gt;Usage in Apache Pinot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#links&quot; id=&quot;markdown-toc-links&quot;&gt;Links:&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;implementing-a-range-index&quot;&gt;Implementing a Range Index&lt;/h2&gt;

&lt;p&gt;Zooming into the data now, imagine you have an array of durations like those below, which are all small numbers for the sake of simplicity:&lt;/p&gt;

&lt;p&gt;$[10, 3, 15, 0, 0, 1, 5, 6, 2, 1, 12, 14, 3, 9, 11]$&lt;/p&gt;

&lt;p&gt;We want to get the row indexes of the values which satisfy predicates so attributes at the same indexes can be selected.
We also want the sets to be sorted so scans over other columns are sequential which is more efficient than traversing in random order.
From the list above we would want the following outputs for each of the predicates below:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;predicate&lt;/th&gt;
        &lt;th&gt;expected output&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;$x &amp;lt; 3$&lt;/td&gt;
        &lt;td&gt;$ \{3, 4, 5, 8, 9 \} $&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$x &amp;lt; 10$&lt;/td&gt;
        &lt;td&gt;$ \{ 1, 3, 4, 5, 6, 7, 8, 9, 12, 13 \}$&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$x &amp;gt; 5$&lt;/td&gt;
        &lt;td&gt;$ \{0, 2, 7, 10, 11, 13, 14 \}$&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$2 &amp;lt; x &amp;lt; 10$&lt;/td&gt;
        &lt;td&gt;$ \{ 1, 6, 7, 12, 13 \}$&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$5 &amp;lt; x &amp;lt; 10$&lt;/td&gt;
        &lt;td&gt;$ \{ 7, 13 \}$&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Or represented as bitmaps which can be iterated over we would have the following:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;$&amp;lt; 3$&lt;/th&gt;
        &lt;th&gt;$&amp;lt; 10$&lt;/th&gt;
        &lt;th&gt;$&amp;gt; 5$&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Representing the array indexes as bitmaps is beneficial because they can be combined with bitmaps obtained from other indexes to satisfy complex filter constraints without needing indexes which themselves can understand every filter constraint.
The following filters should be pushed down into a range index for efficiency reasons, but demonstrate how to build a double bounded range from two single bounded ranges:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;$&amp;lt; 10$&lt;/th&gt;
        &lt;th&gt;$&amp;gt; 5$&lt;/th&gt;
        &lt;th&gt;$5 &amp;lt; x &amp;lt; 10$&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0 &amp;amp; 1 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0 &amp;amp; 1 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 1 = 1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0 &amp;amp; 1 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0 &amp;amp; 1 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 0 = 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1 &amp;amp; 1 = 1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0 &amp;amp; 1 = 0&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Pinot’s indexes use &lt;a href=&quot;http://roaringbitmap.org&quot;&gt;RoaringBitmap&lt;/a&gt; to represent filters, so the range index will produce a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; too.&lt;/p&gt;

&lt;h2 id=&quot;bit-slicing&quot;&gt;Bit slicing&lt;/h2&gt;

&lt;p&gt;The algorithm used to do range evaluations is described in the 1998 paper &lt;a href=&quot;https://www.comp.nus.edu.sg/~chancy/sigmod98.pdf&quot;&gt;Bitmap Index Design and Evaluation&lt;/a&gt;.
This algorithm has been used at least twice before: the paper mentions that SybaseIQ used it in the 90s, and &lt;a href=&quot;https://www.pilosa.com/blog/range-encoded-bitmaps/&quot;&gt;pilosa&lt;/a&gt; also implemented it in the last decade. 
To understand it, first look at the binary layout of the numbers in the example, which have all been chosen to be representable in 4 bits to limit the number of columns I need to write.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;bit 3&lt;/th&gt;
        &lt;th&gt;bit 2&lt;/th&gt;
        &lt;th&gt;bit 1&lt;/th&gt;
        &lt;th&gt;bit 0&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;The columns can be used to evaluate filters.
For instance, the numbers greater than seven all have a bit in the leftmost column.
Looking at the leftmost column would be enough to evaluate $x &amp;gt; 7$ but not $x &amp;gt; 8$ which also requires at least one bit in the three columns to the right.
This gets coarser as the number of bits in the numbers increases so trying to post-filter wouldn’t scale well.&lt;/p&gt;

&lt;p&gt;To arrive at an algorithm, each column can be &lt;em&gt;range encoded&lt;/em&gt; so that there are two &lt;em&gt;slices&lt;/em&gt; for each column: a bit is set in the $i$th slice if the bit is less than or equal to $i  \in \{0, 1\}$.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;predicate&lt;/th&gt;
        &lt;th&gt;bit 3&lt;/th&gt;
        &lt;th&gt;bit 2&lt;/th&gt;
        &lt;th&gt;bit 1&lt;/th&gt;
        &lt;th&gt;bit 0&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;$\leq 0$&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt; &lt;/td&gt;
        &lt;td&gt;$\leq 1$&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Of course, a bit can only ever be $\leq 1$ because they only take two values, so the second slice is redundant and the table becomes:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;bit 3&lt;/th&gt;
        &lt;th&gt;bit 2&lt;/th&gt;
        &lt;th&gt;bit 1&lt;/th&gt;
        &lt;th&gt;bit 0&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;This mapping is easy to perform because it’s just the logical complement of the input values masked by how many bits we care about, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x -&amp;gt; ~x &amp;amp; 0xF&lt;/code&gt; in this case.&lt;/p&gt;

&lt;p&gt;Now there is a bitset in each slice:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;0&lt;/th&gt;
        &lt;th&gt;1&lt;/th&gt;
        &lt;th&gt;2&lt;/th&gt;
        &lt;th&gt;3&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;100110011011000&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;000111100110010&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;110111001100111&lt;/code&gt;&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;010111111100100&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Performing a transposition like this incurs no spatial overhead, and can end up smaller than storing the data itself if the values have many leading zeros allowing slices to be pruned, especially if bitmap compression is applied to the columns.&lt;/p&gt;

&lt;p&gt;A predicate $x \leq t$ can be evaluated against the slices, assuming $n$ rows were indexed as follows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Assume all rows match, initialise bitmap &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; to $[0, n)$, that is set every bit between 0 and $n$.&lt;/li&gt;
  &lt;li&gt;for each bit in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt;:
    &lt;ol&gt;
      &lt;li&gt;if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t[i]&lt;/code&gt; is set, set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state | slices[i]&lt;/code&gt;
        &lt;ol&gt;
          &lt;li&gt;These bits need to be included in the result because they mean this coefficient in the input was less than the same coefficient of the threshold&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t[i]&lt;/code&gt; is not set, set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state &amp;amp; slices[i]&lt;/code&gt;
        &lt;ol&gt;
          &lt;li&gt;Bits absent in the slice need to be removed because it means the bit was present&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To evaluate an $x &amp;gt; t$ predicate, just take the logical complement of the result of an $x \leq t$ predicate.
Predicates $x &amp;lt; t$ or $x \geq t$ can be evaluated by adding or subtracting 1 from the threshold and evaluating a $x \leq t$ or $x &amp;gt; t$ predicate respectively.
A double ended range $t \leq x \leq u$ can be evaluated by intersecting $x &amp;gt; t$ and $x \leq u$.&lt;/p&gt;

&lt;p&gt;This algorithm has strengths and weaknesses. 
Firstly, it should be clear it can’t compete with the potentialities of sorting the data: binary search over run-length encoded values or even the raw data has lower time complexity.
The complexity is linear in the product of the number of rows and the number of slices, so evaluation time depends on the largest value in the data set.
However, logical operations between bitmaps are very efficient and can be vectorised, so hundreds of rows can be evaluated in a handful of CPU instructions.
Space is saved by storing the row indexes implicitly in the positions of the bits (and this is improved by the compression explained later), and it automatically produces sorted outputs.
The various tree data structures which could also solve this problem can’t compress the row indexes (though some can compress the values) and would all need to sort the row indexes after evaluating the predicate, which prevents lazy evaluation.&lt;/p&gt;

&lt;h3 id=&quot;generalisation-to-higher-bases&quot;&gt;Generalisation to higher bases&lt;/h3&gt;

&lt;p&gt;Any number can be expressed as the coefficients of a polynomial with base $b$, binary numbers just have coefficients 0 or 1.
Other bases can be chosen, which create space/time tradeoffs best visualised by considering the representations of numbers in different bases:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;base&lt;/th&gt;
        &lt;th&gt;representation&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;111010110111100110100010101&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;22121022020212200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;13112330310111&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;726746425&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;123456789&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;75bcd15&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;In base-2, a large number 123456789 has 27 digits, but only one slice per digit, so the range encoding operation is lightning fast and has no write-amplification.
At the other end of the spectrum, in base-16 there are only 7 digits but 15 slices per digits.
Reducing the number of digits reduces the number of bitmap operations which need to be performed, but increase spatial overhead and write amplification.
&lt;a href=&quot;https://www.comp.nus.edu.sg/~chancy/sigmod98.pdf&quot;&gt;Bitmap Index Design and Evaluation&lt;/a&gt; explores this tradeoff (see &lt;a href=&quot;https://github.com/richardstartin/range-index&quot;&gt;here&lt;/a&gt; for implementations).&lt;/p&gt;

&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;

&lt;p&gt;This is an example of how to evaluate the predicate $x \leq 9$ or $x &amp;lt; 10$ against a 4-bit base-2 index. &lt;br /&gt;
There is a table for each step of the algorithm:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The far left column in each has the value as encountered in the data, the next four columns represent the values stored in the index; these columns never change.&lt;/li&gt;
  &lt;li&gt;The next column represents the state bitmap, which changes on each step of the algorithm (and if it doesn’t, this step should be eliminated if it can be detected).&lt;/li&gt;
  &lt;li&gt;The final two columns represent the &lt;em&gt;target&lt;/em&gt;, the bitmap we know we want to have produced once the algorithm has terminated, and whether the state bitmap is currently accurate.
    &lt;ul&gt;
      &lt;li&gt;The far right column should give the intuition that the algorithm doesn’t converge to a solution and randomly flips bits depending on what’s in the slice. 
In this example, the hamming distance between the state and the solution only increases until the last step.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The bold column is the slice being operated on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The threshold 9 in binary is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1001&lt;/code&gt;, so the first and last slices will be united, and the middle slices will be intersected.&lt;/p&gt;

&lt;h4 id=&quot;initialise-state&quot;&gt;Initialise state&lt;/h4&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h4 id=&quot;union-with-slice-0&quot;&gt;Union with slice 0&lt;/h4&gt;

&lt;p&gt;Perform a destructive logical union between the first slice and the state, modifying the state bitmap.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;This step didn’t actually change the state bitmap, and could have been eliminated by checking if the LSB is set.&lt;/p&gt;

&lt;h4 id=&quot;intersection-with-slice-1&quot;&gt;Intersection with slice 1&lt;/h4&gt;

&lt;p&gt;Perform a destructive logical intersection between the second slice and the state bitmap, modifying the state bitmap.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h4 id=&quot;intersection-with-slice-2&quot;&gt;Intersection with slice 2&lt;/h4&gt;

&lt;p&gt;Perform a destructive logical intersection between the third slice and the state bitmap, modifying the state bitmap.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h4 id=&quot;union-with-slice-3-and-terminate&quot;&gt;Union with slice 3 and terminate&lt;/h4&gt;

&lt;p&gt;Perform a destructive logical union between the fourth slice and the state bitmap, modifying the state bitmap.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;6&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;12&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;14&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;9&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;There are no more slices so the algorithm terminates here.&lt;/p&gt;

&lt;h3 id=&quot;optimisations&quot;&gt;Optimisations&lt;/h3&gt;

&lt;p&gt;The algorithm’s time complexity is super-linear, depending on both the number of indexed rows and how many slices are present in the indexed values.
This means that every slice which doesn’t need to be operated on (the first step in the example above was redundant) should be eliminated.&lt;/p&gt;

&lt;p&gt;There are various fast-forwarding optimisations which eliminate bitmap operations between slices at the start of the evaluation.
Some of these depend on the threshold:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Evaluating the result for the first slice has redundancy: if the LSB is set in the threshold, the first union is redundant. 
Similarly, if it isn’t set, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; bitmap can be initialised to the values of the first slice; initialising to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, max)&lt;/code&gt; beforehand is redundant.&lt;/li&gt;
  &lt;li&gt;If there is a run of $k$ set bits starting from 0, all $k$ operations can be eliminated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Others depend on the data:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;If there is an empty slice at position $k$ and the bit $k$ is absent from the threshold $x$, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; bitmap will be empty afterwards.
All $k$ operations can be replaced by just setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; to $\emptyset$.&lt;/li&gt;
  &lt;li&gt;If there is a full slice at position $k$ and the bit $k$ is present in the threshold $x$, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; bitmap will be full afterwards, so all $k$ operations can be replaced by setting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;state&lt;/code&gt; to $[0, max)$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example above was for a 4-bit index, but arbitrary size numbers (64 bits is an artificial limit) can be indexed in theory, given a choice of a maximum value.
However, allocating capacity for values much larger than the largest indexed value leads to unnecessary slices and slower evaluations.
Knowing the maximum value reduces the number of slices which need to be operated on.&lt;/p&gt;

&lt;p&gt;Similarly, if the minimum value is large, subtracting it from each value reduces the number of slices by a factor of $\log_2(x_{min})$.
This is especially important for indexing timestamps, where the minimum timestamp in a data set will often have a large offset from 1970-01-01.
At the time of writing, the unix epoch time is $1646510472$, and a collection of timestamps over the next 24 hours would fall in the range $[1646510472, 1646596872]$.
The number of slices required to encode this range would be $ \lceil \log_2( 1646596872 ) \rceil = 31$, whereas to index $[0, 1646596872 - 1646510472]$ or $[0, 86400]$ only  $ \lceil \log_2(86400) \rceil = 17$ slices are required, which roughly halves the evaluation time.&lt;/p&gt;

&lt;h3 id=&quot;horizontal-and-vertical-evaluations&quot;&gt;Horizontal and Vertical Evaluations&lt;/h3&gt;

&lt;p&gt;An important implementation choice is whether to operate on an entire slice at a time (vertically) or across a small horizontal section of all slices producing a section of the final result (horizontally).&lt;/p&gt;

&lt;p&gt;In the step by step example of the algorithm above, the evaluation was vertical.
Instead, let’s say we perform all four operations two rows at a time (recall that to evaluate $v \leq 9$ there was a union followed by two intersections and a final union) and append the partial result to an output incrementally.
The evaluation would proceed as follows:&lt;/p&gt;

&lt;h4 id=&quot;or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1&quot;&gt;OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 0, 1&lt;/h4&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;15&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;❓&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❓️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❓️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❓️&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h4 id=&quot;or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3&quot;&gt;OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 2, 3&lt;/h4&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;value&lt;/th&gt;
        &lt;th&gt;slice 3&lt;/th&gt;
        &lt;th&gt;slice 2&lt;/th&gt;
        &lt;th&gt;slice 1&lt;/th&gt;
        &lt;th&gt;slice 0&lt;/th&gt;
        &lt;th&gt;state&lt;/th&gt;
        &lt;th&gt;target&lt;/th&gt;
        &lt;th&gt; &lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;10&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;3&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;✔️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❓️&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;0&lt;/td&gt;
        &lt;td&gt;-&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;❓️&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;In reality, hundreds or thousands of bits would be operated on at a time; two was just chosen to make it possible to visualise.&lt;/p&gt;

&lt;p&gt;There are pros and cons either way.
Owen Kaser and Daniel Lemire describe the trade-offs in &lt;a href=&quot;https://arxiv.org/pdf/1402.4073.pdf&quot;&gt;Threshold and Symmetric Functions over Bitmaps&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Implementations of bitmap operations can be described as “horizontal” or “vertical”. 
The former implementation produces its output incrementally, and can be stopped early. 
The latter implementation produces the entire completed bitmap. 
Horizontal approaches would be preferred if the output of the algorithm is to be consumed immediately, as we may be able to avoid storing the entire bitmap. 
As well, the producer and consumer can run in parallel. Vertical implementations may be simpler and have less overhead. 
If the output of the algorithm needs to be stored because it is consumed more than once, there may be no substantial space advantage to a horizontal implementation.
With uncompressed bitmaps, a particularly horizontal implementation exists: a word of each input is read, after which one word of the output is produced.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are good reasons to prefer a horizontal approach beyond the ability to iterate over the result before it has been computed.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cache efficiency&lt;/strong&gt;: operating on slices horizontally means that no horizontal section of a slice ever needs to be loaded twice. Intermediate state for computing a horizontal section of the result can stay in cache if it is reused.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Density&lt;/strong&gt;: if we have an arbitrary collection of values, there is no good reason to expect the slices to be sparse, unless the values are sorted. If the values were sorted, there are better choices of data structure.
This means that the ability of compressed bitmaps like RoaringBitmap (see &lt;a href=&quot;http://roaringbitmap.org&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;/posts/roaringbitmap-performance-tricks&quot;&gt;here&lt;/a&gt;) to exploit sparseness to prune intersections will be unreliable. 
If there are regions where some slices are sparse, so long as fast-forward optimisations are implemented, some work can be pruned anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My benchmarks in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; library showed that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; usually outperforms a vertical implementation using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; by more than 2x.&lt;/p&gt;

&lt;h2 id=&quot;rangebitmap-design&quot;&gt;RangeBitmap design&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap/blob/master/RoaringBitmap/src/main/java/org/roaringbitmap/RangeBitmap.java&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;&lt;/a&gt; in the &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap&quot;&gt;RoaringBitmap Java library&lt;/a&gt; implements this algorithm.
The data structure reuses some of RoaringBitmap’s building blocks, so here is a quick recap.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; is a compressed bitmap which prefix compresses integers, grouping values into aligned buckets of width $2^{16}$, storing the high 16 bits of each integer just once.
There are different kinds of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Container&lt;/code&gt; for storing the lower 16 bits, depending on the distribution of values within the bucket.
If there are fewer than $2^{12}$ values in the bucket, an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ArrayContainer&lt;/code&gt; - just a sorted array of 16 bit values - is used to store the values. 
Otherwise a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BitmapContainer&lt;/code&gt; - an 8KB bitset - is used instead.
However, if the values can be run-length encoded into fewer than $2^{11}$ runs, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RunContainer&lt;/code&gt; is used, which is just an array of 16-bit values, where the value at every even index is the starting position of a run, and the next value is the length of the run. 
The array is sorted by the starting positions of the runs.&lt;/p&gt;

&lt;h3 id=&quot;layout&quot;&gt;Layout&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt;’s container implementations to represent the slices, but does away with prefix compression.
This is based on a few heuristics which appear to hold for real-world datasets:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Lower order slices will be incompressible in most cases (the least significant slice could only ever be run-length encoded, and only if there are runs of even or odd values).&lt;/li&gt;
  &lt;li&gt;Unless the values are entirely random, higher order slices will be very dense or very sparse as values will tend to cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes prefix compression useless for lower order slices because every row will have values here, and gives it a good chance of being useless in higher order slices depending on how the values cluster and whether the slices are full or empty.
If every $2^{16}$ range is partially populated, the prefix can be represented implicitly by the container’s position.
A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; of size $n$ has $ \lceil \frac{n}{2^{16}} \rceil $ 16 bit values to store integer prefixes.
We should expect &lt;em&gt;at least&lt;/em&gt; the bottom 5 slices (i.e. the values modulo 32) to have values in every $2^{16}$ range, so we can save at least $5 \times 2 \times \lceil \frac{n}{2^{16}} \rceil$ bytes by getting rid of the prefixes, even if all the higher order slices somehow end up empty or sparse.
Getting rid of the prefixes and using the position to store the high 16 bits implicitly frees up space for metadata which can be used to determine whether a slice has any values for a $2^{16}$ range or not.&lt;/p&gt;

&lt;p&gt;For each $2^{16}$ range, there is a mask which has the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt;th bit set if slice &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; is non-empty.
These masks are stored contiguously, and if all slices are empty in a $2^{16}$ range, then an empty mask needs to be stored because the high 16 bits are implied by the position.
The mask sizes are rounded up to the next multiple of 8 so that long sequences of leading zeros are not stored (if there are only 10 slices, a 64 bit mask would be wasteful, and a 16 bit mask would be used instead).&lt;/p&gt;

&lt;p&gt;After the masks, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; containers are stored in ascending slice order.
If a slice is empty for the $n$th slice, it is not stored, which makes random access to a $2^{16}$ range impossible - the containers must be iterated from the start.&lt;/p&gt;

&lt;p&gt;There is a small header before the masks which helps with memory-mapping:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;field&lt;/th&gt;
        &lt;th&gt;size (bytes)&lt;/th&gt;
        &lt;th&gt;purpose&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;cookie&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;allows checks whether a supplied buffer is not a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, as well as evolution&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;base&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;this allows evolution to e.g. base 4 or base 8 slicing&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;slice count&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;determines how many slices exist, which allows the size of the mask to&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;number of masks&lt;/td&gt;
        &lt;td&gt;2&lt;/td&gt;
        &lt;td&gt;this is actually redundant and could have been derived from the next field&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;max row&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
        &lt;td&gt;the index of the last value, e.g. 10M if 10M values are indexed&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;h3 id=&quot;construction-and-memory-mapping&quot;&gt;Construction and Memory Mapping&lt;/h3&gt;

&lt;p&gt;A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; is immutable, and there are good reasons for this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Slice pruning optimisations work better when the range of values is known ahead of time.&lt;/li&gt;
  &lt;li&gt;There’s not much point in supporting mutability without supporting concurrent updates, and there’s a lot of state to keep consistent, even though every update is an append. 
Fast query execution relies on the ability to vectorise operations, which would be hampered by supporting row level concurrent appends.&lt;/li&gt;
  &lt;li&gt;It just isn’t necessary for OLAP where mutable data structures are short-lived and immutable data structures are long-lived. 
It would be better to choose a data structure which simplifies concurrent updates at the expense of query efficiency for indexing live data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A two phase construction process where values are first buffered and then sealed models this best:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;getLongValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;appender:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To write the data to disk so that it can be memory mapped (or otherwise loaded from disk into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer&lt;/code&gt;), the process is a little more convoluted:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;maxValue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;getLongValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;appender:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;ByteBuffer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;allocateByteBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serializedSizeInBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serialize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After executing the code above, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer&lt;/code&gt; will contain the header described above, the masks, and some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; containers laid out so that it can be queried without deserialization.
A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; can be loaded from a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer&lt;/code&gt; in a matter of nanoseconds as follows:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The usual caveats of memory mapping apply, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; doesn’t care how the bytes get from disk into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ByteBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;encoding&quot;&gt;Encoding&lt;/h3&gt;

&lt;p&gt;Values need to be encoded into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt;s before indexing, which allows any primitive type to be indexed with the same API.
Moreover, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; interprets &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt; values in unsigned order, which simplifies slice layout.
This makes using the data structure fairly user-unfriendly, but this was a suitable API for its use in Apache Pinot’s range index.
For example, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;double&lt;/code&gt; is mapped to an unsigned &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt; so that ordering is preserved as follows:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ordinalOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;POSITIVE_INFINITY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xFFFFFFFFFFFFFFFF&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;NEGATIVE_INFINITY&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isNaN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Double&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doubleToLongBits&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// need negatives to come before positives&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// conflate 0/-0, or reverse order of negatives&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// positives after negatives&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Subtracting the minimum value from every other value, as is done in Apache Pinot’s range index for the sake of reducing the number of slices, automatically anchors the value range to zero and makes all values implicitly unsigned.&lt;/p&gt;

&lt;h3 id=&quot;querying-a-rangebitmap&quot;&gt;Querying a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;Querying a mapped &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; is straightforward, with the API described by the table:&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;operation&lt;/th&gt;
        &lt;th&gt;method&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x &amp;lt; t \} $&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap lt(long threshold)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x &amp;lt; t \} \cap C$&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap lt(long threshold, RoaringBitmap context)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x \leq t \} $&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap lte(long threshold)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x \leq t \} \cap C$&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap lte(long threshold, RoaringBitmap context)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x &amp;gt; t \} $&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap gt(long threshold)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x &amp;gt; t \} \cap C$&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap gt(long threshold, RoaringBitmap context)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x \geq t \}$&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap gte(long threshold)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : x \geq t \} ∩ C$&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap gte(long threshold, RoaringBitmap context)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : t \leq x \leq u \} $&lt;/td&gt;
        &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap between(long min, long max)&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;$ \{ x : t \leq x \leq u \} \cap C$&lt;/td&gt;
        &lt;td&gt;Currently unimplemented&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Some methods support pushing a set intersection down into range predicate evaluation, which allows skipping over regions of rows not found in the set to be intersected with.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rangeBitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createRangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intersection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoarinBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The following is equivalent to the code above, but what’s below is more efficient because it doesn’t need to materialise an intermediate bitmap and skips doing range evaluations where the intersection would definitely be empty:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rangeBitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createRangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;intersection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;threshold&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;performance-evaluation&quot;&gt;Performance Evaluation&lt;/h3&gt;

&lt;p&gt;I was reluctant to add this section to the post because it can’t really be a fair comparison and is more of a sanity check.
Pinot already had range indexes, and the goal of implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; was to improve on the old implementation, and very early measurements indicated that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; would improve performance by 5-10x, so alternatives were not evaluated exhaustively.
I haven’t included every possible implementation in this comparison, but I feel it’s reasonably balanced because I demonstrate how much faster queries over sorted data are.&lt;/p&gt;

&lt;p&gt;Various implementations of this interface are compared with values of different distributions, the size of the input is 76MB (10M &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt;s) in each case:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The distributions are varied just to show that it actually matters, rather than systematically. 
The distributions all produce duplicate values and the size of the range depends on the distributions because the benchmark selects ranges based on values at statically defined ranks.
This means that results are comparable given a choice of distribution, but it also demonstrates that &lt;em&gt;your mileage may vary&lt;/em&gt; as peculiarities of data sets can be an important factor. 
For implementations which only work with sorted inputs, the data is sorted beforehand.&lt;/p&gt;

&lt;p&gt;As a baseline, imagine you’re lucky enough to have sorted data and can perform an out-of-the-box binary search.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Arrays.binarySearch&lt;/code&gt; doesn’t make guarantees about which index is produced when there are duplicates so a linear scan is needed to find it after yielding an index (though this could use, say, a quadratic probing algorithm).&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BinarySearch&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BinarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmapOfRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whilst this has no spatial overhead, the linear search is problematic and it can perform quite badly when there are many duplicates (see $\exp(0.5)$).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-binarySearch.png&quot; alt=&quot;Binary search latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To get around this, the data structure below is a naive approximation to Apache Pinot’s sorted index:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntervalsEvaluator&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IntervalsEvaluator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;uniqueValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ranges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmapOfRange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BYTES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BYTES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-intervals.png&quot; alt=&quot;Intervals latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This could be micro-optimised for locality, but it’s hard to beat for this problem, even though it has a spatial overhead of up to 1.5x the size of the input.
Note that the $\exp(0.5)$ case is slower because the range is larger because there are lots of duplicates, but none of these distributions produce unique values, so the size is always smaller than the data.
&lt;img src=&quot;/assets/2022/03/range-bitmap-index/serialized-size-intervals.png&quot; alt=&quot;Intervals serialized size&quot; /&gt;
In fact, none of the approaches which support range queries over unsorted data will even get close to this.&lt;/p&gt;

&lt;p&gt;The first implementation to support unsorted inputs is a natural extension of binary search: make a copy of the data, sort it, and store an array of the indexes where the value was stored in the unsorted array.
This is included as a proxy for the way trees which sort the values but not the indexes which I never found justification to implement.
These would also need to store and post-process the indexes.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SortedValuesUnsortedIndexesEvaluator&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SortedValuesUnsortedIndexesEvaluator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IntStream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mapToObj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sorted&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mapToLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;indexes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;mapToInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BYTES&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BYTES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Comparable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;LongIntPair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;compareTo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;compare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;that&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LongIntPair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;that&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;that&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hashCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This doesn’t perform very well, and storing it requires 1.5x the size of the data, despite finding the boundaries quickly when there are few duplicates.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-sortedValuesUnsortedIndexes.png&quot; alt=&quot;Sorted Values Unsorted Indexes latency&quot; /&gt;
&lt;img src=&quot;/assets/2022/03/range-bitmap-index/serialized-size-sortedValuesUnsortedIndexes.png&quot; alt=&quot;Sorted Values Unsorted Indexes serialized  size&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Part of the problem is it’s not very efficient to build a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; from unsorted data, and maybe Pinot needing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; is an artificial constraint.
If building a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; weren’t the bottleneck, copying and sorting a section of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;indexes&lt;/code&gt; array might be.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InvertedIndexEvaluator&lt;/code&gt; is a natural extension of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalsEvaluator&lt;/code&gt;, instead of storing the start of the range, it stores a bitmap of row indexes which have the value.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InvertedIndexEvaluator&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InvertedIndexEvaluator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sortedValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;uniqueValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;copyOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmapWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmapWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numRanges&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmapWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;writers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;writers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmaps&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;ss&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serializedSizeInBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serializedSize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ss&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;binarySearch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uniqueValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finish&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;or&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bitmaps&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a good option when there are a lot of duplicates, but needing to merge many tiny bitmaps to produce an output is the bottleneck.
&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-invertedIndex.png&quot; alt=&quot;Inverted index latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The index is always smaller than the data:
&lt;img src=&quot;/assets/2022/03/range-bitmap-index/serialized-size-invertedIndex.png&quot; alt=&quot;Inverted index serialized size&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pinot’s old range index was quite similar to this, except the posting lists corresponded to buckets, and required a scan to remove false positives after filtering.
By range encoding an inverted index (that is, when indexing a row with a value update all posting lists greater than the value too) range queries can be very fast, but the indexes end up huge and the consequent write amplification is unacceptable.&lt;/p&gt;

&lt;p&gt;The simplest approach is a scan:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Scan&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Scan&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;RoaringBitmapWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmapWriter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This has no spatial overhead but is very slow, though this could be done a lot faster by Java programs once the Vector API is available so will be a better option in the future.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-scan.png&quot; alt=&quot;Scan latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmapEvaluator&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RangeBitmapEvaluator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIN_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RangeBitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;datum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;serializedSize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serializedSizeInBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitmap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appender&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RoaringBitmap&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitmap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;between&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serializedSize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It doesn’t get close to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IntervalsEvaluator&lt;/code&gt;, but it’s the least bad considered option for unsorted values, it is always smaller than the data.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-rangeBitmap.png&quot; alt=&quot;RangeBitmap latency&quot; /&gt;
&lt;img src=&quot;/assets/2022/03/range-bitmap-index/serialized-size-rangeBitmap.png&quot; alt=&quot;RangeBitmap serialized size&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Excluding the implementations which only work on sorted inputs, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; looks like the least bad option.
It is only beaten by the inverted index in one case, is an order of magnitude faster than scanning or the sorted-values/unsorted-indexes approach and the inverted index in other cases.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/range-latency-all.png&quot; alt=&quot;RangeBitmap latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It also has the least spatial overhead except for scanning which has none.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2022/03/range-bitmap-index/serialized-size-all.png&quot; alt=&quot;RangeBitmap latency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The source code for these benchmarks is located &lt;a href=&quot;http://github.com/richardstartin/range-benchmark&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;usage-in-apache-pinot&quot;&gt;Usage in Apache Pinot&lt;/h2&gt;

&lt;p&gt;Apache Pinot allows range indexing of immutable segments, and since 0.9.0 these have been implemented in terms of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt;, enabled via a feature flag.
From 0.10.0, this feature flag is enabled by default, and just needs the configuration in the documentation &lt;a href=&quot;https://docs.pinot.apache.org/basics/indexing/range-index&quot;&gt;here&lt;/a&gt;.
Users who have enabled the feature flag (which I won’t name: just upgrade to 0.10.0) have reported improvements of upwards of 5x on Pinot’s old range index implementation.&lt;/p&gt;

&lt;h2 id=&quot;links&quot;&gt;Links:&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.startree.ai/blogs&quot;&gt;StarTree blog&lt;/a&gt; covers Apache Pinot&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/c/StarTree&quot;&gt;StarTree YouTube channel&lt;/a&gt; curates Apache Pinot related content&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://communityinviter.com/apps/apache-pinot/apache-pinot&quot;&gt;Apache Pinot Slack channel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Apache Pinot project on &lt;a href=&quot;https://github.com/apache/pinot&quot;&gt;github&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Apache Pinot &lt;a href=&quot;https://docs.pinot.apache.org&quot;&gt;documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Apache Pinot Range index &lt;a href=&quot;https://docs.google.com/document/d/1se2OgqXJiD7r7S7U6SUmTIAApO66QIrAYosxvXHEXlw/edit&quot;&gt;design document&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Apache Pinot range index &lt;a href=&quot;https://github.com/apache/pinot/blob/f9ab252980e4f973d60b9db2a0f5e7d5764bdaf2/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/inv/BitSlicedRangeIndexCreator.java&quot;&gt;creator&lt;/a&gt; and &lt;a href=&quot;https://github.com/apache/pinot/blob/0031f2444c8684eb515245d9298ae8b336268aa4/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/readers/BitSlicedRangeIndexReader.java&quot;&gt;reader&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;RoaringBitmap &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap&quot;&gt;Java Library&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RangeBitmap&lt;/code&gt; &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap/blob/master/RoaringBitmap/src/main/java/org/roaringbitmap/RangeBitmap.java&quot;&gt;implementation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;High level &lt;a href=&quot;https://www.markhneedham.com/blog/2021/12/07/apache-pinot-exploring-range-queries/&quot;&gt;overview&lt;/a&gt; of Pinot range queries&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><category term="java" /><category term="roaring" /><category term="pinot" /><summary type="html">Suppose you have an unsorted array of numeric values and need to find the set of indexes of all the values which are within a range. The range predicate will be evaluated many times, so any time spent preprocessing will be amortised, and non-zero spatial overhead is expected. If the data were sorted, this would be very easy, but the indexes of the values have meaning so the data cannot be sorted. To complicate the problem slightly, the set of indexes must be produced in sorted order.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2022/03/range-bitmap-index/range-latency-all.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2022/03/range-bitmap-index/range-latency-all.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Performance Myths and Continuous Profiling</title><link href="https://richardstartin.github.io/posts/perf-myths-and-continuous-profiling" rel="alternate" type="text/html" title="Performance Myths and Continuous Profiling" /><published>2021-12-27T00:00:00+00:00</published><updated>2021-12-27T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/perf-myths-and-continuous-profiling</id><content type="html" xml:base="https://richardstartin.github.io/posts/perf-myths-and-continuous-profiling">&lt;p&gt;My &lt;a href=&quot;/posts/5-java-mundane-performance-tricks&quot;&gt;last post&lt;/a&gt; was about five very simple things you can do to avoid Java programs from being slower than they need to be.
The reception to this post was mixed.
Some readers agreed that the problems mentioned in the post were indeed very common, while others suggested more common inefficient patterns to avoid.
For example, I could have suggested to precompile regular expressions, or not to program by exception, or to avoid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.format&lt;/code&gt;, but I felt this was all well covered already.
The aim of the post was to help Java programmers to program defensively for efficiency - just as many do for correctness - by giving an idea of what some common things cost.
However, several readers dismissed the content of the post as premature optimisation.&lt;/p&gt;

&lt;p&gt;It’s interesting how negative responses to posts about minor software efficiencies can be.
There will be various causes for this reaction, but a common theme is the existence of performance myths perpetuated by bad blog posts.
I certainly hope that I’m not perpetuating performance myths but this position is understandable.&lt;/p&gt;

&lt;p&gt;Performance myths can be split into two categories, and I’ll give an example of each:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Those that were never true&lt;/li&gt;
  &lt;li&gt;Those that used to be true&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think it’s the second category which cause the strongest reactions because they counteract future improvements.
In this post I take a look at examples of both before discussing continuous profiling since the items I chose were based on things I’ve seen show up in profiles a lot.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables&quot; id=&quot;markdown-toc-never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables&quot;&gt;Never true: prefer pre-increment to post-increment for loop induction variables&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#true-once-prefer-stringbuilder-to-string-concatenation&quot; id=&quot;markdown-toc-true-once-prefer-stringbuilder-to-string-concatenation&quot;&gt;True once: prefer StringBuilder to string concatenation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#continuous-profiling&quot; id=&quot;markdown-toc-continuous-profiling&quot;&gt;Continuous profiling&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables&quot;&gt;Never true: prefer pre-increment to post-increment for loop induction variables&lt;/h3&gt;

&lt;p&gt;I have encountered claims that loops which use pre-increment for loop induction variables are faster than those which use post-increment.
So:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;doIt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;  
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;would be slightly slower than:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;doIt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;  
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Even if this were true, the loop body would dominate the cost of control flow in most cases.
It’s worth looking at the origin of the myth and the reasoning behind it though.
The myth comes from C++, where it’s possible to overload increment operators.
As you are probably aware, post-increment yields the value before performing the increment.
This may lead to copying the value, so unless the compiler eliminates the copy because it’s not observable, pre-increment would have an advantage.
I’m not going to dig in to how often or when this is true in C++ because it has no relevance to programs written in Java.&lt;/p&gt;

&lt;p&gt;Since the JIT compiler optimises loops, it’s very unlikely that it will generate code corresponding to either  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i++&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;++i&lt;/code&gt;.
This can be seen by analysing a simple benchmark:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Increments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1024&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;autovecPre&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;autovecPost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reducePre&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reducePost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;blackholedPre&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;blackholedPost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The benchmark consists of a pre- and post-increment version of three loops selected carefully.
Loops &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autovecPre&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autovecPost&lt;/code&gt; can be autovectorised. 
Loops &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reducePre&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reducePost&lt;/code&gt; can’t be autovectorised (because the operation in the loop body can’t, but fortunately this will no longer be the case on some hardware, see &lt;a href=&quot;https://github.com/openjdk/jdk/pull/6857&quot;&gt;JDK-8278868&lt;/a&gt;) but can be unrolled.
Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Blackhold::consume&lt;/code&gt; prevents loop unrolling, so different code will be generated in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blackholedPre&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blackholedPost&lt;/code&gt; than in the other loops.&lt;/p&gt;

&lt;p&gt;Firstly, there is no difference in the scores for these benchmarks (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pre&lt;/code&gt; is &lt;em&gt;slower&lt;/em&gt; once but it’s just noise):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Benchmark                  (size)  Mode  Cnt     Score   Error  Units
Increments.autovecPost       1024  avgt    5   156.744 ± 0.416  ns/op
Increments.autovecPre        1024  avgt    5   158.390 ± 0.096  ns/op
Increments.blackholedPost    1024  avgt    5  2798.470 ± 2.657  ns/op
Increments.blackholedPre     1024  avgt    5  2798.259 ± 2.553  ns/op
Increments.reducePost        1024  avgt    5   456.012 ± 0.267  ns/op
Increments.reducePre         1024  avgt    5   455.922 ± 0.299  ns/op
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is because the loops are JIT-compiled exactly the same way.&lt;/p&gt;

&lt;p&gt;Here’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autovecPre&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autovecPost&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; is incremented by 64 at a time: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add $0x40,%edx&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;....[Hottest Region 1]..............................................................................
c2, level 4, inc.generated.Increments_autovecPost_jmhTest::autovecPost_avgt_jmhStub, version 472 (464 bytes) 

                       0x00007fd6f435aff4: vzeroupper 
                       0x00007fd6f435aff7: add    $0x30,%rsp
                       0x00007fd6f435affb: pop    %rbp
                       0x00007fd6f435affc: mov    0x108(%r15),%r10
                       0x00007fd6f435b003: test   %eax,(%r10)        
                       0x00007fd6f435b006: retq   
                       0x00007fd6f435b007: nop                       
                                                                     
                                                                     
         ↗         ↗   0x00007fd6f435b008: vmovdqu 0x10(%r8,%rdx,4),%ymm0
  0.73%  │         │   0x00007fd6f435b00f: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0
  0.75%  │         │   0x00007fd6f435b016: vmovdqu %ymm0,0x10(%r8,%rdx,4)
         │         │                                                 
         │         │                                                 
         │         │   0x00007fd6f435b01d: add    $0x8,%edx          
         │         │                                                 
         │         │                                                 
  0.66%  │         │   0x00007fd6f435b020: cmp    %r9d,%edx
         ╰         │   0x00007fd6f435b023: jl     0x00007fd6f435b008 
                   │                                                 
                   │                                                 
                 ↗ │↗  0x00007fd6f435b025: cmp    %r11d,%edx
          ╭      │ ││  0x00007fd6f435b028: jge    0x00007fd6f435b03d
  0.02%   │      │ ││  0x00007fd6f435b02a: xchg   %ax,%ax            
          │      │ ││                                                
          │      │ ││                                                
          │↗     │ ││  0x00007fd6f435b02c: mov    0x10(%r10,%rdx,4),%ebx
  0.54%   ││     │ ││  0x00007fd6f435b031: add    %ebx,0x10(%r8,%rdx,4)
          ││     │ ││                                                
          ││     │ ││                                                
  0.58%   ││     │ ││  0x00007fd6f435b036: inc    %edx               
          ││     │ ││                                                
          ││     │ ││                                                
  0.54%   ││     │ ││  0x00007fd6f435b038: cmp    %r11d,%edx
          │╰     │ ││  0x00007fd6f435b03b: jl     0x00007fd6f435b02c 
          │      │ ││                                                
          │      │ ││                                                
          ↘ ↗    │ ││  0x00007fd6f435b03d: mov    %rcx,%rdx
  0.23%     │    │ ││  0x00007fd6f435b040: shl    $0x3,%rdx          
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007fd6f435b044: mov    0x10(%rsp),%rsi
  0.10%     │    │ ││  0x00007fd6f435b049: data16 xchg %ax,%ax
            │    │ ││  0x00007fd6f435b04c: vzeroupper 
  0.29%     │    │ ││  0x00007fd6f435b04f: callq  0x00007fd6ec89cb00 
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007fd6f435b054: mov    0x40(%rsp),%r10
  0.17%     │    │ ││  0x00007fd6f435b059: movzbl 0x94(%r10),%r10d   
            │    │ ││                                                
            │    │ ││                                                
  0.10%     │    │ ││  0x00007fd6f435b061: mov    0x108(%r15),%r11
            │    │ ││  0x00007fd6f435b068: add    $0x1,%rbp          
            │    │ ││                                                
            │    │ ││                                                
  0.08%     │    │ ││  0x00007fd6f435b06c: test   %eax,(%r11)        
            │    │ ││  0x00007fd6f435b06f: test   %r10d,%r10d
            │    │ ││  0x00007fd6f435b072: jne    0x00007fd6f435afcf 
            │    │ ││                                                
  0.02%     │    │ ││  0x00007fd6f435b078: mov    0x50(%rsp),%r10
            │    │ ││  0x00007fd6f435b07d: mov    0x10(%r10),%r10d   
            │    │ ││                                                
            │    │ ││                                                
  0.12%     │    │ ││  0x00007fd6f435b081: mov    0xc(%r12,%r10,8),%r11d  
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007fd6f435b086: mov    0x50(%rsp),%r8
  0.06%     │    │ ││  0x00007fd6f435b08b: mov    0x14(%r8),%ecx     
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007fd6f435b08f: test   %r11d,%r11d
            ╰    │ ││  0x00007fd6f435b092: jbe    0x00007fd6f435b03d  
                 │ ││                                                
                 │ ││                                                
  0.23%          │ ││  0x00007fd6f435b094: mov    0xc(%r12,%rcx,8),%r8d  
                 │ ││                                                
                 │ ││                                                
                 │ ││                                                
                 │ ││  0x00007fd6f435b099: test   %r8d,%r8d
             ╭   │ ││  0x00007fd6f435b09c: jbe    0x00007fd6f435b1ed
  0.08%      │   │ ││  0x00007fd6f435b0a2: mov    %r11d,%r9d
             │   │ ││  0x00007fd6f435b0a5: dec    %r9d
  0.17%      │   │ ││  0x00007fd6f435b0a8: cmp    %r8d,%r9d
             │╭  │ ││  0x00007fd6f435b0ab: jae    0x00007fd6f435b1ed
             ││  │ ││  0x00007fd6f435b0b1: cmp    %r11d,%r9d
             ││╭ │ ││  0x00007fd6f435b0b4: jae    0x00007fd6f435b1ed
  0.08%      │││ │ ││  0x00007fd6f435b0ba: shl    $0x3,%r10
             │││ │ ││  0x00007fd6f435b0be: lea    (%r12,%rcx,8),%r8
  0.19%      │││ │ ││  0x00007fd6f435b0c2: mov    %r8d,%r9d
             │││ │ ││  0x00007fd6f435b0c5: shr    $0x2,%r9d
  0.06%      │││ │ ││  0x00007fd6f435b0c9: and    $0x7,%r9d
             │││ │ ││  0x00007fd6f435b0cd: mov    $0x3,%ebx
  0.19%      │││ │ ││  0x00007fd6f435b0d2: sub    %r9d,%ebx
             │││ │ ││  0x00007fd6f435b0d5: and    $0x7,%ebx
  0.06%      │││ │ ││  0x00007fd6f435b0d8: inc    %ebx
             │││ │ ││  0x00007fd6f435b0da: cmp    %r11d,%ebx
  0.08%      │││ │ ││  0x00007fd6f435b0dd: cmovg  %r11d,%ebx
             │││ │ ││  0x00007fd6f435b0e1: xor    %edx,%edx          
             │││ │ ││                                                
             │││ │ ││                                                
  0.48%      │││↗│ ││  0x00007fd6f435b0e3: mov    0x10(%r10,%rdx,4),%edi
             │││││ ││  0x00007fd6f435b0e8: add    %edi,0x10(%r8,%rdx,4)  
             │││││ ││                                                
             │││││ ││                                                
  0.95%      │││││ ││  0x00007fd6f435b0ed: inc    %edx               
             │││││ ││                                                
             │││││ ││                                                
             │││││ ││  0x00007fd6f435b0ef: cmp    %ebx,%edx
             │││╰│ ││  0x00007fd6f435b0f1: jl     0x00007fd6f435b0e3  
             │││ │ ││                                                
             │││ │ ││                                                
  0.02%      │││ │ ││  0x00007fd6f435b0f3: mov    %r11d,%ebx
             │││ │ ││  0x00007fd6f435b0f6: add    $0xffffffc1,%ebx
  0.21%      │││ │ ││  0x00007fd6f435b0f9: cmp    %ebx,%edx
             │││ ╰ ││  0x00007fd6f435b0fb: jge    0x00007fd6f435b025  
             │││   ││                                                
             │││   ││                                                
  2.90%      │││  ↗││  0x00007fd6f435b101: vmovdqu 0x10(%r8,%rdx,4),%ymm0
  0.04%      │││  │││  0x00007fd6f435b108: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0
 10.08%      │││  │││  0x00007fd6f435b10f: vmovdqu %ymm0,0x10(%r8,%rdx,4)
  1.00%      │││  │││  0x00007fd6f435b116: vmovdqu 0x30(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b11d: vpaddd 0x30(%r10,%rdx,4),%ymm0,%ymm0
  9.27%      │││  │││  0x00007fd6f435b124: vmovdqu %ymm0,0x30(%r8,%rdx,4)
  0.79%      │││  │││  0x00007fd6f435b12b: vmovdqu 0x50(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b132: vpaddd 0x50(%r10,%rdx,4),%ymm0,%ymm0
  8.07%      │││  │││  0x00007fd6f435b139: vmovdqu %ymm0,0x50(%r8,%rdx,4)
  0.89%      │││  │││  0x00007fd6f435b140: vmovdqu 0x70(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b147: vpaddd 0x70(%r10,%rdx,4),%ymm0,%ymm0
  9.58%      │││  │││  0x00007fd6f435b14e: vmovdqu %ymm0,0x70(%r8,%rdx,4)
  0.52%      │││  │││  0x00007fd6f435b155: vmovdqu 0x90(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b15f: vpaddd 0x90(%r10,%rdx,4),%ymm0,%ymm0
  8.34%      │││  │││  0x00007fd6f435b169: vmovdqu %ymm0,0x90(%r8,%rdx,4)
  0.64%      │││  │││  0x00007fd6f435b173: vmovdqu 0xb0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b17d: vpaddd 0xb0(%r10,%rdx,4),%ymm0,%ymm0
  9.71%      │││  │││  0x00007fd6f435b187: vmovdqu %ymm0,0xb0(%r8,%rdx,4)
  0.58%      │││  │││  0x00007fd6f435b191: vmovdqu 0xd0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b19b: vpaddd 0xd0(%r10,%rdx,4),%ymm0,%ymm0
 14.68%      │││  │││  0x00007fd6f435b1a5: vmovdqu %ymm0,0xd0(%r8,%rdx,4)
  0.54%      │││  │││  0x00007fd6f435b1af: vmovdqu 0xf0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007fd6f435b1b9: vpaddd 0xf0(%r10,%rdx,4),%ymm0,%ymm0
  9.46%      │││  │││  0x00007fd6f435b1c3: vmovdqu %ymm0,0xf0(%r8,%rdx,4)  
             │││  │││                                                
             │││  │││                                                
  0.52%      │││  │││  0x00007fd6f435b1cd: add    $0x40,%edx         
             │││  │││                                                
             │││  │││                                                
             │││  │││  0x00007fd6f435b1d0: cmp    %ebx,%edx
             │││  ╰││  0x00007fd6f435b1d2: jl     0x00007fd6f435b101
  0.23%      │││   ││  0x00007fd6f435b1d8: mov    %r11d,%r9d
             │││   ││  0x00007fd6f435b1db: add    $0xfffffff9,%r9d
  0.10%      │││   ││  0x00007fd6f435b1df: cmp    %r9d,%edx
             │││   ╰│  0x00007fd6f435b1e2: jl     0x00007fd6f435b008
             │││    ╰  0x00007fd6f435b1e8: jmpq   0x00007fd6f435b025 
             │││                                                     
             │││                                                     
             ↘↘↘       0x00007fd6f435b1ed: mov    $0xffffff7e,%esi
                       0x00007fd6f435b1f2: mov    %r11d,0x18(%rsp)
                       0x00007fd6f435b1f7: nop
                       0x00007fd6f435b1f8: vzeroupper 
                       0x00007fd6f435b1fb: callq  0x00007fd6ec89d280  
                                                                     
....................................................................................................
 95.73%  &amp;lt;total for region 1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;....[Hottest Region 1]..............................................................................
c2, level 4, inc.generated.Increments_autovecPre_jmhTest::autovecPre_avgt_jmhStub, version 460 (464 bytes) 
             
                       0x00007f44cc35d2f4: vzeroupper 
                       0x00007f44cc35d2f7: add    $0x30,%rsp
                       0x00007f44cc35d2fb: pop    %rbp
                       0x00007f44cc35d2fc: mov    0x108(%r15),%r10
                       0x00007f44cc35d303: test   %eax,(%r10)        
                       0x00007f44cc35d306: retq   
                       0x00007f44cc35d307: nop                       
                                                                     
                                                                     
  0.77%  ↗         ↗   0x00007f44cc35d308: vmovdqu 0x10(%r8,%rdx,4),%ymm0
  0.25%  │         │   0x00007f44cc35d30f: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0
  3.27%  │         │   0x00007f44cc35d316: vmovdqu %ymm0,0x10(%r8,%rdx,4)
         │         │                                                 
         │         │                                                 
  0.29%  │         │   0x00007f44cc35d31d: add    $0x8,%edx          
         │         │                                                 
         │         │                                                 
  0.10%  │         │   0x00007f44cc35d320: cmp    %r9d,%edx
         ╰         │   0x00007f44cc35d323: jl     0x00007f44cc35d308  
                   │                                                 
                   │                                                 
  0.19%          ↗ │↗  0x00007f44cc35d325: cmp    %r11d,%edx
          ╭      │ ││  0x00007f44cc35d328: jge    0x00007f44cc35d33d
          │      │ ││  0x00007f44cc35d32a: xchg   %ax,%ax            
          │      │ ││                                                
          │      │ ││                                                
  0.50%   │↗     │ ││  0x00007f44cc35d32c: mov    0x10(%r10,%rdx,4),%ebx
          ││     │ ││  0x00007f44cc35d331: add    %ebx,0x10(%r8,%rdx,4)
          ││     │ ││                                                
          ││     │ ││                                                
  1.06%   ││     │ ││  0x00007f44cc35d336: inc    %edx               
          ││     │ ││                                                
          ││     │ ││                                                
          ││     │ ││  0x00007f44cc35d338: cmp    %r11d,%edx
          │╰     │ ││  0x00007f44cc35d33b: jl     0x00007f44cc35d32c 
          │      │ ││                                                
          │      │ ││                                                
  0.10%   ↘ ↗    │ ││  0x00007f44cc35d33d: mov    %rcx,%rdx
            │    │ ││  0x00007f44cc35d340: shl    $0x3,%rdx          
            │    │ ││                                                
            │    │ ││                                                
  0.17%     │    │ ││  0x00007f44cc35d344: mov    0x10(%rsp),%rsi
            │    │ ││  0x00007f44cc35d349: data16 xchg %ax,%ax
  0.10%     │    │ ││  0x00007f44cc35d34c: vzeroupper 
  0.10%     │    │ ││  0x00007f44cc35d34f: callq  0x00007f44c489cb00 
            │    │ ││                                                
            │    │ ││                                               
            │    │ ││                                               
            │    │ ││                                               
  0.02%     │    │ ││  0x00007f44cc35d354: mov    0x40(%rsp),%r10
  0.08%     │    │ ││  0x00007f44cc35d359: movzbl 0x94(%r10),%r10d   
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007f44cc35d361: mov    0x108(%r15),%r11
  0.04%     │    │ ││  0x00007f44cc35d368: add    $0x1,%rbp          
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007f44cc35d36c: test   %eax,(%r11)        
  0.25%     │    │ ││  0x00007f44cc35d36f: test   %r10d,%r10d
            │    │ ││  0x00007f44cc35d372: jne    0x00007f44cc35d2cf 
            │    │ ││                                                
            │    │ ││  0x00007f44cc35d378: mov    0x50(%rsp),%r10
  0.04%     │    │ ││  0x00007f44cc35d37d: mov    0x10(%r10),%r10d   
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││  0x00007f44cc35d381: mov    0xc(%r12,%r10,8),%r11d
            │    │ ││                                                
            │    │ ││                                                
            │    │ ││                                                
  0.15%     │    │ ││  0x00007f44cc35d386: mov    0x50(%rsp),%r8
            │    │ ││  0x00007f44cc35d38b: mov    0x14(%r8),%ecx     
            │    │ ││                                                
            │    │ ││                                                
  0.04%     │    │ ││  0x00007f44cc35d38f: test   %r11d,%r11d
            ╰    │ ││  0x00007f44cc35d392: jbe    0x00007f44cc35d33d 
                 │ ││                                                
                 │ ││                                                
  0.06%          │ ││  0x00007f44cc35d394: mov    0xc(%r12,%rcx,8),%r8d
                 │ ││                                                
                 │ ││                                                
                 │ ││                                                
  0.27%          │ ││  0x00007f44cc35d399: test   %r8d,%r8d
             ╭   │ ││  0x00007f44cc35d39c: jbe    0x00007f44cc35d4ed
  0.15%      │   │ ││  0x00007f44cc35d3a2: mov    %r11d,%r9d
             │   │ ││  0x00007f44cc35d3a5: dec    %r9d
  0.17%      │   │ ││  0x00007f44cc35d3a8: cmp    %r8d,%r9d
             │╭  │ ││  0x00007f44cc35d3ab: jae    0x00007f44cc35d4ed
  0.08%      ││  │ ││  0x00007f44cc35d3b1: cmp    %r11d,%r9d
             ││╭ │ ││  0x00007f44cc35d3b4: jae    0x00007f44cc35d4ed
  0.19%      │││ │ ││  0x00007f44cc35d3ba: shl    $0x3,%r10
             │││ │ ││  0x00007f44cc35d3be: lea    (%r12,%rcx,8),%r8
             │││ │ ││  0x00007f44cc35d3c2: mov    %r8d,%r9d
  0.12%      │││ │ ││  0x00007f44cc35d3c5: shr    $0x2,%r9d
  0.04%      │││ │ ││  0x00007f44cc35d3c9: and    $0x7,%r9d
             │││ │ ││  0x00007f44cc35d3cd: mov    $0x3,%ebx
             │││ │ ││  0x00007f44cc35d3d2: sub    %r9d,%ebx
  0.10%      │││ │ ││  0x00007f44cc35d3d5: and    $0x7,%ebx
  0.17%      │││ │ ││  0x00007f44cc35d3d8: inc    %ebx
             │││ │ ││  0x00007f44cc35d3da: cmp    %r11d,%ebx
             │││ │ ││  0x00007f44cc35d3dd: cmovg  %r11d,%ebx
  0.06%      │││ │ ││  0x00007f44cc35d3e1: xor    %edx,%edx          
             │││ │ ││                                                
             │││ │ ││                                                
  0.19%      │││↗│ ││  0x00007f44cc35d3e3: mov    0x10(%r10,%rdx,4),%edi
  0.21%      │││││ ││  0x00007f44cc35d3e8: add    %edi,0x10(%r8,%rdx,4)
             │││││ ││                                                
             │││││ ││                                                
  0.50%      │││││ ││  0x00007f44cc35d3ed: inc    %edx               
             │││││ ││                                                
             │││││ ││                                                
  0.21%      │││││ ││  0x00007f44cc35d3ef: cmp    %ebx,%edx
             │││╰│ ││  0x00007f44cc35d3f1: jl     0x00007f44cc35d3e3 
             │││ │ ││                                                
             │││ │ ││                                                
  0.08%      │││ │ ││  0x00007f44cc35d3f3: mov    %r11d,%ebx
             │││ │ ││  0x00007f44cc35d3f6: add    $0xffffffc1,%ebx
             │││ │ ││  0x00007f44cc35d3f9: cmp    %ebx,%edx
             │││ ╰ ││  0x00007f44cc35d3fb: jge    0x00007f44cc35d325  
             │││   ││                                                
             │││   ││                                                
  3.25%      │││  ↗││  0x00007f44cc35d401: vmovdqu 0x10(%r8,%rdx,4),%ymm0
  0.42%      │││  │││  0x00007f44cc35d408: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0
  9.16%      │││  │││  0x00007f44cc35d40f: vmovdqu %ymm0,0x10(%r8,%rdx,4)
  1.29%      │││  │││  0x00007f44cc35d416: vmovdqu 0x30(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d41d: vpaddd 0x30(%r10,%rdx,4),%ymm0,%ymm0
  8.47%      │││  │││  0x00007f44cc35d424: vmovdqu %ymm0,0x30(%r8,%rdx,4)
  0.60%      │││  │││  0x00007f44cc35d42b: vmovdqu 0x50(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d432: vpaddd 0x50(%r10,%rdx,4),%ymm0,%ymm0
 13.43%      │││  │││  0x00007f44cc35d439: vmovdqu %ymm0,0x50(%r8,%rdx,4)
  1.04%      │││  │││  0x00007f44cc35d440: vmovdqu 0x70(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d447: vpaddd 0x70(%r10,%rdx,4),%ymm0,%ymm0
  7.91%      │││  │││  0x00007f44cc35d44e: vmovdqu %ymm0,0x70(%r8,%rdx,4)
  0.58%      │││  │││  0x00007f44cc35d455: vmovdqu 0x90(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d45f: vpaddd 0x90(%r10,%rdx,4),%ymm0,%ymm0
  9.66%      │││  │││  0x00007f44cc35d469: vmovdqu %ymm0,0x90(%r8,%rdx,4)
  0.58%      │││  │││  0x00007f44cc35d473: vmovdqu 0xb0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d47d: vpaddd 0xb0(%r10,%rdx,4),%ymm0,%ymm0
  9.14%      │││  │││  0x00007f44cc35d487: vmovdqu %ymm0,0xb0(%r8,%rdx,4)
  0.50%      │││  │││  0x00007f44cc35d491: vmovdqu 0xd0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d49b: vpaddd 0xd0(%r10,%rdx,4),%ymm0,%ymm0
  9.08%      │││  │││  0x00007f44cc35d4a5: vmovdqu %ymm0,0xd0(%r8,%rdx,4)
  0.83%      │││  │││  0x00007f44cc35d4af: vmovdqu 0xf0(%r8,%rdx,4),%ymm0
             │││  │││  0x00007f44cc35d4b9: vpaddd 0xf0(%r10,%rdx,4),%ymm0,%ymm0
  8.52%      │││  │││  0x00007f44cc35d4c3: vmovdqu %ymm0,0xf0(%r8,%rdx,4)
             │││  │││                                                
             │││  │││                                                
  0.37%      │││  │││  0x00007f44cc35d4cd: add    $0x40,%edx         
             │││  │││                                                
             │││  │││                                                
             │││  │││  0x00007f44cc35d4d0: cmp    %ebx,%edx
             │││  ╰││  0x00007f44cc35d4d2: jl     0x00007f44cc35d401
  0.15%      │││   ││  0x00007f44cc35d4d8: mov    %r11d,%r9d
             │││   ││  0x00007f44cc35d4db: add    $0xfffffff9,%r9d
             │││   ││  0x00007f44cc35d4df: cmp    %r9d,%edx
             │││   ╰│  0x00007f44cc35d4e2: jl     0x00007f44cc35d308
             │││    ╰  0x00007f44cc35d4e8: jmpq   0x00007f44cc35d325 
             │││                                                     
             │││                                                     
             ↘↘↘       0x00007f44cc35d4ed: mov    $0xffffff7e,%esi
                       0x00007f44cc35d4f2: mov    %r11d,0x18(%rsp)
                       0x00007f44cc35d4f7: nop
                       0x00007f44cc35d4f8: vzeroupper 
....................................................................................................
 95.13%  &amp;lt;total for region 1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I haven’t included the perfasm output for the other loops, but the compiled code is also identical.
In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reducePre&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reducePost&lt;/code&gt; the loop is unrolled so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; is incremented 8 at a time (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add $0x8,%esi&lt;/code&gt;).
In the loops contrived not to be unrolled, the increments are performed one at a time using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inc %ebp&lt;/code&gt;, but the code is still identical.&lt;/p&gt;

&lt;p&gt;Even though this is a myth, does it actually cause any harm? 
Of course not, loops optimised under this delusion are compiled the same way. 
The only negative scenario would be if a misguided team member were to insist on this practice being followed.&lt;/p&gt;

&lt;h3 id=&quot;true-once-prefer-stringbuilder-to-string-concatenation&quot;&gt;True once: prefer StringBuilder to string concatenation&lt;/h3&gt;

&lt;p&gt;A very long time ago, in JDK1.4, when concatenating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;s, a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuffer&lt;/code&gt; was used.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StringExample&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;concatenate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;concatenateStringBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;StringBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The top method above would use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuffer&lt;/code&gt;, which has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;synchronized&lt;/code&gt; methods:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public static java.lang.String concatenate(java.lang.String, java.lang.String);
    Code:
       0: new           #2                  // class java/lang/StringBuffer
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuffer.&quot;&amp;lt;init&amp;gt;&quot;:()V
       7: aload_0
       8: invokevirtual #4                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
      11: aload_1
      12: invokevirtual #4                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
      15: invokevirtual #5                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
      18: areturn

  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);
    Code:
       0: new           #6                  // class java/lang/StringBuilder
       3: dup
       4: aload_0
       5: invokevirtual #7                  // Method java/lang/String.length:()I
       8: aload_1
       9: invokevirtual #7                  // Method java/lang/String.length:()I
      12: iadd
      13: invokespecial #8                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:(I)V
      16: aload_0
      17: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: aload_1
      21: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This meant that using a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt; at language level 1.4 would be beneficial.
If you learnt Java when 1.4 was commonly used, you might think this is an essential optimisation.&lt;/p&gt;

&lt;p&gt;Later, the generated bytecode was improved, so that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt; would be used instead.
In JDK1.8 (and earlier) the bytecode looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public static java.lang.String concatenate(java.lang.String, java.lang.String);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #3                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:()V
       7: aload_0
       8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      11: aload_1
      12: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      18: areturn

  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);
    Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: aload_0
       5: invokevirtual #6                  // Method java/lang/String.length:()I
       8: aload_1
       9: invokevirtual #6                  // Method java/lang/String.length:()I
      12: iadd
      13: invokespecial #7                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:(I)V
      16: aload_0
      17: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: aload_1
      21: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Maybe there’s a tiny benefit in sizing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt;, but now the code is harder to read for marginal gain.
In JDK9, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; concatenation was reworked, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt; code is at a disadvantage to the more readable concatenation code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  public static java.lang.String concatenate(java.lang.String, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #2,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
       7: areturn

  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);
    Code:
       0: new           #3                  // class java/lang/StringBuilder
       3: dup
       4: aload_0
       5: invokevirtual #4                  // Method java/lang/String.length:()I
       8: aload_1
       9: invokevirtual #4                  // Method java/lang/String.length:()I
      12: iadd
      13: invokespecial #5                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:(I)V
      16: aload_0
      17: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: aload_1
      21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invokedynamic&lt;/code&gt; bytecode instruction, which can be used to drive better optimisations, is used instead of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt;.
The confusing thing is this only applies when the number of strings is known, so not in loops, but in most cases it’s now better just to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt;.
I won’t bother to measure this progress because the people who made these changes to javac did that at the time, but the point is that even the bytecode can change in such a way to render old optimisations neutral or negative.&lt;/p&gt;

&lt;p&gt;In my last post, I tried to avoid putting forward items which might become untrue in the future, but didn’t quite manage this.
I predict that four of the five items will stand the test of time, but item three (avoid iterating over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt;) may eventually become a myth if &lt;a href=&quot;https://openjdk.java.net/jeps/8261007&quot;&gt;frozen arrays&lt;/a&gt; become a reality in the future.&lt;/p&gt;

&lt;h3 id=&quot;continuous-profiling&quot;&gt;Continuous profiling&lt;/h3&gt;

&lt;p&gt;What these myths have in common is they focus on low level technical trivia rather than fundamental algorithmic or engineering constraints, and trivial details are as likely to be misunderstood as they are to change.
There are more important factors to system efficiency than low-level programming details.
For instance, being required by specification to do something wasteful by an external system is a problem which should be solved architecturally.
If architectural blunders create bottlenecks, it’s very costly to fix.&lt;/p&gt;

&lt;p&gt;Assuming a sensible architecture, the causes of many costs are neither dictated by nor are observable to external systems, so can be changed easily. 
Efficiency at this level depends on choices of algorithm, data structure, and level of mechanical sympathy.
These kinds of problems can be surfaced by using continuous profiling, and are often surprising.
Continuous profiling, by its nature, tends to highlight what’s important to reducing overall cost, and filters out distracting and obvious fixed costs in favour of unexpected unit costs.
My impression is that many engineers believe profiling to be too expensive to run in production, but I think these engineers’ minds would be blown by how much CPU time is spent collecting logs, metrics, and traces if they tried it.
The reality is that it costs about 3% CPU utilisation and will lead to double-digit savings which translate to significantly reduced running costs for all but the leanest applications.&lt;/p&gt;

&lt;p&gt;I think a diagram, drawn by Aleksey Shipilёv several years ago, explains where continuous profiling is useful:&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I relax by drawing stuff on my large whiteboard. Here&amp;#39;s &amp;quot;Perf Work Phase Diagram&amp;quot; for you. &lt;a href=&quot;http://t.co/RnITiLoNFH&quot;&gt;pic.twitter.com/RnITiLoNFH&lt;/a&gt;&lt;/p&gt;&amp;mdash; Aleksey Shipilëv (@shipilev) &lt;a href=&quot;https://twitter.com/shipilev/status/578193813946134529?ref_src=twsrc%5Etfw&quot;&gt;March 18, 2015&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Essentially, continuous profiling will help you get from an &lt;em&gt;Amazingly stupid program&lt;/em&gt; to &lt;em&gt;Beauty Peak&lt;/em&gt; by making it obvious where the costs are; the majority of programs needn’t go beyond &lt;em&gt;Beauty Peak&lt;/em&gt;.
This process doesn’t consist entirely of making the kinds of mundane point substitutions in my last post.
It’s often necessary to take a step back and ask why something costly is happening at all, and rework the algorithm, rather than think about cost reduction.&lt;/p&gt;

&lt;p&gt;Despite all this, once you have data about what your program spends its time doing, many of the culprits will be mundane unless you have a very interesting program.
When there’s no increase in complexity incurred in using a more efficient idiom, there’s no need to wait for its necessity to be proven by a profiler.
In any case, continuous profiling should help calibrate your sense of which idioms are costly, and avoid the myths.&lt;/p&gt;</content><author><name></name></author><category term="java" /><summary type="html">My last post was about five very simple things you can do to avoid Java programs from being slower than they need to be. The reception to this post was mixed. Some readers agreed that the problems mentioned in the post were indeed very common, while others suggested more common inefficient patterns to avoid. For example, I could have suggested to precompile regular expressions, or not to program by exception, or to avoid String.format, but I felt this was all well covered already. The aim of the post was to help Java programmers to program defensively for efficiency - just as many do for correctness - by giving an idea of what some common things cost. However, several readers dismissed the content of the post as premature optimisation.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2021/12/perf-myths-and-continuous-profiling/Ardre_Odin_Sleipnir.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2021/12/perf-myths-and-continuous-profiling/Ardre_Odin_Sleipnir.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">5 Mundane Java Performance Tips</title><link href="https://richardstartin.github.io/posts/5-java-mundane-performance-tricks" rel="alternate" type="text/html" title="5 Mundane Java Performance Tips" /><published>2021-11-28T00:00:00+00:00</published><updated>2021-11-28T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/5-java-mundane-performance-tricks</id><content type="html" xml:base="https://richardstartin.github.io/posts/5-java-mundane-performance-tricks">&lt;p&gt;Most of the time it isn’t really necessary to &lt;em&gt;optimise&lt;/em&gt; software, but this post contains 5 tips to avoid making software written in Java slower for the sake of it.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;How to read this post:&lt;/strong&gt; it didn’t take long for the term &lt;em&gt;premature optimisation&lt;/em&gt; to arise after posting this on the Internet. Apart from item 5 (you are burning money if you are still using JDK8), none of this advice is important enough to contort your code to take advantage of. The particular block of code may never get called much, which is where the mantra to fix performance problems once they have been identified comes from. However, for one reason or another, I have looked at probably thousands of profiles of Java code I didn’t write during my career, and all of these things have shown up in profiles several times. That is, these things &lt;strong&gt;do&lt;/strong&gt; cause excessive resource consumption in real applications and libraries. Sometimes, when there is a bottleneck, it indicates a design problem, and preventing the bottleneck from being called so often is usually more effective than speeding up the block of code at the bottleneck. On the other hand, you don’t &lt;strong&gt;need&lt;/strong&gt; to be resizing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt;s at least once per request if you have the information to size them properly in hand. If you can, size the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt; properly, and it will never become a bottleneck showing up in other people’s profiles down the line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#size-hashmaps-whenever-possible&quot; id=&quot;markdown-toc-size-hashmaps-whenever-possible&quot;&gt;Size HashMaps whenever possible&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#use-wrappers-for-composite-hashmap-keys&quot; id=&quot;markdown-toc-use-wrappers-for-composite-hashmap-keys&quot;&gt;Use wrappers for composite HashMap keys&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#dont-iterate-over-enumvalues&quot; id=&quot;markdown-toc-dont-iterate-over-enumvalues&quot;&gt;Don’t iterate over Enum.values()&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#use-enums-instead-of-constant-strings&quot; id=&quot;markdown-toc-use-enums-instead-of-constant-strings&quot;&gt;Use enums instead of constant Strings&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#stop-using-jdk8&quot; id=&quot;markdown-toc-stop-using-jdk8&quot;&gt;Stop using JDK8&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;size-hashmaps-whenever-possible&quot;&gt;Size HashMaps whenever possible&lt;/h3&gt;

&lt;p&gt;Even if most of its operations are quite fast, resizing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt;s is slow and hard to optimise, so the size should be calculated before building the map.
The size parameter does not take the load factor in to account, so the number of elements needs to be divided by the load factor, which is 0.75 by default: multiplying by 4/3 is usually enough.&lt;/p&gt;

&lt;p&gt;There are four scenarios in the following benchmark: 10 and 14 keys are inserted into maps with default capacity (16) and maps with a capacity of 24 respectively.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMapResize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  
    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;14&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;16&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;24&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadHashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The maps have the default load factor of 0.75, so up to 12 keys can be inserted into a map with capacity 16 before a resize.
To insert 14 keys without a resize would require an initial capacity of at least 19, but capacities are rounded up to the next power of 2, so 14 keys would end up in a map with a table of capacity 32.
Since the initial capacity is rounded up to the next power of 2, it just so happens that when initial capacity 24 is requested, 24 keys would actually fit without a resize.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: capacity&lt;/th&gt;
        &lt;th&gt;Param: keys&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;191.729672&lt;/td&gt;
        &lt;td&gt;9.744065&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;448.000079&lt;/td&gt;
        &lt;td&gt;0.000010&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;330.267466&lt;/td&gt;
        &lt;td&gt;14.739395&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;14&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;720.000139&lt;/td&gt;
        &lt;td&gt;0.000025&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;16&lt;/td&gt;
        &lt;td&gt;14&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;221.798264&lt;/td&gt;
        &lt;td&gt;97.696476&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;24&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;512.000092&lt;/td&gt;
        &lt;td&gt;0.000038&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;24&lt;/td&gt;
        &lt;td&gt;10&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;292.934060&lt;/td&gt;
        &lt;td&gt;13.489139&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;24&lt;/td&gt;
        &lt;td&gt;14&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HashMapResize.loadHashMap:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;640.000121&lt;/td&gt;
        &lt;td&gt;0.000016&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;24&lt;/td&gt;
        &lt;td&gt;14&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;It takes 70% longer to put 14 keys into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt; with capacity 16 than it does to insert 10 keys, and allocation rate per constructed map is 60% higher.
Increasing the capacity to 24 saves 11% in allocation rate and 34% in build time per &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt; by avoiding a resize in this benchmark.
Exact numbers will vary but the point is that, if the size of the map can be calculated easily, doing so will save time and reduce allocation rate.&lt;/p&gt;

&lt;h3 id=&quot;use-wrappers-for-composite-hashmap-keys&quot;&gt;Use wrappers for composite HashMap keys&lt;/h3&gt;

&lt;p&gt;Whenever a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt; has composite &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; keys, use a wrapper instead of concatenating the strings to make a key.
Doing so will make the lookup much faster and reduce allocation rate, as the benchmark below demonstrates.
Two maps, one with concatenated keys and the other with keys in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pair&lt;/code&gt; object are constructed so lookup time can be compared.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompositeLookup&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1024&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;concatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pairMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;concatMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;pairMap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;concatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// use new String to avoid reference equality speeding up the equals calls &lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;pairMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@OperationsPerInvocation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;concatenate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;concatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]));&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@OperationsPerInvocation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pairMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Pair&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prefixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;suffixes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;])));&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;CompositeLookup.concatenate&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;158.160452&lt;/td&gt;
        &lt;td&gt;5.622590&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;CompositeLookup.concatenate:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;120.000066&lt;/td&gt;
        &lt;td&gt;0.000010&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;CompositeLookup.wrap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;43.550144&lt;/td&gt;
        &lt;td&gt;0.932947&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;CompositeLookup.wrap:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;24.000018&lt;/td&gt;
        &lt;td&gt;0.000003&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Concatenating the keys takes 3.7x longer per lookup and allocates five times more (this depends on the size of the keys though).
The reason wrapping outperforms concatenation is that a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; instance caches its hash code, and constructing a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; requires calculation of a new hash code and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;’s hash code algorithm also isn’t very efficient. 
The difference is actually huge, and I have seen this make a big difference in real applications many times - this idiom should be more common.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/VladimirSitnikv&quot;&gt;Vladimir Sitnikov&lt;/a&gt; &lt;a href=&quot;https://github.com/spring-projects/spring-framework/pull/913&quot;&gt;points out&lt;/a&gt; that this pattern turned up as a bottleneck in Spring framework.&lt;/p&gt;

&lt;h3 id=&quot;dont-iterate-over-enumvalues&quot;&gt;Don’t iterate over Enum.values()&lt;/h3&gt;

&lt;p&gt;An array is allocated every time &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt; is called, which can really add up.
If you own the code, and the class won’t be visible to untrusted third parties (i.e. you are the end user), the best thing you can do is preallocate the array and use it instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt;, on the basis that your own code won’t mutate it.
Otherwise it can be stashed in a local variable wherever needed, which should alleviate concerns about mutability.&lt;/p&gt;

&lt;p&gt;The benchmark below compares iteration over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt;, a preallocated array populated from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt; and over an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumSet&lt;/code&gt; for enums of different sizes.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumIterationBenchmark&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valuesFour&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valuesEight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valuesSixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cachedFour&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VALUES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cachedEight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VALUES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cachedSixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;VALUES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;enumSetFour&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Four&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;enumSetEight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Eight&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;enumSetSixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Sixteen&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ordinal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2021/11/5-mundane-java-performance-tips/enumit.png&quot; alt=&quot;enum iteration&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedEight&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;28.644844&lt;/td&gt;
        &lt;td&gt;0.171543&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedEight:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;0.000012&lt;/td&gt;
        &lt;td&gt;0.000000&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedFour&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;14.774474&lt;/td&gt;
        &lt;td&gt;0.072698&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedFour:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;0.000006&lt;/td&gt;
        &lt;td&gt;0.000000&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedSixteen&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;56.613368&lt;/td&gt;
        &lt;td&gt;0.296960&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.cachedSixteen:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;0.000023&lt;/td&gt;
        &lt;td&gt;0.000000&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetEight&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;53.622149&lt;/td&gt;
        &lt;td&gt;1.782352&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetEight:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;32.000020&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetFour&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;29.840317&lt;/td&gt;
        &lt;td&gt;1.830070&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetFour:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;32.000011&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetSixteen&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;105.247332&lt;/td&gt;
        &lt;td&gt;49.049506&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.enumSetSixteen:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;32.000040&lt;/td&gt;
        &lt;td&gt;0.000018&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesEight&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;33.237779&lt;/td&gt;
        &lt;td&gt;2.077222&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesEight:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;48.000014&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesFour&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;16.610465&lt;/td&gt;
        &lt;td&gt;0.906318&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesFour:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;32.000007&lt;/td&gt;
        &lt;td&gt;0.000000&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesSixteen&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;56.557226&lt;/td&gt;
        &lt;td&gt;3.012126&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumIterationBenchmark.valuesSixteen:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;80.000023&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enum.values()&lt;/code&gt; will allocate an easily avoidable 80 bytes per call for a 16 element enum, though doesn’t pose a time penalty.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumSet&lt;/code&gt;s are tiny, so using them reduces allocation rate, but the iteration code is slower than iterating over an array so they don’t offer a good stand in.&lt;/p&gt;

&lt;p&gt;I found a &lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/26842&quot;&gt;case&lt;/a&gt; of this which had existed in Spring framework for over a decade earlier in the year, where resolving HTTP status code to Spring’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpStatus&lt;/code&gt; enum was allocating ~1MB/s at very low request rate.&lt;/p&gt;

&lt;h3 id=&quot;use-enums-instead-of-constant-strings&quot;&gt;Use enums instead of constant Strings&lt;/h3&gt;

&lt;p&gt;There are obvious benefits to using enums instead of strings for constants because enums enforce validation, but they are generally good for performance: even though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt; is fast, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumMap&lt;/code&gt; is faster.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumMapBenchmark&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;10000&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;42&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;SplittableRandom&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SplittableRandom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumMapState&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;EnumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MixedState&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;EnumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EnumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMapState&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseState&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;randomValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;enumValues&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;enumMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;EnumMapState&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnEnum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HashMapState&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: seed&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumMapBenchmark.enumMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;65.225800&lt;/td&gt;
        &lt;td&gt;7.990521&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;42&lt;/td&gt;
        &lt;td&gt;10000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;EnumMapBenchmark.hashMap&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;151.394872&lt;/td&gt;
        &lt;td&gt;3.564463&lt;/td&gt;
        &lt;td&gt;us/op&lt;/td&gt;
        &lt;td&gt;42&lt;/td&gt;
        &lt;td&gt;10000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Naturally, there is a one time conversion cost to produce the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt; (which ironically uses a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HashMap&lt;/code&gt;) but once that’s been done it will pay for itself by allowing the use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumMap&lt;/code&gt; (and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumSet&lt;/code&gt;) unless the value is basically inert.
Don’t contort your code or risk being unable to consume data you don’t control just because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumMap&lt;/code&gt; is fast, but if your data naturally fits an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;enum&lt;/code&gt;, then be sure to make use of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumMap&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EnumSet&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;stop-using-jdk8&quot;&gt;Stop using JDK8&lt;/h3&gt;

&lt;p&gt;All Java applications use a lot of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;s, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt; was just a lot fatter and slower in JDK8.
For example, consider constructing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;s from ASCII encoded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte[]&lt;/code&gt;s, which happens a lot if you do things like parse JSON or load classes.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UTF8Benchmark&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charsetName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;4&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;20&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;200&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2000&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Charset&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;charset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;forName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;charsetName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asciiBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stringFromBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
   
    &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bytesFromString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;asciiByted&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x7F&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The benchmark was run on JDK8 and JDK11.
ASCII &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;s (e.g. class names and metadata or JSON keys) were twice the size on JDK8 because the content was always stored in UTF-16, which explains why the allocation rate was halved in JDK11, but pay attention to how much faster the decoding is too.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2021/11/5-mundane-java-performance-tips/decode-time.png&quot; alt=&quot;UTF-8 decode time&quot; /&gt;
&lt;img src=&quot;/assets/2021/11/5-mundane-java-performance-tips/decode-allocation.png&quot; alt=&quot;UTF-8 decode allocation&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;JDK&lt;/th&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: charsetName&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;18.355847&lt;/td&gt;
        &lt;td&gt;0.674115&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;51.693575&lt;/td&gt;
        &lt;td&gt;1.610212&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;48.000008&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;88.000023&lt;/td&gt;
        &lt;td&gt;0.000003&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;20.827067&lt;/td&gt;
        &lt;td&gt;3.695586&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;60.232549&lt;/td&gt;
        &lt;td&gt;1.767569&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;64.000009&lt;/td&gt;
        &lt;td&gt;0.000002&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;120.000026&lt;/td&gt;
        &lt;td&gt;0.000004&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;34.451863&lt;/td&gt;
        &lt;td&gt;9.095702&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;159.427489&lt;/td&gt;
        &lt;td&gt;8.422851&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;240.000014&lt;/td&gt;
        &lt;td&gt;0.000004&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;480.000070&lt;/td&gt;
        &lt;td&gt;0.000011&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;285.590238&lt;/td&gt;
        &lt;td&gt;75.077635&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1350.229058&lt;/td&gt;
        &lt;td&gt;69.483579&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2040.000118&lt;/td&gt;
        &lt;td&gt;0.000030&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.stringFromBytes:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;4080.000601&lt;/td&gt;
        &lt;td&gt;0.000117&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;It’s a similar story when encoding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String&lt;/code&gt;s to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte[]&lt;/code&gt; too, which is something your application will do if it does any kind of serialization, logging, or tracing.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2021/11/5-mundane-java-performance-tips/encode-time.png&quot; alt=&quot;UTF-8 encode time&quot; /&gt;
&lt;img src=&quot;/assets/2021/11/5-mundane-java-performance-tips/encode-allocation.png&quot; alt=&quot;UTF-8 encode allocation&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;JDK&lt;/th&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: charsetName&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;13.835438&lt;/td&gt;
        &lt;td&gt;0.661028&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;62.958213&lt;/td&gt;
        &lt;td&gt;1.832734&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;24.000006&lt;/td&gt;
        &lt;td&gt;0.000001&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;160.000028&lt;/td&gt;
        &lt;td&gt;0.000004&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;4&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;17.457885&lt;/td&gt;
        &lt;td&gt;2.175969&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;75.760063&lt;/td&gt;
        &lt;td&gt;3.000610&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;40.000007&lt;/td&gt;
        &lt;td&gt;0.000002&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;224.000033&lt;/td&gt;
        &lt;td&gt;0.000004&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;20&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;30.758900&lt;/td&gt;
        &lt;td&gt;8.336043&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;227.269922&lt;/td&gt;
        &lt;td&gt;18.536450&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;216.000013&lt;/td&gt;
        &lt;td&gt;0.000003&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;936.000100&lt;/td&gt;
        &lt;td&gt;0.000014&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;200&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;280.886995&lt;/td&gt;
        &lt;td&gt;61.136388&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2138.703149&lt;/td&gt;
        &lt;td&gt;203.825640&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;11&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2016.000116&lt;/td&gt;
        &lt;td&gt;0.000024&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;UTF8Benchmark.bytesFromString:·gc.alloc.rate.norm&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;8136.000937&lt;/td&gt;
        &lt;td&gt;0.000113&lt;/td&gt;
        &lt;td&gt;B/op&lt;/td&gt;
        &lt;td&gt;UTF-8&lt;/td&gt;
        &lt;td&gt;2000&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;These operations are so fundamental that it’s impossible to escape them.&lt;/p&gt;</content><author><name></name></author><category term="java" /><summary type="html">Most of the time it isn’t really necessary to optimise software, but this post contains 5 tips to avoid making software written in Java slower for the sake of it.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2021/11/5-mundane-java-performance-tips/tbd.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2021/11/5-mundane-java-performance-tips/tbd.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Loop Fission</title><link href="https://richardstartin.github.io/posts/loop-fission" rel="alternate" type="text/html" title="Loop Fission" /><published>2021-11-27T00:00:00+00:00</published><updated>2021-11-27T00:00:00+00:00</updated><id>https://richardstartin.github.io/posts/loop-fission</id><content type="html" xml:base="https://richardstartin.github.io/posts/loop-fission">&lt;p&gt;Loop fission is a process normally applied by a compiler to loops to make them faster.
The idea is to split larger loop bodies which perform several distinct tasks into separate loops, in the hope that the individual loops can be optimised more effectively in isolation. 
As far as I’m aware, C2, Hotspot’s JIT compiler, doesn’t do this so you have to do it yourself.
I came across a couple of cases where this was profitable recently, and this post uses these as examples.&lt;/p&gt;

&lt;ol id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#xor&quot; id=&quot;markdown-toc-xor&quot;&gt;XOR&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#group-by&quot; id=&quot;markdown-toc-group-by&quot;&gt;Group By&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;xor&quot;&gt;XOR&lt;/h3&gt;

&lt;p&gt;One of the operations which can be performed between two &lt;a href=&quot;https://github.com/RoaringBitmap/RoaringBitmap&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt;&lt;/a&gt; objects is an XOR or symmetric difference.
After applying a destructive (i.e. one of the bitmaps will be mutated) XOR between two bitmaps, the mutated bitmap will contain a bit wherever the bitmaps differed before the XOR.
One of the &lt;a href=&quot;/posts/a-quick-look-at-roaringbitmap&quot;&gt;three container types&lt;/a&gt; in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RoaringBitmap&lt;/code&gt; is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BitmapContainer&lt;/code&gt; which contains a bitmap, a dense collection of bits.
When a destructive XOR is applied between two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BitmapContainer&lt;/code&gt;s, the mutated container must have its bits updated, but they also need to be counted.
This can be done in one loop or two separate loops, as in the representative benchmark below:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;XorCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;256&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;512&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1024&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2048&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4096&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ThreadLocalRandom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextLong&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;


  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fused&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fissured&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It seems intuitive that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fused&lt;/code&gt; would be faster; there is only one pass over the data, but this isn’t necessarily the case.&lt;/p&gt;
&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: size&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;146.324870&lt;/td&gt;
        &lt;td&gt;0.166643&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;283.583423&lt;/td&gt;
        &lt;td&gt;0.156367&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;564.507047&lt;/td&gt;
        &lt;td&gt;0.512299&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1154.083245&lt;/td&gt;
        &lt;td&gt;6.005591&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2805.933619&lt;/td&gt;
        &lt;td&gt;10.880235&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;229.713707&lt;/td&gt;
        &lt;td&gt;0.138824&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;462.435821&lt;/td&gt;
        &lt;td&gt;1.015399&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;908.624781&lt;/td&gt;
        &lt;td&gt;0.759116&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1802.717233&lt;/td&gt;
        &lt;td&gt;3.106054&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;XorCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;3585.279127&lt;/td&gt;
        &lt;td&gt;6.716832&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2021/11/loop-fission/xorcount.png&quot; alt=&quot;XorCount&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For small sizes, the fissured loop is noticeably faster, with convergence as the lengths grow. 
Eventually, the bitset would get so large that doing a single pass would make more sense as it makes better use of cache, but in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BitmapContainer&lt;/code&gt; the length is fixed to 1024 elements, so this is immaterial.&lt;/p&gt;

&lt;p&gt;Why is the fissured loop faster?
This is because of the loop which performs the XOR operation:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;^=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;right&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This loop can be autovectorised - many XORs can be computed in a single instruction. 
On my laptop, running JDK11 on Ubuntu, 256 bits or 4 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;long&lt;/code&gt;s are operated on from each array at a time.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;  1.11%  │    │││ │↗      ││  0x00007f1bc835b892: vmovdqu 0x10(%rdi,%rdx,8),%ymm0
  0.31%  │    │││ ││      ││  0x00007f1bc835b898: vpxor  0x10(%r11,%rdx,8),%ymm0,%ymm0
  0.40%  │    │││ ││      ││  0x00007f1bc835b89f: vmovdqu %ymm0,0x10(%r11,%rdx,8)
  1.09%  │    │││ ││      ││  0x00007f1bc835b8a6: vmovdqu 0x30(%rdi,%rdx,8),%ymm0
  0.44%  │    │││ ││      ││  0x00007f1bc835b8ac: vpxor  0x30(%r11,%rdx,8),%ymm0,%ymm0
  1.69%  │    │││ ││      ││  0x00007f1bc835b8b3: vmovdqu %ymm0,0x30(%r11,%rdx,8)
  0.42%  │    │││ ││      ││  0x00007f1bc835b8ba: vmovdqu 0x50(%rdi,%rdx,8),%ymm0
  0.31%  │    │││ ││      ││  0x00007f1bc835b8c0: vpxor  0x50(%r11,%rdx,8),%ymm0,%ymm0
  0.77%  │    │││ ││      ││  0x00007f1bc835b8c7: vmovdqu %ymm0,0x50(%r11,%rdx,8)
  0.54%  │    │││ ││      ││  0x00007f1bc835b8ce: vmovdqu 0x70(%rdi,%rdx,8),%ymm0
  0.42%  │    │││ ││      ││  0x00007f1bc835b8d4: vpxor  0x70(%r11,%rdx,8),%ymm0,%ymm0
  2.28%  │    │││ ││      ││  0x00007f1bc835b8db: vmovdqu %ymm0,0x70(%r11,%rdx,8)
  0.33%  │    │││ ││      ││  0x00007f1bc835b8e2: vmovdqu 0x90(%rdi,%rdx,8),%ymm0
  0.46%  │    │││ ││      ││  0x00007f1bc835b8eb: vpxor  0x90(%r11,%rdx,8),%ymm0,%ymm0
  1.13%  │    │││ ││      ││  0x00007f1bc835b8f5: vmovdqu %ymm0,0x90(%r11,%rdx,8)
  0.19%  │    │││ ││      ││  0x00007f1bc835b8ff: vmovdqu 0xb0(%rdi,%rdx,8),%ymm0
  0.36%  │    │││ ││      ││  0x00007f1bc835b908: vpxor  0xb0(%r11,%rdx,8),%ymm0,%ymm0
  2.38%  │    │││ ││      ││  0x00007f1bc835b912: vmovdqu %ymm0,0xb0(%r11,%rdx,8)
  0.13%  │    │││ ││      ││  0x00007f1bc835b91c: vmovdqu 0xd0(%rdi,%rdx,8),%ymm0
  0.40%  │    │││ ││      ││  0x00007f1bc835b925: vpxor  0xd0(%r11,%rdx,8),%ymm0,%ymm0
  1.44%  │    │││ ││      ││  0x00007f1bc835b92f: vmovdqu %ymm0,0xd0(%r11,%rdx,8)
  0.15%  │    │││ ││      ││  0x00007f1bc835b939: vmovdqu 0xf0(%rdi,%rdx,8),%ymm0
  0.23%  │    │││ ││      ││  0x00007f1bc835b942: vpxor  0xf0(%r11,%rdx,8),%ymm0,%ymm0
  2.84%  │    │││ ││      ││  0x00007f1bc835b94c: vmovdqu %ymm0,0xf0(%r11,%rdx,8)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The other loop doesn’t get the same treatment from the compiler:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;left&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Long&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bitCount&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;popcnt&lt;/code&gt; instructions below operate on 64 bits each.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;  0.09%  │││              ↗   0x00007f3b1c35bd02: popcnt 0x28(%r11,%rbx,8),%r8
 12.50%  │││              │   0x00007f3b1c35bd09: popcnt 0x20(%r11,%rbx,8),%rdi
  0.02%  │││              │   0x00007f3b1c35bd10: popcnt 0x48(%r11,%rbx,8),%rdx
 12.86%  │││              │   0x00007f3b1c35bd17: popcnt 0x40(%r11,%rbx,8),%r9
  0.02%  │││              │   0x00007f3b1c35bd1e: popcnt 0x38(%r11,%rbx,8),%rax
  0.02%  │││              │   0x00007f3b1c35bd25: popcnt 0x30(%r11,%rbx,8),%rsi
         │││              │   0x00007f3b1c35bd2c: popcnt 0x18(%r11,%rbx,8),%r13
  7.61%  │││              │   0x00007f3b1c35bd33: popcnt 0x10(%r11,%rbx,8),%rbp
         │││              │   0x00007f3b1c35bd3a: add    %ecx,%ebp
  0.07%  │││              │   0x00007f3b1c35bd3c: add    %ebp,%r13d
  0.02%  │││              │   0x00007f3b1c35bd3f: add    %edi,%r13d
  8.25%  │││              │   0x00007f3b1c35bd42: add    %r8d,%r13d
  0.07%  │││              │   0x00007f3b1c35bd45: add    %r13d,%esi
  0.07%  │││              │   0x00007f3b1c35bd48: add    %esi,%eax
  7.94%  │││              │   0x00007f3b1c35bd4a: add    %eax,%r9d
  8.10%  │││              │   0x00007f3b1c35bd4d: add    %r9d,%edx 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When the loops are fused (or aren’t fissured) the XORs aren’t vectorised and only operate on 64 bits at a time.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;  0.31%    ↗│││   0x00007f6eac35baa0: mov    0x10(%r10,%r11,8),%rsi
  2.28%    ││││   0x00007f6eac35baa5: xor    0x10(%rbx,%r11,8),%rsi  
           ││││                                                 
           ││││                                                 
  7.69%    ││││   0x00007f6eac35baaa: mov    %rsi,0x10(%rbx,%r11,8)
           ││││                                                 
           ││││                                                 
  2.06%    ││││   0x00007f6eac35baaf: popcnt %rsi,%rsi
  8.92%    ││││   0x00007f6eac35bab4: add    %esi,%edx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the fused loop, the loop advances at the pace of the slowest operation.
At some point, the array would get so large that not doing two passes over the array would trump better code generation, which might be why C2 is cautious here.
Notably, the &lt;a href=&quot;/posts/population-count-in-java&quot;&gt;popcount&lt;/a&gt; operation can be &lt;a href=&quot;https://arxiv.org/abs/1611.07612&quot;&gt;vectorised&lt;/a&gt; and clang &lt;a href=&quot;https://reviews.llvm.org/rG6ba9730a4ef3515653d1813fb716988398ca2c5d&quot;&gt;does so&lt;/a&gt;, so the fused loop written in C++ and compiled with clang would be much faster.
clang will also perform loop fission (which it calls &lt;a href=&quot;https://reviews.llvm.org/D19403&quot;&gt;distribution&lt;/a&gt;).  &lt;br /&gt;
This is a good example of performance intuition from one language not carrying over into another.&lt;/p&gt;

&lt;h3 id=&quot;group-by&quot;&gt;Group By&lt;/h3&gt;

&lt;p&gt;I recently started working on &lt;a href=&quot;https://pinot.apache.org/&quot;&gt;Apache Pinot&lt;/a&gt;, which is a scalable OLAP store.
In a group by query, values from one column are aggregated by values in another, and in Pinot this is performed on a block of data at a time.
One of the steps in the aggregation is to copy a set of dictionary ids corresponding to the groups and to mark which groups are present in the block so they can be iterated over when aggregating the other column.
The total number of groups for the entire column is known from statistics collected about the values, but not at the block level.
The fused and fissured operations look like this (resetting the array would not happen for real but is necessary to make the benchmark stable):&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@State&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Scope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Benchmark&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GroupCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;8&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;64&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;128&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Param&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;({&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;256&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;512&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1024&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2048&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4096&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Level&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Trial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SplittableRandom&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SplittableRandom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;nextInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fused&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Benchmark&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fissured&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Blackhole&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;arraycopy&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;groups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xFF&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;numGroups&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bh&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;consume&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a little more interesting than the other loop.
Firstly, splitting the loops makes it obvious that the copy part is a manual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.arraycopy&lt;/code&gt;.
The second loop can terminate whenever &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;numGroups == groups&lt;/code&gt; without consuming the entire input, but in the fused loop has to carry on to ensure the entire (slow) manual array copy can take place.
Whilst fission makes the copy fast, and an obvious candidate for removal, fission allows the second loop to terminate early.&lt;/p&gt;

&lt;p&gt;Early termination isn’t necessarily a good thing because if the number of groups is high enough for it to be likely that no single block will contain all the groups, the loop won’t exit early.
Worse, with early termination, the exit condition from the loop is data dependent, which prevents aggressive unrolling, so the loop will actually be slower when it doesn’t exit early.&lt;/p&gt;

&lt;div class=&quot;table-holder&quot;&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Benchmark&lt;/th&gt;
        &lt;th&gt;Mode&lt;/th&gt;
        &lt;th&gt;Threads&lt;/th&gt;
        &lt;th&gt;Samples&lt;/th&gt;
        &lt;th&gt;Score&lt;/th&gt;
        &lt;th&gt;Score Error (99.9%)&lt;/th&gt;
        &lt;th&gt;Unit&lt;/th&gt;
        &lt;th&gt;Param: groups&lt;/th&gt;
        &lt;th&gt;Param: length&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;41.606830&lt;/td&gt;
        &lt;td&gt;0.187490&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;44.518117&lt;/td&gt;
        &lt;td&gt;0.183473&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;50.099552&lt;/td&gt;
        &lt;td&gt;0.263780&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;61.991607&lt;/td&gt;
        &lt;td&gt;0.254863&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;134.196339&lt;/td&gt;
        &lt;td&gt;0.138997&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;542.240261&lt;/td&gt;
        &lt;td&gt;1.993239&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;494.577003&lt;/td&gt;
        &lt;td&gt;0.331404&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;501.147187&lt;/td&gt;
        &lt;td&gt;0.265733&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;510.997369&lt;/td&gt;
        &lt;td&gt;1.264461&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;575.261847&lt;/td&gt;
        &lt;td&gt;0.583042&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;603.379813&lt;/td&gt;
        &lt;td&gt;1.389718&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1121.185683&lt;/td&gt;
        &lt;td&gt;26.927230&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1132.562254&lt;/td&gt;
        &lt;td&gt;36.868698&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1141.871722&lt;/td&gt;
        &lt;td&gt;18.612546&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fissured&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1198.111175&lt;/td&gt;
        &lt;td&gt;49.512727&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;197.811972&lt;/td&gt;
        &lt;td&gt;0.352569&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;344.481666&lt;/td&gt;
        &lt;td&gt;0.602220&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;642.021575&lt;/td&gt;
        &lt;td&gt;2.235174&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1236.629115&lt;/td&gt;
        &lt;td&gt;7.446193&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2478.591752&lt;/td&gt;
        &lt;td&gt;2.470746&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;8&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;380.130086&lt;/td&gt;
        &lt;td&gt;1.642884&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;441.293203&lt;/td&gt;
        &lt;td&gt;1.776462&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;793.848668&lt;/td&gt;
        &lt;td&gt;17.267290&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1265.961589&lt;/td&gt;
        &lt;td&gt;5.980144&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2340.019651&lt;/td&gt;
        &lt;td&gt;4.249029&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;64&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;290.768967&lt;/td&gt;
        &lt;td&gt;9.365530&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;256&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;752.002294&lt;/td&gt;
        &lt;td&gt;0.905900&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;512&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1110.519415&lt;/td&gt;
        &lt;td&gt;1.109794&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;1024&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;1887.382144&lt;/td&gt;
        &lt;td&gt;48.908744&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;2048&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GroupCount.fused&lt;/td&gt;
        &lt;td&gt;avgt&lt;/td&gt;
        &lt;td&gt;1&lt;/td&gt;
        &lt;td&gt;5&lt;/td&gt;
        &lt;td&gt;2550.872751&lt;/td&gt;
        &lt;td&gt;23.857060&lt;/td&gt;
        &lt;td&gt;ns/op&lt;/td&gt;
        &lt;td&gt;128&lt;/td&gt;
        &lt;td&gt;4096&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2021/11/loop-fission/groupcount.png&quot; alt=&quot;results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As can be seen above, loop fission changes things a lot, and the effect of the number of groups is more pronounced in the fissured implementation.
When the ratio between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;length&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;groups&lt;/code&gt; is high, the fissured implementation wins (see 4096/8 134ns vs 2479ns).
When the ratio is low, the effect is less extreme but in the opposite direction (see 512/128 1121ns vs 752ns).
If you like looking at things like perfasm output to compare the counted and non–counted loop, it is &lt;a href=&quot;https://github.com/richardstartin/runtime-benchmarks/blob/master/GroupCount.perfasm&quot;&gt;here&lt;/a&gt;.
Given that there are statistics about the number of groups, and the block size is a small and bounded value, fission allows a data driven decision to be made for whether to attempt to exit the second loop early.&lt;/p&gt;</content><author><name></name></author><category term="java" /><summary type="html">Loop fission is a process normally applied by a compiler to loops to make them faster. The idea is to split larger loop bodies which perform several distinct tasks into separate loops, in the hope that the individual loops can be optimised more effectively in isolation. As far as I’m aware, C2, Hotspot’s JIT compiler, doesn’t do this so you have to do it yourself. I came across a couple of cases where this was profitable recently, and this post uses these as examples.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://richardstartin.github.io/assets/2021/11/loop-fission/groupcount.png" /><media:content medium="image" url="https://richardstartin.github.io/assets/2021/11/loop-fission/groupcount.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>