{"generator":"Jekyll","link":[{"@attributes":{"href":"https:\/\/richardstartin.github.io\/feed.xml","rel":"self","type":"application\/atom+xml"}},{"@attributes":{"href":"https:\/\/richardstartin.github.io\/","rel":"alternate","type":"text\/html"}}],"updated":"2023-11-13T10:24:21+00:00","id":"https:\/\/richardstartin.github.io\/feed.xml","title":"Richard Startin\u2019s Blog","entry":[{"title":"How Async-Profiler\u2019s crash handler works","link":{"@attributes":{"href":"https:\/\/richardstartin.github.io\/posts\/async-profiler-crash-handler","rel":"alternate","type":"text\/html","title":"How Async-Profiler\u2019s crash handler works"}},"published":"2023-11-12T00:00:00+00:00","updated":"2023-11-12T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/async-profiler-crash-handler","content":"<p>Raw pointers in C\/C++ open up entire classes of error that are practically unimaginable in higher level languages.\nSo why does anybody use them at all?\nUnfortunately, it\u2019s impossible to write a profiler without getting close to some of the sharp edges of unsafe memory. \nThe async-profiler code base contains a lot of low level tricks, and it\u2019s worth studying how some of them work.<\/p>\n\n<p>This post is about how async-profiler safely dereferences arbitrary pointers without crashing the JVM, and why it needs to do that sometimes.<\/p>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#signals-and-signal-handlers\" id=\"markdown-toc-signals-and-signal-handlers\">Signals and signal handlers<\/a><\/li>\n  <li><a href=\"#frame-pointer-unwinding\" id=\"markdown-toc-frame-pointer-unwinding\">Frame pointer unwinding<\/a><\/li>\n  <li><a href=\"#safeaccessload\" id=\"markdown-toc-safeaccessload\"><code class=\"language-plaintext highlighter-rouge\">SafeAccess::load<\/code><\/a><\/li>\n  <li><a href=\"#replacing-crash-handlers\" id=\"markdown-toc-replacing-crash-handlers\">Replacing crash handlers<\/a><\/li>\n<\/ol>\n\n<h1 id=\"signals-and-signal-handlers\">Signals and signal handlers<\/h1>\n\n<p>Linux allows threads to be interrupted by signals, which are identified by a signal number.\nSignals can be sent to threads via a number of syscalls, such as <a href=\"https:\/\/man7.org\/linux\/man-pages\/man2\/tgkill.2.html\"><code class=\"language-plaintext highlighter-rouge\">tgkill<\/code><\/a>, or indirectly via profiling services like <a href=\"https:\/\/man7.org\/linux\/man-pages\/man3\/setitimer.3p.html\">itimer<\/a> or <a href=\"https:\/\/man7.org\/linux\/man-pages\/man2\/perf_event_open.2.html\">perf_event_open<\/a>.\nWhen 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.\nIf there is a pending signal, and the signal isn\u2019t blocked by a <a href=\"https:\/\/man7.org\/linux\/man-pages\/man2\/sigprocmask.2.html\">signal mask<\/a>, and there is a <em>signal handler<\/em> installed for the signal, then the thread is halted and the signal handler is invoked.\nA signal based sampling profiler consists of a mechanism for choosing threads and sending signals (usually <code class=\"language-plaintext highlighter-rouge\">SIGPROF<\/code>) to them, then a signal handler for collecting information about the state of the thread (e.g. unwinding the stack).\nSignals and signal handlers are used for a number of other things, such as killing the process (<code class=\"language-plaintext highlighter-rouge\">SIGKILL<\/code>), or signalling that something has gone wrong (<code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code>, <code class=\"language-plaintext highlighter-rouge\">SIGABRT<\/code>, <code class=\"language-plaintext highlighter-rouge\">SIGBUS<\/code>).\nIf 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.<\/p>\n\n<h1 id=\"frame-pointer-unwinding\">Frame pointer unwinding<\/h1>\n\n<p>You don\u2019t need to handle segfaults if your code can\u2019t segfault, so why not just use a programming language with a compiler which verifies memory safety?\nUnfortunately, you wouldn\u2019t have much of a profiler if you did that (without unsafe blocks, anyway).\nConsider the simplest stack unwinding approach using frame pointers.<\/p>\n\n<p>Signal handlers are invoked with a <code class=\"language-plaintext highlighter-rouge\">ucontext<\/code> which consists of a program counter, a frame pointer, and a stack pointer.<\/p>\n\n<p>The program counter (<code class=\"language-plaintext highlighter-rouge\">pc<\/code>) is the address of the code currently being executed.\nIt can be used to find the currently executing function by binary searching a compiled binary\u2019s text section (where the code is found) or the JVM\u2019s code heap for JIT compiled code.\nIt is enough to determine the currently executing instruction (which is how instruction profiling works).\nHowever, the program counter is not enough to determine the caller of the interrupted function.<\/p>\n\n<p>The frame pointer (<code class=\"language-plaintext highlighter-rouge\">fp<\/code>) determines the start address of the stack frame, which contains function parameters and local variables.\nThe 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.\nObtaining the frame pointer to the calling frame from the callee frame is known as <em>unwinding<\/em>.\n<em>If<\/em> a binary is compiled with frame pointers, it\u2019s really simple: on x64, the <code class=\"language-plaintext highlighter-rouge\">rbp<\/code> register contains the caller\u2019s frame pointer.\nSo frame pointer unwinding consists of the following (on x64, the registers differ on aarch64):<\/p>\n\n<ol>\n  <li>Record the program counter from the <code class=\"language-plaintext highlighter-rouge\">ucontext<\/code> (this can be used later to determine things like function name, line number, and the instruction)<\/li>\n  <li>Read the caller\u2019s frame pointer from <code class=\"language-plaintext highlighter-rouge\">rbp<\/code><\/li>\n  <li>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).<\/li>\n  <li>Restore the caller frame from the caller\u2019s frame pointer (dereference the frame pointer).<\/li>\n  <li>Record the program counter (see point 1)<\/li>\n  <li>Go to step 2<\/li>\n<\/ol>\n\n<p>This is about as fast and simple as stack unwinding gets, but the problem is lots of binaries are compiled without frame pointers.\nIf a binary is compiled without frame pointers, <code class=\"language-plaintext highlighter-rouge\">rbp<\/code> contains arbitrary data and isn\u2019t safe to dereference.\nDepending on the exact value stored in the register, dereferencing it may produce a bogus frame but will probably segfault (generate a <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> signal).\nI 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 <code class=\"language-plaintext highlighter-rouge\">rbp<\/code> is a frame pointer or not is to dereference it and hopefuly get a frame.\nThis puts frame pointer unwinding outside the realm of compile time memory safety checks: either mitigate at runtime or don\u2019t do it.<\/p>\n\n<h1 id=\"safeaccessload\"><code class=\"language-plaintext highlighter-rouge\">SafeAccess::load<\/code><\/h1>\n\n<p>Async-profiler uses the following mechanism:<\/p>\n<ol>\n  <li>Always dereference untrusted data via a function called <code class=\"language-plaintext highlighter-rouge\">SafeAccess::load<\/code>.<\/li>\n  <li>Install a <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> handler.<\/li>\n  <li>Check in the <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> handler if the <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> signal\u2019s <code class=\"language-plaintext highlighter-rouge\">pc<\/code> came from <code class=\"language-plaintext highlighter-rouge\">SafeAccess::load<\/code>. If it did, patch things up as if the function returned zero, otherwise pass the signal on.<\/li>\n<\/ol>\n\n<p>There are some details which are worth paying attention to in the function declaration (<a href=\"https:\/\/github.com\/async-profiler\/async-profiler\/blob\/master\/src\/safeAccess.h#L33\">source<\/a>):<\/p>\n\n<div class=\"language-cpp highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>   <span class=\"cp\">#ifdef __clang__\n<\/span>   <span class=\"cp\">#  define NOINLINE __attribute__((noinline))\n<\/span>   <span class=\"cp\">#else\n<\/span>   <span class=\"cp\">#  define NOINLINE __attribute__((noinline,noclone))\n<\/span>   <span class=\"cp\">#endif\n<\/span>   <span class=\"p\">...<\/span>\n   <span class=\"n\">NOINLINE<\/span> <span class=\"nf\">__attribute__<\/span><span class=\"p\">((<\/span><span class=\"n\">aligned<\/span><span class=\"p\">(<\/span><span class=\"mi\">16<\/span><span class=\"p\">)))<\/span>\n    <span class=\"k\">static<\/span> <span class=\"kt\">void<\/span><span class=\"o\">*<\/span> <span class=\"n\">load<\/span><span class=\"p\">(<\/span><span class=\"kt\">void<\/span><span class=\"o\">**<\/span> <span class=\"n\">ptr<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"o\">*<\/span><span class=\"n\">ptr<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Firstly, the function can\u2019t be inlined because of the compiler attributes applied to it, because if it were inlined, it wouldn\u2019t be possible to determine whether the <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code>\u2019s <code class=\"language-plaintext highlighter-rouge\">pc<\/code> came from the function or not.\nNext, the function is aligned so that a <code class=\"language-plaintext highlighter-rouge\">pc<\/code> can be determined to be within the function\u2019s code\u2019s address range easily.<\/p>\n\n<p>In the signal handler installed when starting the profiler, the following code executes:<\/p>\n\n<div class=\"language-cpp highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">uintptr_t<\/span> <span class=\"n\">length<\/span> <span class=\"o\">=<\/span> <span class=\"n\">SafeAccess<\/span><span class=\"o\">::<\/span><span class=\"n\">skipLoad<\/span><span class=\"p\">(<\/span><span class=\"n\">pc<\/span><span class=\"p\">);<\/span>\n    <span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">length<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"c1\">\/\/ Skip the fault instruction, as if it successfully loaded NULL<\/span>\n        <span class=\"n\">frame<\/span><span class=\"p\">.<\/span><span class=\"n\">pc<\/span><span class=\"p\">()<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">length<\/span><span class=\"p\">;<\/span>\n        <span class=\"n\">frame<\/span><span class=\"p\">.<\/span><span class=\"n\">retval<\/span><span class=\"p\">()<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n        <span class=\"k\">return<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This code figures out how far to skip ahead to simulate returning from the function, having set the return value to zero.\nThe way the number of instructions to skip is determined is a little cryptic (only for x64 below):<\/p>\n\n<div class=\"language-cpp highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"k\">static<\/span> <span class=\"kt\">uintptr_t<\/span> <span class=\"nf\">skipLoad<\/span><span class=\"p\">(<\/span><span class=\"kt\">uintptr_t<\/span> <span class=\"n\">pc<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"p\">((<\/span><span class=\"n\">pc<\/span> <span class=\"o\">-<\/span> <span class=\"p\">(<\/span><span class=\"kt\">uintptr_t<\/span><span class=\"p\">)<\/span><span class=\"n\">load<\/span><span class=\"p\">)<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">16<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n            <span class=\"k\">return<\/span> <span class=\"o\">*<\/span><span class=\"p\">(<\/span><span class=\"n\">u16<\/span><span class=\"o\">*<\/span><span class=\"p\">)<\/span><span class=\"n\">pc<\/span> <span class=\"o\">==<\/span> <span class=\"mh\">0x8b48<\/span> <span class=\"o\">?<\/span> <span class=\"mi\">3<\/span> <span class=\"o\">:<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span>  <span class=\"c1\">\/\/ mov rax, [reg]<\/span>\n        <span class=\"p\">}<\/span>\n        <span class=\"k\">return<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n    <span class=\"p\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Checking if the <code class=\"language-plaintext highlighter-rouge\">pc<\/code> is in the address range of the <code class=\"language-plaintext highlighter-rouge\">load<\/code> function is straightforward enough, but what on earth is <code class=\"language-plaintext highlighter-rouge\">0x8b48<\/code>?\nDereferencing a <code class=\"language-plaintext highlighter-rouge\">pc<\/code> gives a sequence of bytes corresponding to the instructions. \nCasting to a <code class=\"language-plaintext highlighter-rouge\">u16*<\/code> truncates to the most significant 2 bytes, which has the effect of ignoring the register the value would be loaded into.\nThe <code class=\"language-plaintext highlighter-rouge\">0x8b48<\/code> check verifies that the <code class=\"language-plaintext highlighter-rouge\">pc<\/code> is pointing at a <code class=\"language-plaintext highlighter-rouge\">mov<\/code> operation, otherwise something has gone wrong.\nFinally, 3 bytes need to be skipped over: 2 for the load, and one for the destination register.<\/p>\n\n<h1 id=\"replacing-crash-handlers\">Replacing crash handlers<\/h1>\n\n<p>The JVM has its own <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> handler already, in fact, it\u2019s quite important because it\u2019s part of the <a href=\"https:\/\/shipilev.net\/jvm\/anatomy-quarks\/29-uncommon-traps\/\">uncommon trap mechanism<\/a> for deoptimisation of speculative optimisations.\nThis means Async-profiler must delegate to the JVM\u2019s <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> handler in order not to break the JVM.\nIt does this by <a href=\"https:\/\/github.com\/async-profiler\/async-profiler\/blob\/master\/src\/os_linux.cpp#L255\">returning a function pointer to the old handler<\/a> when <a href=\"https:\/\/github.com\/async-profiler\/async-profiler\/blob\/master\/src\/profiler.cpp#L888\">installing its own handler<\/a>, and then delegates to it if the <code class=\"language-plaintext highlighter-rouge\">pc<\/code> didn\u2019t come from <code class=\"language-plaintext highlighter-rouge\">SafeAccess::load<\/code>.\nThe JVM doesn\u2019t exactly like signals it handles being overridden like this, given the central importance of <code class=\"language-plaintext highlighter-rouge\">SIGSEGV<\/code> signals to the lifecycle of JIT compiled code, and running with <code class=\"language-plaintext highlighter-rouge\">-Xcheck:jni<\/code> will spit out angry warning messages about it. \nThese 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.<\/p>","author":{"name":{}},"category":{"@attributes":{"term":"profiling"}},"summary":"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\u2019s 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\u2019s worth studying how some of them work."},{"title":"Understanding Request Latency with Profiling","link":{"@attributes":{"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","updated":"2023-09-05T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/wallclock-profiler","content":"<p>It can be hard to figure out why response times are high in Java applications.\nIn 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.\nMaking 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 <em>why<\/em> it was a bad change and are left in limbo.\nIn 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\u2019re using the wrong <em>type<\/em> of profile for analysis.\nLots of profiling data focuses on CPU time, but the cause of your latency problem may be related to time spent off CPU instead. \nThis post is about Datadog\u2019s 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.<\/p>\n\n<blockquote>\n  <p>This is a personal blog and everything written below is my own personal opinion.<\/p>\n<\/blockquote>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#from-metrics-to-profiles\" id=\"markdown-toc-from-metrics-to-profiles\">From metrics to profiles<\/a><\/li>\n  <li><a href=\"#async-profiler\" id=\"markdown-toc-async-profiler\">async-profiler<\/a><\/li>\n  <li><a href=\"#wallclock-profiling\" id=\"markdown-toc-wallclock-profiling\">Wallclock profiling<\/a>    <ol>\n      <li><a href=\"#sampling-strategy\" id=\"markdown-toc-sampling-strategy\">Sampling strategy<\/a><\/li>\n      <li><a href=\"#tracer-managed-thread-filter\" id=\"markdown-toc-tracer-managed-thread-filter\">Tracer managed thread filter<\/a><\/li>\n    <\/ol>\n  <\/li>\n  <li><a href=\"#latency-investigation\" id=\"markdown-toc-latency-investigation\">Latency investigation<\/a>    <ol>\n      <li><a href=\"#increasing-the-heap-size\" id=\"markdown-toc-increasing-the-heap-size\">Increasing the heap size<\/a><\/li>\n      <li><a href=\"#increasing-the-number-of-vcpus\" id=\"markdown-toc-increasing-the-number-of-vcpus\">Increasing the number of vCPUs<\/a><\/li>\n    <\/ol>\n  <\/li>\n  <li><a href=\"#what-about-cost\" id=\"markdown-toc-what-about-cost\">What about cost?<\/a><\/li>\n<\/ol>\n\n<h2 id=\"from-metrics-to-profiles\">From metrics to profiles<\/h2>\n\n<p>Suppose you have metrics which show you that request latency is too high for a particular endpoint.\nHere is the endpoint level latency timeseries for a slow endpoint <code class=\"language-plaintext highlighter-rouge\">GET \/allocation-stalls<\/code> (the name may create a sense of foreboding) in a very contrived demo Java application.<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/latency-before.png\" alt=\"High request latency\" \/><\/p>\n\n<p>The response time is about 3-4s, which we would like to improve somehow without undertaking an R&amp;D project.\nUnless you have detailed tracing, you might start looking at profiles to do this.\nOpenJDK has a built-in profiler called Java Flight Recorder (JFR), which Datadog has used historically for collecting CPU profiles from Java services.\nHere\u2019s what a flamegraph of JFR\u2019s <code class=\"language-plaintext highlighter-rouge\">jdk.ExecutionSample<\/code> events looks like after being processed by Datadog\u2019s profiling backend:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/jfr-flamegraph.png\" alt=\"JFR flamegraph\" \/><\/p>\n\n<p>To the far left there is some JVM activity derived from JFR\u2019s GC events. \nNext to that there are some stack traces for Jetty\u2019s routing (see <code class=\"language-plaintext highlighter-rouge\">JettyHandler.doHandle()<\/code>).\nTo the right are stack traces for handling the requests in another thread pool (see <code class=\"language-plaintext highlighter-rouge\">ThreadPoolExecutor$Worker.run()<\/code>).\nThere\u2019s not much context beyond the frame names, and the most we have to go on is <code class=\"language-plaintext highlighter-rouge\">JavaServer.lambda$allocationStall$28()<\/code> which we can probably guess is related to handling <code class=\"language-plaintext highlighter-rouge\">GET \/allocation-stalls<\/code> requests by its name.\nJava code can be very generic and it\u2019s not always straightforward to map frames to context by frame names alone, though in this case it\u2019s easy enough. \nExpanding 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:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/jfr-flamegraph-expanded.png\" alt=\"JFR flamegraph expanded\" \/><\/p>\n\n<p>This points to random number generation being the bottleneck, which you aren\u2019t likely to speed up.\nYou may be able to reduce the number of random numbers generated, but in this contrived example, this would require renegotiating requirements.\nPart of the problem is that this flamegraph doesn\u2019t show the complete picture. \nWithout having the full picture, we can\u2019t be sure we know what the bottleneck is, and might just be looking at the frame we happen to <em>measure<\/em> the most.\nThis is where async-profiler comes in.<\/p>\n\n<h2 id=\"async-profiler\">async-profiler<\/h2>\n\n<p>Some time ago, Datadog started experimenting with using <a href=\"https:\/\/github.com\/async-profiler\/async-profiler\">async-profiler<\/a> instead of JFR\u2019s execution sampler in its continuous profiling product.\nThere were two primary motivations for doing this:<\/p>\n\n<ol>\n  <li>It\u2019s better\n    <ol>\n      <li>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.<\/li>\n      <li>async-profiler has several approaches to unwinding native parts of the stack, whereas JFR does not unwind stacks with native frames.<\/li>\n      <li>async-profiler reports failed samples so even if it\u2019s not possible to get a full stack trace, it doesn\u2019t distort the weights of other samples.<\/li>\n    <\/ol>\n  <\/li>\n  <li>We could modify the source code in a fork\n    <ol>\n      <li>It\u2019s easy to make changes to the source code and get those changes shipped in a timely manner.<\/li>\n      <li>JFR doesn\u2019t 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.<\/li>\n    <\/ol>\n  <\/li>\n<\/ol>\n\n<p>So async-profiler\u2019s excellent codebase gave us a starting point for implementing some of the features we had in mind.<\/p>\n\n<p>Below is a flamegraph of the same application as above recorded with Datadog\u2019s customisation of async-profiler. \nNotice the ZGC activity to the left and compare it to the GC activity we could infer from JFR above, there\u2019s more detail and it\u2019s more obvious that the service has a GC problem.<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-flamegraph-cpu-time-all-activity.png\" alt=\"async-profiler all activity\" \/><\/p>\n\n<p>Having access to the source code made it possible to add new fields to event types, such as correlation identifiers.\nDecorating 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).\nThis makes it possible to filter the flamegraph by endpoint instead of guessing based on the frame names:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-flamegraph-cpu-time-endpoint-filter.png\" alt=\"async-profiler all activity\" \/><\/p>\n\n<p>The green frames to the left, which are JNI calls into the lz4-java library, are missing from the JFR flamegraph because JFR won\u2019t unwind stacks when the JVM is executing native code.\nIn this particular case, this doesn\u2019t actually distort the flamegraph <em>much<\/em>, because LZ4 compression is very fast and doesn\u2019t 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.<\/p>\n\n<p>Even though the flamegraph now shows a complete picture of CPU time, it still points towards optimising random number generation, which we can\u2019t do.\nThe problem is now that CPU time isn\u2019t always enough to understand latency.<\/p>\n\n<h2 id=\"wallclock-profiling\">Wallclock profiling<\/h2>\n\n<p>As used in Datadog\u2019s profiler, async-profiler\u2019s CPU profiler uses CPU time to schedule sampling, which means time spent off-CPU will not be sampled. \nIn Java <code class=\"language-plaintext highlighter-rouge\">Thread<\/code> terms, a CPU profiler can only sample threads in the <code class=\"language-plaintext highlighter-rouge\">RUNNABLE<\/code> state, and ignores threads in the <code class=\"language-plaintext highlighter-rouge\">BLOCKED<\/code>, <code class=\"language-plaintext highlighter-rouge\">TIMED_WAITING<\/code>, and <code class=\"language-plaintext highlighter-rouge\">WAITING<\/code> states, but a <em>wallclock profiler<\/em> can sample threads in any state.\nasync-profiler also has a wallclock profiler, but we found we needed to tailor it to our requirements.<\/p>\n\n<h3 id=\"sampling-strategy\">Sampling strategy<\/h3>\n\n<p>The first issue we encountered was with overhead (for our own particular requirements, don\u2019t be discouraged from using async-profiler\u2019s wallclock profiler in a different context).\nCPU 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.\nasync-profiler\u2019s 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.\nUnless the user provides a thread filter expression to control how many threads to sample, the overhead of this approach can be quite high.\nUsing 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.\nIt 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.<\/p>\n\n<p>We wanted smaller recordings and needed to be very conservative about overhead, so we changed the way threads are sampled. \nWe implemented reservoir sampling of threads so we could take a uniform sample of a fixed number of threads at a constant sampling interval. \nThis 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.\nThis 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.<\/p>\n\n<h3 id=\"tracer-managed-thread-filter\">Tracer managed thread filter<\/h3>\n\n<p>High thread counts also can also reduce sample relevance, without a thread filter. \nFor latency analysis it would be ideal to bias towards threads on the critical path. \nThere are two kinds of noncritical threads for typical applications:<\/p>\n\n<ol>\n  <li>idle threads in over-provisioned thread pools, each waiting for items to appear in a queue.<\/li>\n  <li>background threads such as metrics reporters.<\/li>\n<\/ol>\n\n<p>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 <code class=\"language-plaintext highlighter-rouge\">LinkedBlockingQueue<\/code>.<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/all-threads-datadog-flamegraph-all-activity.png\" alt=\"all threads\" \/><\/p>\n\n<p>This isn\u2019t very useful information: while fine-tuning the size of this thread pool would be generally beneficial, the threads aren\u2019t blocking the threads processing requests.<\/p>\n\n<p>What\u2019s worse is when the samples are rendered as a timeline of a particular trace, the coverage is quite sparse (the grey bars are <code class=\"language-plaintext highlighter-rouge\">PARKED<\/code> walltime samples, the blue bars are CPU samples).<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/all-threads-datadog-timeline-trace-filter.png\" alt=\"all timeline trace filter\" \/><\/p>\n\n<p>Sampling idle or background activity reduces the coverage of traces.<\/p>\n\n<p>Both of these problems can be solved by only sampling threads which have an active trace context set up by the tracer.\nFirstly, 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.\nThanks to context propagation, which propagates trace context into thread pools, any thread which ends up working to complete the request can be sampled.\nSecondly, biasing sampling towards traced activity helps <em>focus<\/em> the profiler on explaining trace latency, so we have the situation where the tracer can tell you how long a request took, and there\u2019s a good chance that there will be wallclock samples to explain why it took so long.\nWhenever we have too much work to do, we can do a better job by prioritising some tasks over others, and it\u2019s the same for sampling threads.<\/p>\n\n<p>This results in a flamegraph focused on latency-critical work:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/thread-filter-datadog-flamegraph-all-activity.png\" alt=\"traced threads\" \/><\/p>\n\n<p>and when we look at the timeline of a particular trace, we have much better coverage (i.e. more grey bars for more threads)<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/thread-filter-datadog-timeline-trace-filter.png\" alt=\"filtered timeline trace filter\" \/><\/p>\n\n<p>This is implemented by repurposing async-profiler\u2019s thread filter, so that it is only applied to the wallclock profiler, and is controlled by the tracer. \nThe 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.\nIf you have used a tracing API, you will be familiar with <em>spans<\/em> (logical operations within traces) and <em>scopes<\/em> (activations of spans on a thread). \nThe 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.\nModifying 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.\nInstead, 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.\nFinally, the sampler thread implements reservoir sampling over the bitset of traced threads, rather than over all threads.<\/p>\n\n<h2 id=\"latency-investigation\">Latency investigation<\/h2>\n\n<p>The wallclock profiler reports stack traces for any blocking off-CPU activity, which should tell you <em>where<\/em> the blocking operation happened, and <em>why<\/em> the blocking operation occurred by looking at the ancestor frames.\nIt should have been possible to figure out why the request latency is so high from the screenshots so far, but here\u2019s the timeline for a particular request trace again:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/thread-filter-datadog-timeline-trace-filter.png\" alt=\"filtered timeline trace filter\" \/><\/p>\n\n<p>The long grey bars, with durations of about a second, all contain the frame <code class=\"language-plaintext highlighter-rouge\">ZPageAllocator::alloc_page_stall()<\/code>, which blocks on <code class=\"language-plaintext highlighter-rouge\">PosixSemaphore::wait()<\/code>, with causal ancestor frame <code class=\"language-plaintext highlighter-rouge\">ByteBuffer.allocate(int)<\/code>.\nIn the flamegraph scoped to the endpoint, we can see that about 50% of the samples under <code class=\"language-plaintext highlighter-rouge\">JavaServer.lambda$allocationStall$29()<\/code> are allocation stalls, so the CPU profile missed roughly half the activity:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-flamegraph-wall-time-endpoint-filter.png\" alt=\"\" \/><\/p>\n\n<p>Allocation stalls happen with ZGC, when the application threads try to allocate more memory than is currently available.\nThe application thread blocks until the requested allocation can be performed, which incurs latency. \nZGC 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.\nAllocation 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.<\/p>\n\n<p>In this contrived case, there are 30 tasks allocating a pair of 50 MiB <code class=\"language-plaintext highlighter-rouge\">ByteBuffer<\/code>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.\nHaving identified and understood allocation stalls, we can see the heap is too small for the workload.\nWe can\u2019t reduce allocation pressure by reducing the size of the buffers without renegotiating requirements, and we can\u2019t 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.<\/p>\n\n<blockquote>\n  <p>There is also a JFR event <code class=\"language-plaintext highlighter-rouge\">jdk.ZAllocationStall<\/code> which could be used instead of a wallclock profiler to determine that the heap is too small, but it does not collect stacktraces.<\/p>\n<\/blockquote>\n\n<h3 id=\"increasing-the-heap-size\">Increasing the heap size<\/h3>\n\n<p>Solving this problem should be a simple case of increasing the heap size so more memory can be allocated without stalling.\nThe service should then become CPU bound, and it was already established that random number generation can\u2019t be accelerated, so we wouldn\u2019t be able to remove this bottleneck without redefining the workload.<\/p>\n\n<p>The heap size was increased to 6GiB.\nLooking at the wallclock profile, we can see a change in thread state:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/thread-state-with-stalls.png\" alt=\"thread state with stalls\" \/><\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/thread-state-without-stalls.png\" alt=\"thread state without stalls\" \/><\/p>\n\n<p>Before, in the top image, about half the time was in the <code class=\"language-plaintext highlighter-rouge\">RUNNABLE<\/code> state and half in the <code class=\"language-plaintext highlighter-rouge\">PARKED<\/code> state, afterwards the split is 3:1.\nThe allocation stalls have gone, and none of the <code class=\"language-plaintext highlighter-rouge\">PARKED<\/code> time is under the <code class=\"language-plaintext highlighter-rouge\">JavaServer.lambda$allocationStall$29()<\/code> frame: the allocation stalls have disappeared.\nLooking at a trace of a particular request, the grey bars which represented parking have gone, and we only see the blue CPU samples:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-timeline-bigger-heap.png\" alt=\"timeline bigger heap\" \/><\/p>\n\n<p>It\u2019s clear the way the JVM executes the code has changed as a result of being given more resources, but it hasn\u2019t actually reduced latency.<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/latency-bigger-heap.png\" alt=\"latency bigger heap\" \/><\/p>\n\n<h3 id=\"increasing-the-number-of-vcpus\">Increasing the number of vCPUs<\/h3>\n\n<p>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.<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-timeline-bigger-heap.png\" alt=\"timeline bigger heap\" \/><\/p>\n\n<p>We can also see a strange discrepancy between the amount of wall time sampled in the <code class=\"language-plaintext highlighter-rouge\">RUNNABLE<\/code> state and the amount of sampled CPU time:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-flamegraph-bigger-heap-endpoint-filter-cpu-time.png\" alt=\"cpu time\" \/>\n<img src=\"\/assets\/2023\/09\/wallclock-profiler\/datadog-flamegraph-bigger-heap-endpoint-filter-wall-time.png\" alt=\"wall time\" \/><\/p>\n\n<p>If the workload is now CPU bound, as we expect it to be, <em>and<\/em> it\u2019s actually executing, then the CPU time and the wall time should be the same, but there\u2019s 18 seconds of <code class=\"language-plaintext highlighter-rouge\">RUNNABLE<\/code> wall time and less than 5s of CPU time.\nThough we don\u2019t 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.\nAs can be seen in the timeline, the workload is parallelised over 10 threads, but only 2 vCPUs were allocated. \nIncreasing the number of vCPUs to 10, finally, reduces latency dramatically:<\/p>\n\n<p><img src=\"\/assets\/2023\/09\/wallclock-profiler\/latency-after.png\" alt=\"latency more cpu\" \/><\/p>\n\n<p>Though they will never correspond exactly, the sampled <code class=\"language-plaintext highlighter-rouge\">RUNNABLE<\/code> wall time and CPU times have converged:\n<img src=\"\/assets\/2023\/09\/wallclock-profiler\/more-cpu-cpu-time.png\" alt=\"Wall time\" \/>\n<img src=\"\/assets\/2023\/09\/wallclock-profiler\/more-cpu-wall-time.png\" alt=\"CPU time\" \/><\/p>\n\n<p>and the timeline for a particular trace shows 10 threads with denser blue CPU samples:\n<img src=\"\/assets\/2023\/09\/wallclock-profiler\/more-cpu-timeline.png\" alt=\"timeline\" \/><\/p>\n\n<h2 id=\"what-about-cost\">What about cost?<\/h2>\n\n<p>This probably isn\u2019t very satisfying: the cost of the running service has been increased dramatically.\nHowever, the aim was to reduce request latency for a contrived workload that isn\u2019t amenable to optimisations.\nWhen code is at its efficient frontier (or <em>effectively<\/em> at its efficient frontier without, say, incurring huge engineering costs), there is a tradeoff between latency and cost.\nIf you don\u2019t 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.\nIf you do have an SLA, and optimisation is impractical or impossible, and you can\u2019t negotiate requirements, you\u2019ll need to incur costs and allocate more compute resources.\nIt\u2019s quite rare for a Java service to be at its efficient frontier, and there\u2019s usually low hanging fruit surfaced by continuous profiling which can sometimes reduce latency and cost simultaneously.<\/p>","author":{"name":{}},"category":{"@attributes":{"term":"profiling"}},"summary":"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\u2019re 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\u2019s 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."},{"title":"Using JFR and JMC to root cause a performance bug in JMC caused by a typo in JFR","link":{"@attributes":{"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","updated":"2022-12-18T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/jmc-hanging","content":"<p>When you open a profile in JMC, it normally takes a few seconds, but there are some profiles that JMC struggles to load.\nThis happens when a profile contains events which violate an assumption made in JMC\u2019s parser: events on the same thread are almost always disjoint in their durations.\nWhen 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.\nJMC can handle overlapping events, but assumes this essentially never happens, and exhibits quadratic scaling when events overlap.<\/p>\n\n<p>The good news is that unless you hack it into the JFR file generation in your own customisation of async-profiler, it\u2019s practically impossible to generate events that violate this assumption.\nHowever, if you run this code:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">main<\/span><span class=\"o\">(<\/span><span class=\"nc\">String<\/span><span class=\"o\">...<\/span> <span class=\"n\">args<\/span><span class=\"o\">)<\/span> <span class=\"kd\">throws<\/span> <span class=\"nc\">Exception<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">args<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">1<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n            <span class=\"nc\">System<\/span><span class=\"o\">.<\/span><span class=\"na\">err<\/span><span class=\"o\">.<\/span><span class=\"na\">println<\/span><span class=\"o\">(<\/span><span class=\"s\">\"provide a directory to write the JFR file to\"<\/span><span class=\"o\">);<\/span>\n            <span class=\"nc\">System<\/span><span class=\"o\">.<\/span><span class=\"na\">exit<\/span><span class=\"o\">(<\/span><span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"k\">try<\/span><span class=\"o\">(<\/span><span class=\"nc\">Recording<\/span> <span class=\"n\">recording<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Recording<\/span><span class=\"o\">()){<\/span>\n            <span class=\"n\">recording<\/span><span class=\"o\">.<\/span><span class=\"na\">setName<\/span><span class=\"o\">(<\/span><span class=\"s\">\"slow\"<\/span><span class=\"o\">);<\/span>\n            <span class=\"n\">recording<\/span><span class=\"o\">.<\/span><span class=\"na\">setDumpOnExit<\/span><span class=\"o\">(<\/span><span class=\"kc\">true<\/span><span class=\"o\">);<\/span>\n            <span class=\"n\">recording<\/span><span class=\"o\">.<\/span><span class=\"na\">setDestination<\/span><span class=\"o\">(<\/span><span class=\"n\">getOrCreateDirectory<\/span><span class=\"o\">(<\/span><span class=\"n\">args<\/span><span class=\"o\">[<\/span><span class=\"mi\">0<\/span><span class=\"o\">]).<\/span><span class=\"na\">resolve<\/span><span class=\"o\">(<\/span><span class=\"s\">\"slow.jfr\"<\/span><span class=\"o\">));<\/span>\n            <span class=\"n\">recording<\/span><span class=\"o\">.<\/span><span class=\"na\">enable<\/span><span class=\"o\">(<\/span><span class=\"s\">\"jdk.FileWrite\"<\/span><span class=\"o\">);<\/span>\n            <span class=\"n\">recording<\/span><span class=\"o\">.<\/span><span class=\"na\">start<\/span><span class=\"o\">();<\/span>\n            <span class=\"n\">generateFileEvents<\/span><span class=\"o\">();<\/span>\n            \n        <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">private<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">generateFileEvents<\/span><span class=\"o\">()<\/span> <span class=\"kd\">throws<\/span> <span class=\"nc\">IOException<\/span> <span class=\"o\">{<\/span>\n        <span class=\"nc\">Path<\/span> <span class=\"n\">directory<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Files<\/span><span class=\"o\">.<\/span><span class=\"na\">createTempDirectory<\/span><span class=\"o\">(<\/span><span class=\"s\">\"files\"<\/span><span class=\"o\">);<\/span>\n        <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">100<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">1000<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n            <span class=\"k\">try<\/span> <span class=\"o\">(<\/span><span class=\"nc\">RandomAccessFile<\/span> <span class=\"n\">file<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">RandomAccessFile<\/span><span class=\"o\">(<\/span><span class=\"n\">directory<\/span><span class=\"o\">.<\/span><span class=\"na\">resolve<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span> <span class=\"o\">+<\/span> <span class=\"s\">\"\"<\/span><span class=\"o\">).<\/span><span class=\"na\">toFile<\/span><span class=\"o\">(),<\/span> <span class=\"s\">\"rw\"<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n               <span class=\"n\">file<\/span><span class=\"o\">.<\/span><span class=\"na\">write<\/span><span class=\"o\">((<\/span><span class=\"s\">\"data\"<\/span> <span class=\"o\">+<\/span> <span class=\"n\">i<\/span><span class=\"o\">).<\/span><span class=\"na\">getBytes<\/span><span class=\"o\">(<\/span><span class=\"nc\">StandardCharsets<\/span><span class=\"o\">.<\/span><span class=\"na\">UTF_8<\/span><span class=\"o\">));<\/span>\n            <span class=\"o\">}<\/span>\n        <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">private<\/span> <span class=\"kd\">static<\/span> <span class=\"nc\">Path<\/span> <span class=\"nf\">getOrCreateDirectory<\/span><span class=\"o\">(<\/span><span class=\"nc\">String<\/span> <span class=\"n\">dir<\/span><span class=\"o\">)<\/span> <span class=\"kd\">throws<\/span> <span class=\"nc\">IOException<\/span> <span class=\"o\">{<\/span>\n        <span class=\"nc\">Path<\/span> <span class=\"n\">destination<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Paths<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">dir<\/span><span class=\"o\">);<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(!<\/span><span class=\"nc\">Files<\/span><span class=\"o\">.<\/span><span class=\"na\">exists<\/span><span class=\"o\">(<\/span><span class=\"n\">destination<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n            <span class=\"nc\">Files<\/span><span class=\"o\">.<\/span><span class=\"na\">createDirectory<\/span><span class=\"o\">(<\/span><span class=\"n\">destination<\/span><span class=\"o\">);<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"k\">return<\/span> <span class=\"n\">destination<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>And then open the .jfr file in JMC, you will see this for quite some time (well over 30s):<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/slow.png\" alt=\"Slow\" \/><\/p>\n\n<p>Fortunately JMC itself can be profiled as it is loading a profile, using JFR with the command:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>jcmd &lt;pid of JMC&gt; JFR.start settings=profile filename=jmcrecording.jfr duration=60s\n<\/code><\/pre><\/div><\/div>\n\n<p>After 60s a profile will be dumped containing execution samples of whatever JMC spent its time doing.\nLoading the profile of JMC itself and navigating to <em>Method Profiling<\/em>:<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/navigate.png\" alt=\"Method Profiling\" \/><\/p>\n\n<p>This shows you a flamegraph of CPU samples:<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/before.png\" alt=\"Cause\" \/><\/p>\n\n<p>There are over 6000 samples, which is a good number for a JFR CPU profile, which takes samples at about 100Hz.\nIf 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\u2019t have a lot of failed samples, which JFR doesn\u2019t otherwise account for.\nAssuming there is no JNI activity, this means the profile should be reasonably accurate.\nIt\u2019s tempting to scroll to the bottom and look for the widest frame and optimise it.\nThe obvious candidate here are the calls to <code class=\"language-plaintext highlighter-rouge\">ImpreciseScaleFactor.targetNumber<\/code>, outlined in black below:<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/leaf.png\" alt=\"Red Herring\" \/><\/p>\n\n<p>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.\nCPU sampling can\u2019t differentiate between a method being slow (requiring constant factors improvements) and being called too often (requiring algorithmic improvements).\nUnfortunately, JFR offers no way to perform a differential diagnosis to rule out constant factors and focus on algorithmic issues.<\/p>\n\n<p>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.\nSometimes, this is too complicated, and I have resorted to inserting counter probes in these methods with bytebuddy (but I recently discovered <a href=\"https:\/\/github.com\/jvm-profiling-tools\/async-profiler#java-method-profiling\">async-profiler can do this<\/a> and I have adopted this approach in preference to instrumentation during analyses).\nIf you have a superlinear algorithm, you see high hit counts at the leaves, and low hit counts in the interior nodes.\nIn any case, reading the code for <code class=\"language-plaintext highlighter-rouge\">DisjointBuilder.add<\/code> (outlined in black below):<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/circled.png\" alt=\"DisjointBuilder\" \/><\/p>\n\n<p>reveals what looks like an optimisation:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"c1\">\/\/ at 77580fe1b483b4daf8f46938c297185b01f32304<\/span>\n\t<span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">add<\/span><span class=\"o\">(<\/span><span class=\"no\">T<\/span> <span class=\"n\">e<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t<span class=\"nc\">IQuantity<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"n\">startAccessor<\/span><span class=\"o\">.<\/span><span class=\"na\">getMember<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">);<\/span>\n\t\t<span class=\"nc\">IQuantity<\/span> <span class=\"n\">end<\/span> <span class=\"o\">=<\/span> <span class=\"n\">endAccessor<\/span><span class=\"o\">.<\/span><span class=\"na\">getMember<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">).<\/span><span class=\"na\">in<\/span><span class=\"o\">(<\/span><span class=\"n\">start<\/span><span class=\"o\">.<\/span><span class=\"na\">getUnit<\/span><span class=\"o\">());<\/span>\n\t\t<span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">noLanes<\/span> <span class=\"o\">==<\/span> <span class=\"mi\">0<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t\t<span class=\"n\">addToNewLane<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n\t\t<span class=\"o\">}<\/span> <span class=\"k\">else<\/span> <span class=\"k\">if<\/span> <span class=\"o\">(!<\/span><span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"mi\">0<\/span><span class=\"o\">].<\/span><span class=\"na\">accept<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n\t\t\t<span class=\"kt\">int<\/span> <span class=\"n\">changedLane<\/span> <span class=\"o\">=<\/span> <span class=\"n\">addToOtherLane<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n\t\t\t<span class=\"n\">sortLanes<\/span><span class=\"o\">(<\/span><span class=\"n\">changedLane<\/span><span class=\"o\">);<\/span>\n\t\t<span class=\"o\">}<\/span>\n\t<span class=\"o\">}<\/span>\n\n\t<span class=\"kd\">private<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">sortLanes<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">fromIndex<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t<span class=\"c1\">\/\/ Sorting the lanes by descending end time<\/span>\n\t\t<span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">fromIndex<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">--)<\/span> <span class=\"o\">{<\/span>\n\t\t\t<span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">].<\/span><span class=\"na\">end<\/span><span class=\"o\">.<\/span><span class=\"na\">compareTo<\/span><span class=\"o\">(<\/span><span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">].<\/span><span class=\"na\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t\t\t<span class=\"nc\">DisjointArray<\/span><span class=\"o\">&lt;<\/span><span class=\"no\">T<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">tmp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">];<\/span>\n\t\t\t\t<span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n\t\t\t\t<span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">tmp<\/span><span class=\"o\">;<\/span>\n\t\t\t<span class=\"o\">}<\/span>\n\t\t<span class=\"o\">}<\/span>\n\t<span class=\"o\">}<\/span>\n\n\t<span class=\"kd\">private<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">addToOtherLane<\/span><span class=\"o\">(<\/span><span class=\"no\">T<\/span> <span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t<span class=\"c1\">\/\/ Try with the other existing lanes<\/span>\n\t\t<span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">noLanes<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n\t\t\t<span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">].<\/span><span class=\"na\">accept<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n\t\t\t\t<span class=\"k\">return<\/span> <span class=\"n\">i<\/span><span class=\"o\">;<\/span>\n\t\t\t<span class=\"o\">}<\/span>\n\t\t<span class=\"o\">}<\/span>\n\t\t<span class=\"k\">return<\/span> <span class=\"nf\">addToNewLane<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n\t<span class=\"o\">}<\/span>\n\n\t<span class=\"kd\">private<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">addToNewLane<\/span><span class=\"o\">(<\/span><span class=\"no\">T<\/span> <span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t<span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">noLanes<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">lanes<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n\t\t\t<span class=\"n\">lanes<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">lanes<\/span><span class=\"o\">,<\/span> <span class=\"o\">(<\/span><span class=\"n\">lanes<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">3<\/span><span class=\"o\">)<\/span> <span class=\"o\">\/<\/span> <span class=\"mi\">2<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">2<\/span><span class=\"o\">);<\/span>\n\t\t<span class=\"o\">}<\/span>\n\t\t<span class=\"n\">lanes<\/span><span class=\"o\">[<\/span><span class=\"n\">noLanes<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">DisjointArray<\/span><span class=\"o\">&lt;&gt;(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n\t\t<span class=\"k\">return<\/span> <span class=\"n\">noLanes<\/span><span class=\"o\">++;<\/span>\n\t<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">lanes[0].accept<\/code> is obviously expected to be the common case (it happens whenever the events are disjoint).\nIts implementation appears to be a constant time operation too, assuming the comparator isn\u2019t bizarre:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">boolean<\/span> <span class=\"nf\">accept<\/span><span class=\"o\">(<\/span><span class=\"no\">T<\/span> <span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"nc\">IQuantity<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">size<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">array<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n            <span class=\"kt\">int<\/span> <span class=\"n\">newCapacity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">array<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">100<\/span> <span class=\"o\">?<\/span> <span class=\"n\">array<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">4<\/span> <span class=\"o\">:<\/span> <span class=\"o\">(<\/span><span class=\"n\">array<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">3<\/span><span class=\"o\">)<\/span> <span class=\"o\">\/<\/span> <span class=\"mi\">2<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n            <span class=\"n\">array<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">array<\/span><span class=\"o\">,<\/span> <span class=\"n\">newCapacity<\/span><span class=\"o\">);<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">start<\/span><span class=\"o\">.<\/span><span class=\"na\">compareTo<\/span><span class=\"o\">(<\/span><span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n            <span class=\"n\">array<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">++]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">e<\/span><span class=\"o\">;<\/span>\n            <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">end<\/span> <span class=\"o\">=<\/span> <span class=\"n\">end<\/span><span class=\"o\">;<\/span>\n            <span class=\"k\">return<\/span> <span class=\"kc\">true<\/span><span class=\"o\">;<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"k\">return<\/span> <span class=\"kc\">false<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The method <code class=\"language-plaintext highlighter-rouge\">addToOtherLane<\/code> shows up in the flamegraph (outlined in black):<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/addToOtherLane.png\" alt=\"DisjointBuilder\" \/><\/p>\n\n<p>So we must be going down the other branch:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">int<\/span> <span class=\"n\">changedLane<\/span> <span class=\"o\">=<\/span> <span class=\"n\">addToOtherLane<\/span><span class=\"o\">(<\/span><span class=\"n\">e<\/span><span class=\"o\">,<\/span> <span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n    <span class=\"n\">sortLanes<\/span><span class=\"o\">(<\/span><span class=\"n\">changedLane<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">addToOtherLane<\/code> does a linear search for the first lane for which the last event ends before the event being added.\nThis can obviously be improved by replacing the linear search with binary search, since the sort order of the lanes by end time is maintained.\nThis doesn\u2019t describe all of the samples though, and it seems strange that so many samples should be in <code class=\"language-plaintext highlighter-rouge\">MemberAccessorToolkit$2.getMember<\/code>, and where is <code class=\"language-plaintext highlighter-rouge\">sortLanes<\/code>?<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/getMember.png\" alt=\"MemberAccessToolkit\" \/><\/p>\n\n<p>This only makes sense if <code class=\"language-plaintext highlighter-rouge\">sortLanes<\/code> (which is a quadratic bubble sort) has been inlined.\nKnowing that the lanes are maintained in sort order, when a lane needs to move, it\u2019s possible to find where it needs to move to in logarithmic time, and then perform a linear time copy.\nThis makes the algorithm linear in the number of lanes.<\/p>\n\n<p>These changes were implemented in <a href=\"https:\/\/github.com\/openjdk\/jmc\/pull\/449\">JMC-7950<\/a>, which will be released in as part of JMC 9, and the issue is more or less fixed.\nIf 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.\nUsing 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:<\/p>\n\n<p><img src=\"\/assets\/2022\/12\/jmc-hanging\/after.png\" alt=\"Fixed\" \/><\/p>\n\n<p>Erik Gahlin pointed out on the issue that <code class=\"language-plaintext highlighter-rouge\">jdk.FileWrite<\/code> events making profiles slow to load must be because of a bug in JFR.\nIt turned out there has been a bug in JFR since JDK15, introduced <a href=\"https:\/\/hg.openjdk.java.net\/jdk\/jdk\/rev\/dd0caf00b05c\">here<\/a>.\nThe problematic change is here:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>     <span class=\"nd\">@SuppressWarnings<\/span><span class=\"o\">(<\/span><span class=\"s\">\"deprecation\"<\/span><span class=\"o\">)<\/span>\n     <span class=\"nd\">@JIInstrumentationMethod<\/span>\n     <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">write<\/span><span class=\"o\">(<\/span><span class=\"kt\">byte<\/span> <span class=\"n\">b<\/span><span class=\"o\">[])<\/span> <span class=\"kd\">throws<\/span> <span class=\"nc\">IOException<\/span> <span class=\"o\">{<\/span>\n<span class=\"o\">-<\/span>        <span class=\"nc\">FileWriteEvent<\/span> <span class=\"n\">event<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">FileWriteEvent<\/span><span class=\"o\">.<\/span><span class=\"na\">EVENT<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">();<\/span>\n<span class=\"o\">-<\/span>        <span class=\"k\">if<\/span> <span class=\"o\">(!<\/span><span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">isEnabled<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n<span class=\"o\">+<\/span>        <span class=\"nc\">EventHandler<\/span> <span class=\"n\">handler<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Handlers<\/span><span class=\"o\">.<\/span><span class=\"na\">FILE_WRITE<\/span><span class=\"o\">;<\/span>\n<span class=\"o\">+<\/span>        <span class=\"k\">if<\/span> <span class=\"o\">(!<\/span><span class=\"n\">handler<\/span><span class=\"o\">.<\/span><span class=\"na\">isEnabled<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n             <span class=\"n\">write<\/span><span class=\"o\">(<\/span><span class=\"n\">b<\/span><span class=\"o\">);<\/span>\n             <span class=\"k\">return<\/span><span class=\"o\">;<\/span>\n         <span class=\"o\">}<\/span>\n<span class=\"o\">+<\/span>        <span class=\"kt\">long<\/span> <span class=\"n\">bytesWritten<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n<span class=\"o\">+<\/span>        <span class=\"kt\">long<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n         <span class=\"k\">try<\/span> <span class=\"o\">{<\/span>\n<span class=\"o\">-<\/span>            <span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">begin<\/span><span class=\"o\">();<\/span>\n<span class=\"o\">+<\/span>            <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">EventHandler<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span><span class=\"o\">();<\/span>\n             <span class=\"n\">write<\/span><span class=\"o\">(<\/span><span class=\"n\">b<\/span><span class=\"o\">);<\/span>\n<span class=\"o\">-<\/span>            <span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">bytesWritten<\/span> <span class=\"o\">=<\/span> <span class=\"n\">b<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span>\n<span class=\"o\">+<\/span>            <span class=\"n\">bytesWritten<\/span> <span class=\"o\">=<\/span> <span class=\"n\">b<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span>\n         <span class=\"o\">}<\/span> <span class=\"k\">finally<\/span> <span class=\"o\">{<\/span>\n<span class=\"o\">-<\/span>            <span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">path<\/span> <span class=\"o\">=<\/span> <span class=\"n\">path<\/span><span class=\"o\">;<\/span>\n<span class=\"o\">-<\/span>            <span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">commit<\/span><span class=\"o\">();<\/span>\n<span class=\"o\">-<\/span>            <span class=\"n\">event<\/span><span class=\"o\">.<\/span><span class=\"na\">reset<\/span><span class=\"o\">();<\/span>\n<span class=\"o\">+<\/span>            <span class=\"kt\">long<\/span> <span class=\"n\">duration<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">EventHandler<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span><span class=\"o\">();<\/span> <span class=\"c1\">\/\/ &lt;-- bug here<\/span>\n<span class=\"o\">+<\/span>            <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">handler<\/span><span class=\"o\">.<\/span><span class=\"na\">shouldCommit<\/span><span class=\"o\">(<\/span><span class=\"n\">duration<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n<span class=\"o\">+<\/span>                <span class=\"n\">handler<\/span><span class=\"o\">.<\/span><span class=\"na\">write<\/span><span class=\"o\">(<\/span><span class=\"n\">start<\/span><span class=\"o\">,<\/span> <span class=\"n\">duration<\/span><span class=\"o\">,<\/span> <span class=\"n\">path<\/span><span class=\"o\">,<\/span> <span class=\"n\">bytesWritten<\/span><span class=\"o\">);<\/span>\n<span class=\"o\">+<\/span>            <span class=\"o\">}<\/span>\n         <span class=\"o\">}<\/span>\n     <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The duration should have the start time subtracted from it. \nThis means that these events will generally overlap because <code class=\"language-plaintext highlighter-rouge\">end = start + duration<\/code> makes a nonsense value. \nThe 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.<\/p>","author":{"name":{}},"category":[{"@attributes":{"term":"java"}},{"@attributes":{"term":"profiling"}}],"summary":"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\u2019s 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."},{"title":"Evaluating Equality Predicates","link":{"@attributes":{"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","updated":"2022-12-18T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/range-bitmap-equality-queries","content":"<p>I have just <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\/pull\/606\">implemented support<\/a> for (in)equality queries against a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, a succinct data structure in the <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> library which supports range queries.\n<code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> was designed to support range queries in Apache Pinot (more details <a href=\"https:\/\/richardstartin.github.io\/posts\/range-bitmap-index\">here<\/a>) but this enhancement would allow a range index to be used as a fallback for (in)equality queries in case nothing better is available.\nSupporting (in)equality queries allows a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> to be used as a kind of compact inverted index, trading space for time, capable of supporting high cardinality gracefully.<br \/>\nSince <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> supports memory mapping from files, I think that it could be used for data engineering beyond Apache Pinot.<\/p>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#how-to-use-it\" id=\"markdown-toc-how-to-use-it\">How to use it?<\/a><\/li>\n  <li><a href=\"#how-does-it-work\" id=\"markdown-toc-how-does-it-work\">How does it work?<\/a><\/li>\n<\/ol>\n\n<h3 id=\"how-to-use-it\">How to use it?<\/h3>\n\n<p>This post extends the example set up in <a href=\"https:\/\/richardstartin.github.io\/posts\/range-predicates\">Evaluating Range Predicates<\/a> and <a href=\"https:\/\/richardstartin.github.io\/posts\/range-counts\">Counting over Range Predicates<\/a>, which centre around a large collection of <code class=\"language-plaintext highlighter-rouge\">Transaction<\/code> objects:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">Transaction<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"nf\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">=<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">getQuantity<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getPrice<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getTimestamp<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s find all the transactions with a certain quantity in the most obvious way possible, using the stream API.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n         <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">==<\/span> <span class=\"n\">qty<\/span><span class=\"o\">)<\/span>\n         <span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">(<\/span><span class=\"k\">this<\/span><span class=\"o\">::<\/span><span class=\"n\">processTransaction<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>On my laptop, it takes about 3ms to select about 100 transactions from 1M.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2849.444527<\/td>\n        <td>30.581160<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>With a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, these same transactions can be found with the already existing <code class=\"language-plaintext highlighter-rouge\">between<\/code> method:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">qtyIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">between<\/span><span class=\"o\">(<\/span><span class=\"n\">qty<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">,<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">);<\/span>\n    <span class=\"n\">matchesQuantity<\/span><span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">((<\/span><span class=\"nc\">IntConsumer<\/span><span class=\"o\">)<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">processTransaction<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">)));<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<blockquote>\n  <p>as explained in <a href=\"https:\/\/richardstartin.github.io\/posts\/range-predicates\">Evaluating Range Predicates<\/a>, the quantities in the index have been anchored to the smallest value in the population, as a size optimisation.<\/p>\n<\/blockquote>\n\n<p>This performs quite a lot better, selecting the same transactions in under 300us, which is a 10x improvement.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2849.444527<\/td>\n        <td>30.581160<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>between<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>296.488277<\/td>\n        <td>0.703309<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>This can be rewritten using <code class=\"language-plaintext highlighter-rouge\">eq<\/code>:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">qtyIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">eq<\/span><span class=\"o\">(<\/span><span class=\"n\">qty<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">);<\/span>\n    <span class=\"n\">matchesQuantity<\/span><span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">((<\/span><span class=\"nc\">IntConsumer<\/span><span class=\"o\">)<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">processTransaction<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">)));<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The new <code class=\"language-plaintext highlighter-rouge\">eq<\/code> method doesn\u2019t need to do as much work as <code class=\"language-plaintext highlighter-rouge\">between<\/code>, which needs to maintain and combine two bitsets during the scan over the <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, whereas <code class=\"language-plaintext highlighter-rouge\">eq<\/code> only needs one.\nThis means we can get a good speedup from <code class=\"language-plaintext highlighter-rouge\">eq<\/code> to select the same transactions:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2849.444527<\/td>\n        <td>30.581160<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>between<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>296.488277<\/td>\n        <td>0.703309<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>eq<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>183.913329<\/td>\n        <td>1.994362<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>It\u2019s worth making a comparison with an inverted index over <code class=\"language-plaintext highlighter-rouge\">quantity<\/code> (so a bitmap of transaction positions per quantity) now.\nThe 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.\nA range encoded inverted index (a mapping from quantity to bitmap of the positions of all transactions with a smaller quantity) will generally beat <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> for speed at the cost of space.\nThis makes a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> suitable for equality queries eiter on a high cardinality attribute or as a fallback better than scanning when range queries are more common.<\/p>\n\n<p>Inequality filters tend not to be very selective, so benchmarking the evaluation would be dominated by the time to scan the results, but the <code class=\"language-plaintext highlighter-rouge\">neq<\/code> method is present for API symmetry.<\/p>\n\n<p>In the same set of changes there are also methods to push a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> context down into <code class=\"language-plaintext highlighter-rouge\">eq<\/code> and <code class=\"language-plaintext highlighter-rouge\">neq<\/code> queries, which behaves like an intersection. \nRather 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 <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>.\nThere are also <code class=\"language-plaintext highlighter-rouge\">eqCardinality<\/code> and <code class=\"language-plaintext highlighter-rouge\">neqCardinality<\/code> methods, which produce counts rather than bitmaps, as described for <a href=\"https:\/\/richardstartin.github.io\/posts\/range-counts\">range counts<\/a>.<\/p>\n\n<h3 id=\"how-does-it-work\">How does it work?<\/h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> has a simple two-dimensional structure.\nThe first dimension is the rows in the order the values were added in.\nThe 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.<\/p>\n\n<p>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. \nEach $2^{16}$ rows are bucketed into a <em>horizontal slice<\/em> which consists of $64 - clz(max)$ <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> containers, one for each bit of the inputs, and a mask of size $64 - clz(max)$ bits, indicating the presence of a container. \nIf the $n$th value added to the <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> does not have bit $i$ set, the $i$th container in the $n\/2^{16}$th slice will have no bit set.\nIf 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.<\/p>\n\n<p>If the following values (with their binary representation in brackets) are added in sequence<\/p>\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>42 (101010)\n24 (011000)\n9  (001001)\n27 (011011)\n<\/code><\/pre><\/div><\/div>\n\n<p>The values are first negated to range encode them (see <a href=\"https:\/\/richardstartin.github.io\/posts\/range-bitmap-index\">here<\/a> for explanation).<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>010101\n100111\n110110\n100100\n<\/code><\/pre><\/div><\/div>\n<p>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.\nThis looks something like this, if stored as plain bitsets:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>110111 1100 0110 1111 1010 0110 \n<\/code><\/pre><\/div><\/div>\n\n<p>There are three kinds of <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> containers for different densities - sparse, dense, and run length encoded.\nContainers 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.\nEncoded as containers, this looks like:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>110111 [4,2] {2,4} [0,4] 1010 {2,4}\n<\/code><\/pre><\/div><\/div>\n\n<p>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.<\/p>\n\n<p>To evaluate queries against <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, the slices need to be iterated over in ascending order.\nFor each slice, the containers need to be extracted and combined, using a different combination algorithm for each relation.\nThe equality combination algorithm is very simple:<\/p>\n<ul>\n  <li>start with a bitset <code class=\"language-plaintext highlighter-rouge\">b<\/code> with a set bit for each row in the slice<\/li>\n  <li>for each <code class=\"language-plaintext highlighter-rouge\">i<\/code> from 0 to the size of the slice\u2019s mask,\n    <ul>\n      <li>if <code class=\"language-plaintext highlighter-rouge\">i<\/code> is set in the query value, remove the container <code class=\"language-plaintext highlighter-rouge\">i<\/code>\u2019s bits (or none, if container <code class=\"language-plaintext highlighter-rouge\">i<\/code> is missing for the current slice) from <code class=\"language-plaintext highlighter-rouge\">b<\/code><\/li>\n      <li>If <code class=\"language-plaintext highlighter-rouge\">i<\/code> is absent in the query value, intersect container <code class=\"language-plaintext highlighter-rouge\">i<\/code>\u2019s bits with <code class=\"language-plaintext highlighter-rouge\">b<\/code> (or just clear it id the container is missing for the slice)<\/li>\n    <\/ul>\n  <\/li>\n<\/ul>\n\n<p>For inequality, <code class=\"language-plaintext highlighter-rouge\">b<\/code> needs to be complemented at the end. \nRange queries are generally more complex to evaluate than this, but all queries are evaluated in this slice by slice in ascending row order fashion.<\/p>","author":{"name":{}},"category":[{"@attributes":{"term":"java"}},{"@attributes":{"term":"roaring"}}],"summary":"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."},{"title":"Counting over Range Predicates","link":{"@attributes":{"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","updated":"2022-03-27T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/range-counts","content":"<p>This post follows on from my <a href=\"\/posts\/range-predicates\">last post<\/a> about selecting objects satisfying a range predicate, and instead looks at how to count the objects.\nIf you can select objects, you can count them too, but it\u2019s a simpler problem so resources can be saved with a specialised solution.<\/p>\n\n<p>Suppose you want to count how many of the <code class=\"language-plaintext highlighter-rouge\">Transaction<\/code> objects below satisfy complex filters, and don\u2019t care about details about the objects themselves.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">Transaction<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"nf\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">=<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">getQuantity<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getPrice<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getTimestamp<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Once again, this sounds like a task for a database, but imagine you don\u2019t have one to rely on.<\/p>\n\n<p>You might use the streams API:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n    <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">price<\/span>\n                    <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"na\">count<\/span><span class=\"o\">();<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will inspect every transaction to check if it matches the constraints, and will count the number of matches.\nFor 1M transactions, with parameters which select about 250 transactions, this takes about 10ms on my laptop:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.\nThe transactions are the same as those in the previous post, and sorted by time. \nThis 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.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n    <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">end<\/span>\n                    <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">price<\/span><span class=\"o\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"na\">count<\/span><span class=\"o\">();<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3283.291345<\/td>\n        <td>44.566458<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.\nThis 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).\nArtificially polluting the filter in the benchmark setup shows how artificial the result for <code class=\"language-plaintext highlighter-rouge\">stream2<\/code> was:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"kd\">super<\/span><span class=\"o\">.<\/span><span class=\"na\">setup<\/span><span class=\"o\">();<\/span>\n      <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n          <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">1<\/span>\n              <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"na\">count<\/span><span class=\"o\">();<\/span>\n      <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n          <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">10<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">1000<\/span>\n              <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"o\">-<\/span><span class=\"mi\">1<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">)<\/span>\n          <span class=\"o\">.<\/span><span class=\"na\">count<\/span><span class=\"o\">();<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3283.291345<\/td>\n        <td>44.566458<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2Polluted<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>7801.823477<\/td>\n        <td>41.229256<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2022\/03\/range-counts\/range-count-time-streams.png\" alt=\"Results\" \/><\/p>\n\n<p>Inlining or no inlining, it\u2019s inefficient to use linear search if the transactions are sorted, which they are (by time).\nAs far as I\u2019m aware there\u2019s no way to communicate to the Stream API that a collection is already sorted and can be skipped over, and it doesn\u2019t know how to crack filter predicates to exploit this anyway.\nSo avoiding the Stream API and writing old fashioned Java code is effective.\nIn 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.\nAssume the timestamps are unique for simplicity\u2019s sake:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">int<\/span> <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">first<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> \n    <span class=\"kt\">int<\/span> <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"n\">last<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">last<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">count<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"nc\">Transaction<\/span> <span class=\"n\">transaction<\/span> <span class=\"o\">=<\/span> <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">price<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">count<\/span><span class=\"o\">++;<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3283.291345<\/td>\n        <td>44.566458<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2Polluted<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>7801.823477<\/td>\n        <td>41.229256<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>786.482285<\/td>\n        <td>1.994657<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Sorting is not included in the benchmark time, but data is often naturally sorted by time.\nIt\u2019s not a lot more code but goes a lot faster, and easier to measure because it\u2019s so simple.\nJMH\u2019s <code class=\"language-plaintext highlighter-rouge\">perfnorm<\/code> profiler shows that there is still a good number of branches missed, despite shedding about 90% of the work.\nThis means the instructions per cycle (IPC) is low:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>786.482285<\/td>\n        <td>1.994657<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:IPC<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0.755879<\/td>\n        <td>NaN<\/td>\n        <td>insns\/clk<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:branch-misses<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>50739.339319<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:branches<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>434330.541115<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>This can be fixed by complicating the filter in the scan somewhat.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">long<\/span> <span class=\"n\">count<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"nc\">Transaction<\/span> <span class=\"n\">transaction<\/span> <span class=\"o\">=<\/span> <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"n\">count<\/span> <span class=\"o\">+=<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"mi\">1<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">-<\/span> <span class=\"n\">qty<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">))<\/span> \n        <span class=\"o\">+<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"mi\">1<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">price<\/span> <span class=\"o\">-<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">)))<\/span> <span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>If the transaction\u2019s quantity is greater than the threshold, <code class=\"language-plaintext highlighter-rouge\">Math.max(transaction.quantity - threshold, 0)<\/code> will be positive, wrapping this in <code class=\"language-plaintext highlighter-rouge\">Math.min(1, x)<\/code> means that the count will be one if the predicate holds and zero if it doesn\u2019t.\nThis 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\u2019t.<\/p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">Math.max<\/code> and <code class=\"language-plaintext highlighter-rouge\">Math.min<\/code> are special and get compiled to conditional move instructions which aren\u2019t speculated so can\u2019t miss.\nThey are annotated as <code class=\"language-plaintext highlighter-rouge\">@IntrinsicCandidate<\/code>:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"nd\">@IntrinsicCandidate<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">max<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">a<\/span><span class=\"o\">,<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">b<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"o\">(<\/span><span class=\"n\">a<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">b<\/span><span class=\"o\">)<\/span> <span class=\"o\">?<\/span> <span class=\"n\">a<\/span> <span class=\"o\">:<\/span> <span class=\"n\">b<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@IntrinsicCandidate<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">min<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">a<\/span><span class=\"o\">,<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">b<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"o\">(<\/span><span class=\"n\">a<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">b<\/span><span class=\"o\">)<\/span> <span class=\"o\">?<\/span> <span class=\"n\">a<\/span> <span class=\"o\">:<\/span> <span class=\"n\">b<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.\nThey\u2019re also more readable, in my opinion.\nThe branch misses basically disappear, and the instructions per cycle is now much higher:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>786.482285<\/td>\n        <td>1.994657<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:IPC<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0.755879<\/td>\n        <td>NaN<\/td>\n        <td>insns\/clk<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:branch-misses<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>50739.339319<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch:branches<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>434330.541115<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>299.725900<\/td>\n        <td>2.414697<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan:IPC<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>3.278388<\/td>\n        <td>NaN<\/td>\n        <td>insns\/clk<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan:branch-misses<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>524.965594<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan:branches<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>403042.075820<\/td>\n        <td>NaN<\/td>\n        <td>#\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>However, I am fairly certain that no analytical database written in Java implements counting over predicates like this.\nThis 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.\nIt also won\u2019t 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.\nIf 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.\nSo this is probably an artificially good result, but it is the best so far.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3283.291345<\/td>\n        <td>44.566458<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2Polluted<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>7801.823477<\/td>\n        <td>41.229256<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>786.482285<\/td>\n        <td>1.994657<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>299.725900<\/td>\n        <td>2.414697<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2022\/03\/range-counts\/range-count-time-streams-binarySearch.png\" alt=\"Results\" \/><\/p>\n\n<p>In my last post I used <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> - which is the data structure used in <a href=\"http:\/\/github.com\/apache\/pinot\">Apache Pinot<\/a>\u2019s range index - to select transactions matching the predicate.\nCombined with binary search to apply the time range filter, it was much faster than any other implementation I measured.<\/p>\n\n<p>I have recently <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\/pull\/555\">implemented<\/a> the ability to count values matching a predicate, instead of producing a bitmap of their offsets.\nThe API is symmetric with the selection API:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kt\">long<\/span> <span class=\"nf\">index<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">qty<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">inTimeRange<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestampIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">between<\/span><span class=\"o\">(<\/span><span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">quantityIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">gte<\/span><span class=\"o\">(<\/span><span class=\"n\">qty<\/span><span class=\"o\">,<\/span> <span class=\"n\">inTimeRange<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">priceIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">lteCardinality<\/span><span class=\"o\">(<\/span><span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"n\">matchesQuantity<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n  \n  <span class=\"kt\">long<\/span> <span class=\"nf\">binarySearchThenIndex<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">qty<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">inTimeRange<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmapOfRange<\/span><span class=\"o\">(<\/span><span class=\"n\">first<\/span><span class=\"o\">,<\/span> <span class=\"n\">last<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">quantityIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">gte<\/span><span class=\"o\">(<\/span><span class=\"n\">qty<\/span><span class=\"o\">,<\/span> <span class=\"n\">inTimeRange<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">priceIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">lteCardinality<\/span><span class=\"o\">(<\/span><span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"n\">matchesQuantity<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> is likely to be suboptimal for counting because the data structure is optimised for producing a sorted output set, which isn\u2019t 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.\nFor selection, just using an index for all three attributes and combining the results was a lot faster than <code class=\"language-plaintext highlighter-rouge\">binarySearch<\/code> but for counting it\u2019s a little disappointing at the moment.\nI haven\u2019t tried to optimise the counting methods yet so there is probably a lot of low-hanging fruit.\nCutting the range down with the binary search first produces a good result, even better than the hard to modularise <code class=\"language-plaintext highlighter-rouge\">binarySearchBranchFreeScan<\/code>:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>9721.806835<\/td>\n        <td>27.935884<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3283.291345<\/td>\n        <td>44.566458<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2Polluted<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>7801.823477<\/td>\n        <td>41.229256<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>786.482285<\/td>\n        <td>1.994657<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchBranchFreeScan<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>299.725900<\/td>\n        <td>2.414697<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>index<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>666.951907<\/td>\n        <td>14.861610<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchThenIndex<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>78.191544<\/td>\n        <td>17.903472<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2022\/03\/range-counts\/range-count-time.png\" alt=\"Results\" \/><\/p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> was designed for use in <a href=\"http:\/\/github.com\/apache\/pinot\">Apache Pinot<\/a>, so is compressed and supports zero-copy mapping to and from disk.\nThere are more details about how it works in depth in <a href=\"https:\/\/richardstartin.github.io\/posts\/range-bitmap-index\">RangeBitmap - How range indexes work in Apache Pinot<\/a>.<\/p>\n\n<blockquote>\n  <p>The benchmark used in this post is not particularly scientific but is <a href=\"https:\/\/github.com\/richardstartin\/range-benchmark\/blob\/master\/src\/main\/java\/io\/github\/richardstartin\/range\/CountTransactionsBenchmark.java\">here<\/a>.\nIf you want to run it, you will get different numbers, but the rank of each benchmark score should not change.<\/p>\n<\/blockquote>","author":{"name":{}},"category":[{"@attributes":{"term":"java"}},{"@attributes":{"term":"roaring"}}],"summary":"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\u2019s a simpler problem so resources can be saved with a specialised solution."},{"title":"Evaluating Range Predicates","link":{"@attributes":{"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","updated":"2022-03-12T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/range-predicates","content":"<p>Suppose you are doing some kind of data analysis in Java, perhaps you are analysing transactions (as in sales made).\nYou have complex filters to evaluate before performing a calculation on <code class=\"language-plaintext highlighter-rouge\">Transaction<\/code> objects.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">Transaction<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"nf\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">price<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">=<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">getQuantity<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">quantity<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getPrice<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">price<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">public<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">getTimestamp<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">timestamp<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.\nThis 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:<\/p>\n\n<ol>\n  <li>You don\u2019t have huge data volumes but your management may have been convinced not to procure a proper database with a SQL interface<\/li>\n  <li>You may not have a database because you have a lot of data, and it\u2019s cheaper to store it in something like S3, you have processing jobs which filter the data programmatically<\/li>\n<\/ol>\n\n<p>For whatever reason, you don\u2019t have a database with a SQL interface and have to do the filtering yourself in a Java program, how might you do it?<\/p>\n\n<p>You might use the streams API:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">()<\/span>\n    <span class=\"o\">.<\/span><span class=\"na\">filter<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">price<\/span>\n                    <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">timestamp<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">end<\/span><span class=\"o\">)<\/span>\n            <span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">(<\/span><span class=\"k\">this<\/span><span class=\"o\">::<\/span><span class=\"n\">processTransaction<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This will inspect every transaction to check if it matches the constraints.\nFor 1M transactions, with parameters which select about 250 transactions, this takes about 10ms on my laptop:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Suppose the transactions are actually sorted by time, the <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code> conditions should be predictable but aren\u2019t evaluated first.\nThis means the unpredictable <code class=\"language-plaintext highlighter-rouge\">price<\/code> and <code class=\"language-plaintext highlighter-rouge\">quantity<\/code> conditions are evaluated for every transaction. \nReordering the conditions halves the runtime!<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3859.494033<\/td>\n        <td>42.012070<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>This is an interesting result, and JMH\u2019s <code class=\"language-plaintext highlighter-rouge\">perfnorm<\/code> profiler explains why:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream1:branches<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>4537456.732<\/td>\n        <td>\u00a0<\/td>\n        <td>\u00a0<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream1:branch-misses<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>505305.308<\/td>\n        <td>\u00a0<\/td>\n        <td>\u00a0<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3859.494033<\/td>\n        <td>42.012070<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2:branches<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>4648262.253<\/td>\n        <td>\u00a0<\/td>\n        <td>\u00a0<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2:branch-misses<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>51040.523<\/td>\n        <td>\u00a0<\/td>\n        <td>\u00a0<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>There are about the same number of branches in each case, but ~10x fewer branch misses.\nBranch misses are expensive!<\/p>\n\n<p>However, if the transactions are sorted by time, linear search will be wasteful unless the <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code> range covers most of data.\nIn my benchmark, it covers about 10% of the data, so binary searching for the first and last <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code>s in the range could correspond to up to ~10x improvement.\nAssume the timestamps are unique for simplicity\u2019s sake:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">int<\/span> <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">first<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> \n    <span class=\"kt\">int<\/span> <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"n\">last<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">last<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"nc\">Transaction<\/span> <span class=\"n\">transaction<\/span> <span class=\"o\">=<\/span> <span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">quantity<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">qty<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">price<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">price<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3859.494033<\/td>\n        <td>42.012070<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1204.366198<\/td>\n        <td>8.455373<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Sorting to ensure this is a valid application of binary search is not included in the benchmark time.<\/p>\n\n<p>If several filters need to be evaluated against the collection, it would make sense to perform the sort to benefit from this.<\/p>\n\n<p>This particular problem - applying range predicates to unsorted data - is solved by the <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> data structure in the <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\">RoaringBitmap<\/a> library.\nThe 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.\nThe data structure has a build-then-use lifecycle:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">long<\/span> <span class=\"n\">minTimestamp<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">maxTimestamp<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">minPrice<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">maxPrice<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">minQty<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">maxQty<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Transaction<\/span> <span class=\"n\">transaction<\/span> <span class=\"o\">:<\/span> <span class=\"n\">transactions<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">minTimestamp<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"n\">minTimestamp<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getTimestamp<\/span><span class=\"o\">());<\/span>\n        <span class=\"n\">maxTimestamp<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">maxTimestamp<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getTimestamp<\/span><span class=\"o\">());<\/span>\n        <span class=\"n\">minPrice<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"n\">minPrice<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getPrice<\/span><span class=\"o\">());<\/span>\n        <span class=\"n\">maxPrice<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">maxPrice<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getPrice<\/span><span class=\"o\">());<\/span>\n        <span class=\"n\">minQty<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"n\">minQty<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getQuantity<\/span><span class=\"o\">());<\/span>\n        <span class=\"n\">maxQty<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">maxQty<\/span><span class=\"o\">,<\/span> <span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getQuantity<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">timestampAppender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">maxTimestamp<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minTimestamp<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">priceAppender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">maxPrice<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minPrice<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">qtyAppender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">maxQty<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Transaction<\/span> <span class=\"n\">transaction<\/span> <span class=\"o\">:<\/span> <span class=\"n\">transactions<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">timestampAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getTimestamp<\/span><span class=\"o\">()<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minTimestamp<\/span><span class=\"o\">);<\/span>\n        <span class=\"n\">priceAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getPrice<\/span><span class=\"o\">()<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minPrice<\/span><span class=\"o\">);<\/span>\n        <span class=\"n\">qtyAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">transaction<\/span><span class=\"o\">.<\/span><span class=\"na\">getQuantity<\/span><span class=\"o\">()<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">timestampIndex<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestampAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">build<\/span><span class=\"o\">();<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">priceIndex<\/span> <span class=\"o\">=<\/span> <span class=\"n\">priceAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">build<\/span><span class=\"o\">();<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">qtyIndex<\/span> <span class=\"o\">=<\/span> <span class=\"n\">qtyAppender<\/span><span class=\"o\">.<\/span><span class=\"na\">build<\/span><span class=\"o\">();<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.<\/p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> produces a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> of the indexes which satisfy a predicate, and can take <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> parameters as inputs to skip over rows already filtered out.\nThe Streams API code used before is translated into <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> API calls:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">inTimeRange<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timestampIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">between<\/span><span class=\"o\">(<\/span><span class=\"n\">minTimeThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minTime<\/span><span class=\"o\">,<\/span> <span class=\"n\">maxTimeThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minTime<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">qtyIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">gte<\/span><span class=\"o\">(<\/span><span class=\"n\">minQtyThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">,<\/span> <span class=\"n\">inTimeRange<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesPrice<\/span> <span class=\"o\">=<\/span> <span class=\"n\">priceIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">lte<\/span><span class=\"o\">(<\/span><span class=\"n\">maxPriceThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minPrice<\/span><span class=\"o\">,<\/span> <span class=\"n\">matchesQuantity<\/span><span class=\"o\">);<\/span>\n    <span class=\"n\">matchesPrice<\/span><span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">((<\/span><span class=\"nc\">IntConsumer<\/span><span class=\"o\">)<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">processTransaction<\/span><span class=\"o\">(<\/span><span class=\"n\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">)));<\/span>\n  <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.\nFor the same data this is ~2x faster than the binary search approach:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3859.494033<\/td>\n        <td>42.012070<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1204.366198<\/td>\n        <td>8.455373<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>index<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>690.562119<\/td>\n        <td>25.725005<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.<\/p>\n\n<p>Binary search is much faster than the algorithm <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> uses, and it\u2019s a shame it can only be used on one attribute since it\u2019s not possible to do a global sort on more than one attribute.\nIf the data is sorted by <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code>, a hybrid approach can be taken:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">int<\/span> <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">first<\/span> <span class=\"o\">=<\/span> <span class=\"n\">first<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">first<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">first<\/span><span class=\"o\">;<\/span> \n    <span class=\"kt\">int<\/span> <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Collections<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">,<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">Transaction<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">end<\/span><span class=\"o\">),<\/span>\n            <span class=\"nc\">Comparator<\/span><span class=\"o\">.<\/span><span class=\"na\">comparingLong<\/span><span class=\"o\">(<\/span><span class=\"nl\">Transaction:<\/span><span class=\"o\">:<\/span><span class=\"n\">getTimestamp<\/span><span class=\"o\">));<\/span>\n    <span class=\"n\">last<\/span> <span class=\"o\">=<\/span> <span class=\"n\">last<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"o\">-<\/span><span class=\"n\">last<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">:<\/span> <span class=\"n\">last<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">inTimeRange<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmapOfRange<\/span><span class=\"o\">(<\/span><span class=\"n\">first<\/span><span class=\"o\">,<\/span> <span class=\"n\">last<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesQuantity<\/span> <span class=\"o\">=<\/span> <span class=\"n\">qtyIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">gte<\/span><span class=\"o\">(<\/span><span class=\"n\">maxPriceThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minPrice<\/span><span class=\"o\">,<\/span> <span class=\"n\">inTimeRange<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">matchesPrice<\/span> <span class=\"o\">=<\/span> <span class=\"n\">priceIndex<\/span><span class=\"o\">.<\/span><span class=\"na\">lte<\/span><span class=\"o\">(<\/span><span class=\"n\">minQtyThreshold<\/span> <span class=\"o\">-<\/span> <span class=\"n\">minQty<\/span><span class=\"o\">,<\/span> <span class=\"n\">matchesQuantity<\/span><span class=\"o\">);<\/span>\n    <span class=\"n\">matchesPrice<\/span><span class=\"o\">.<\/span><span class=\"na\">forEach<\/span><span class=\"o\">((<\/span><span class=\"nc\">IntConsumer<\/span><span class=\"o\">)<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">processTransaction<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">transactions<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">)));<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This is over 100x faster than the original Streams API code and takes under 100us, all else being equal.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: minPrice<\/th>\n        <th>Param: minQuantity<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>stream1<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8885.846120<\/td>\n        <td>41.192708<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>stream2<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3859.494033<\/td>\n        <td>42.012070<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearch<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1204.366198<\/td>\n        <td>8.455373<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>index<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>690.562119<\/td>\n        <td>25.725005<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n      <tr>\n        <td>binarySearchThenIndex<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>82.893010<\/td>\n        <td>12.486953<\/td>\n        <td>us\/op<\/td>\n        <td>100<\/td>\n        <td>1<\/td>\n        <td>1000000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2022\/03\/range-predicates\/range-filter-time.png\" alt=\"Results\" \/><\/p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> was designed to power the range indexes in <a href=\"http:\/\/github.com\/apache\/pinot\/pulls\">Apache Pinot<\/a>, so is compressed and supports zero-copy mapping to and from disk.\nThere are more details about how it works in depth in <a href=\"https:\/\/richardstartin.github.io\/posts\/range-bitmap-index\">RangeBitmap - How range indexes work in Apache Pinot<\/a>.<\/p>\n\n<p>It\u2019s likely you\u2019re sensible and pushing your filters down to your database to evaluate, but this data structure may be useful in some applications.<\/p>\n\n<blockquote>\n  <p>The benchmark used in this post is not particularly scientific but is <a href=\"https:\/\/github.com\/richardstartin\/range-benchmark\/blob\/master\/src\/main\/java\/io\/github\/richardstartin\/range\/FindTransactionsBenchmark.java\">here<\/a>.\nIf you want to run it, you will get different numbers, but the rank of each benchmark score should not change.<\/p>\n<\/blockquote>","author":{"name":{}},"category":[{"@attributes":{"term":"java"}},{"@attributes":{"term":"roaring"}}],"summary":"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."},{"title":"RangeBitmap - How range indexes work in Apache Pinot","link":{"@attributes":{"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","updated":"2022-03-07T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/range-bitmap-index","content":"<p>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.\nThe range predicate will be evaluated many times, so any time spent preprocessing will be amortised, and non-zero spatial overhead is expected.\nIf the data were sorted, this would be very easy, but the indexes of the values have meaning so the data cannot be sorted.\nTo complicate the problem slightly, the set of indexes must be produced in sorted order.<\/p>\n\n<p>These are the requirements for a range index in a column store, where the row indexes implicitly link values stored separately in different columns.\nThe 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):<\/p>\n\n<div class=\"language-sql highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">select<\/span> \n  <span class=\"n\">traceId<\/span><span class=\"p\">,<\/span> \n  <span class=\"n\">spanId<\/span><span class=\"p\">,<\/span> \n  <span class=\"n\">operationName<\/span> \n<span class=\"k\">from<\/span> \n  <span class=\"n\">spans<\/span> \n<span class=\"k\">where<\/span> \n  <span class=\"n\">duration<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">10000000<\/span> \n  <span class=\"k\">and<\/span> <span class=\"nb\">timestamp<\/span> <span class=\"k\">between<\/span> <span class=\"s1\">'2022-03-06 02:00:00.000'<\/span> \n  <span class=\"k\">and<\/span> <span class=\"s1\">'2022-03-06 03:00:00.000'<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>How can the spans be indexed to make this query as fast as it can be?\nDifferent databases solve this problem in different ways, but the context of this post is a column store like <a href=\"https:\/\/github.com\/apache\/pinot\">Apache Pinot<\/a>.<\/p>\n\n<p>It\u2019s unusual for there to be only one important class of query, so other constraints need to be taken into account.\nThe query above for slow spans isn\u2019t 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:<\/p>\n\n<div class=\"language-sql highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">select<\/span>  \n  <span class=\"n\">serviceName<\/span><span class=\"p\">,<\/span>\n  <span class=\"n\">operationName<\/span><span class=\"p\">,<\/span>\n  <span class=\"k\">count<\/span><span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"p\">),<\/span>\n  <span class=\"k\">sum<\/span><span class=\"p\">(<\/span><span class=\"n\">duration<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">from<\/span> \n  <span class=\"n\">spans<\/span> \n<span class=\"k\">where<\/span> \n  <span class=\"n\">traceId<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">1234578910<\/span>\n<span class=\"k\">group<\/span> <span class=\"k\">by<\/span> \n  <span class=\"n\">serviceName<\/span><span class=\"p\">,<\/span>\n  <span class=\"n\">operationName<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Apache Pinot tables consist of <em>segments<\/em> 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.\nSegments have a columnar layout, and contain their indexes, metadata, and other data structures. \nSegments 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.<\/p>\n\n<p>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.\n<em>Servers<\/em> 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.\n<em>Brokers<\/em> are responsible for routing queries to servers and merging server-level results, they can perform some amount of segment pruning to protect the servers\u2019 resources.<\/p>\n\n<p>The second query needing to be fast imposes constraints on what can be done to optimise the first query.\nFirstly, brokers can use knowledge of <a href=\"https:\/\/docs.pinot.apache.org\/operators\/operating-pinot\/tuning\/routing#partitioning\">segment partitioning<\/a> to optimise query routing.\nIf we need the second query to be fast, we should partition on <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> which limits the number of servers the broker need to route the query to.\nUnless the trace\u2019s 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 <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> should mean only a single segment need be queried.\nThis 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.<\/p>\n\n<p>The second query will also need an index on <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> to be efficient because a typical segment will contain millions of rows, and scanning all these rows will be too slow.\nChoosing an appropriate index requires some knowledge of the data (though this decision can be automated given representative sample data).\nIn case you are unfamiliar with tracing, <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> and <code class=\"language-plaintext highlighter-rouge\">spanId<\/code> are high cardinality attributes and there is a hierarchical relationship between spans and traces: a trace is a collection of spans.\nWe should expect that traces last at most minutes and consist of fewer than 1000 spans in the typical case.<\/p>\n\n<p>Pinot has <a href=\"https:\/\/www.startree.ai\/blogs\/what-makes-apache-pinot-fast-chapter-ii\">several types of index<\/a>, but the choice here would be between a <a href=\"https:\/\/docs.pinot.apache.org\/basics\/indexing\/forward-index#sorted-forward-index-with-run-length-encoding\">sorted index<\/a> and an <a href=\"https:\/\/docs.pinot.apache.org\/basics\/indexing\/inverted-index\">inverted index<\/a>.\nSorted indexes offer the best space\/time tradeoff of Pinot\u2019s indexes but, unsurprisingly, a sorted index can only be applied to a sorted column, and only one column can be sorted.\nThis makes the choice of sorted column one of the biggest decisions when designing a Pinot table.<\/p>\n\n<p>Given the hierarchical relationship between <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> and <code class=\"language-plaintext highlighter-rouge\">spanId<\/code>, and an expectation that most traces will contain far fewer than 1000 spans, sorting on <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> indexes <code class=\"language-plaintext highlighter-rouge\">spanId<\/code> for free because a scan for <code class=\"language-plaintext highlighter-rouge\">spanId<\/code> would inspect only spans within the same trace.\nCreating an inverted index on <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> 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.\nMoreover, the performance of the aggregation in the second query depends on locality, which sorting improves.\nIn this case, the case for sorting by <code class=\"language-plaintext highlighter-rouge\">traceId<\/code> probably trumps sorting by <code class=\"language-plaintext highlighter-rouge\">duration<\/code> or <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code>.<\/p>\n\n<p>Fortunately, the <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code> filter can be used to prune segments if it is configured as the <a href=\"https:\/\/docs.pinot.apache.org\/integrations\/superset#configuring-time-column\">time column<\/a>.\nSegments 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.\nThe filter <code class=\"language-plaintext highlighter-rouge\">timestamp between '2022-03-06 02:00:00.000' and '2022-03-06 03:00:00.000'<\/code> narrows the segments to query down to those with boundary values which intersect with the hour specified by the query.\nAssuming 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 <em>ever<\/em> to at most twice the number of partitions.\nHowever, 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\u2019t sorted by <code class=\"language-plaintext highlighter-rouge\">timestamp<\/code>.<\/p>\n\n<p>All of this is enough to justify the existence of a range index in a system like Pinot.\nThis post is about the design and implementation of <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, a data structure which powers Pinot\u2019s new range indexes.<\/p>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#implementing-a-range-index\" id=\"markdown-toc-implementing-a-range-index\">Implementing a Range Index<\/a><\/li>\n  <li><a href=\"#bit-slicing\" id=\"markdown-toc-bit-slicing\">Bit slicing<\/a>    <ol>\n      <li><a href=\"#generalisation-to-higher-bases\" id=\"markdown-toc-generalisation-to-higher-bases\">Generalisation to higher bases<\/a><\/li>\n      <li><a href=\"#example\" id=\"markdown-toc-example\">Example<\/a>        <ol>\n          <li><a href=\"#initialise-state\" id=\"markdown-toc-initialise-state\">Initialise state<\/a><\/li>\n          <li><a href=\"#union-with-slice-0\" id=\"markdown-toc-union-with-slice-0\">Union with slice 0<\/a><\/li>\n          <li><a href=\"#intersection-with-slice-1\" id=\"markdown-toc-intersection-with-slice-1\">Intersection with slice 1<\/a><\/li>\n          <li><a href=\"#intersection-with-slice-2\" id=\"markdown-toc-intersection-with-slice-2\">Intersection with slice 2<\/a><\/li>\n          <li><a href=\"#union-with-slice-3-and-terminate\" id=\"markdown-toc-union-with-slice-3-and-terminate\">Union with slice 3 and terminate<\/a><\/li>\n        <\/ol>\n      <\/li>\n      <li><a href=\"#optimisations\" id=\"markdown-toc-optimisations\">Optimisations<\/a><\/li>\n      <li><a href=\"#horizontal-and-vertical-evaluations\" id=\"markdown-toc-horizontal-and-vertical-evaluations\">Horizontal and Vertical Evaluations<\/a>        <ol>\n          <li><a href=\"#or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1\" id=\"markdown-toc-or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1\">OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 0, 1<\/a><\/li>\n          <li><a href=\"#or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3\" id=\"markdown-toc-or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3\">OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 2, 3<\/a><\/li>\n        <\/ol>\n      <\/li>\n    <\/ol>\n  <\/li>\n  <li><a href=\"#rangebitmap-design\" id=\"markdown-toc-rangebitmap-design\">RangeBitmap design<\/a>    <ol>\n      <li><a href=\"#layout\" id=\"markdown-toc-layout\">Layout<\/a><\/li>\n      <li><a href=\"#construction-and-memory-mapping\" id=\"markdown-toc-construction-and-memory-mapping\">Construction and Memory Mapping<\/a><\/li>\n      <li><a href=\"#encoding\" id=\"markdown-toc-encoding\">Encoding<\/a><\/li>\n      <li><a href=\"#querying-a-rangebitmap\" id=\"markdown-toc-querying-a-rangebitmap\">Querying a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code><\/a><\/li>\n      <li><a href=\"#performance-evaluation\" id=\"markdown-toc-performance-evaluation\">Performance Evaluation<\/a><\/li>\n    <\/ol>\n  <\/li>\n  <li><a href=\"#usage-in-apache-pinot\" id=\"markdown-toc-usage-in-apache-pinot\">Usage in Apache Pinot<\/a><\/li>\n  <li><a href=\"#links\" id=\"markdown-toc-links\">Links:<\/a><\/li>\n<\/ol>\n\n<h2 id=\"implementing-a-range-index\">Implementing a Range Index<\/h2>\n\n<p>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:<\/p>\n\n<p>$[10, 3, 15, 0, 0, 1, 5, 6, 2, 1, 12, 14, 3, 9, 11]$<\/p>\n\n<p>We want to get the row indexes of the values which satisfy predicates so attributes at the same indexes can be selected.\nWe also want the sets to be sorted so scans over other columns are sequential which is more efficient than traversing in random order.\nFrom the list above we would want the following outputs for each of the predicates below:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>predicate<\/th>\n        <th>expected output<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>$x &lt; 3$<\/td>\n        <td>$ \\{3, 4, 5, 8, 9 \\} $<\/td>\n      <\/tr>\n      <tr>\n        <td>$x &lt; 10$<\/td>\n        <td>$ \\{ 1, 3, 4, 5, 6, 7, 8, 9, 12, 13 \\}$<\/td>\n      <\/tr>\n      <tr>\n        <td>$x &gt; 5$<\/td>\n        <td>$ \\{0, 2, 7, 10, 11, 13, 14 \\}$<\/td>\n      <\/tr>\n      <tr>\n        <td>$2 &lt; x &lt; 10$<\/td>\n        <td>$ \\{ 1, 6, 7, 12, 13 \\}$<\/td>\n      <\/tr>\n      <tr>\n        <td>$5 &lt; x &lt; 10$<\/td>\n        <td>$ \\{ 7, 13 \\}$<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Or represented as bitmaps which can be iterated over we would have the following:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>$&lt; 3$<\/th>\n        <th>$&lt; 10$<\/th>\n        <th>$&gt; 5$<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.\nThe 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:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>$&lt; 10$<\/th>\n        <th>$&gt; 5$<\/th>\n        <th>$5 &lt; x &lt; 10$<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0 &amp; 1 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0 &amp; 1 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1 &amp; 1 = 1<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0 &amp; 1 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0 &amp; 1 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1 &amp; 0 = 0<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1 &amp; 1 = 1<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0 &amp; 1 = 0<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Pinot\u2019s indexes use <a href=\"http:\/\/roaringbitmap.org\">RoaringBitmap<\/a> to represent filters, so the range index will produce a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> too.<\/p>\n\n<h2 id=\"bit-slicing\">Bit slicing<\/h2>\n\n<p>The algorithm used to do range evaluations is described in the 1998 paper <a href=\"https:\/\/www.comp.nus.edu.sg\/~chancy\/sigmod98.pdf\">Bitmap Index Design and Evaluation<\/a>.\nThis algorithm has been used at least twice before: the paper mentions that SybaseIQ used it in the 90s, and <a href=\"https:\/\/www.pilosa.com\/blog\/range-encoded-bitmaps\/\">pilosa<\/a> also implemented it in the last decade. \nTo 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.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>bit 3<\/th>\n        <th>bit 2<\/th>\n        <th>bit 1<\/th>\n        <th>bit 0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>The columns can be used to evaluate filters.\nFor instance, the numbers greater than seven all have a bit in the leftmost column.\nLooking at the leftmost column would be enough to evaluate $x &gt; 7$ but not $x &gt; 8$ which also requires at least one bit in the three columns to the right.\nThis gets coarser as the number of bits in the numbers increases so trying to post-filter wouldn\u2019t scale well.<\/p>\n\n<p>To arrive at an algorithm, each column can be <em>range encoded<\/em> so that there are two <em>slices<\/em> 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\\}$.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>predicate<\/th>\n        <th>bit 3<\/th>\n        <th>bit 2<\/th>\n        <th>bit 1<\/th>\n        <th>bit 0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>$\\leq 0$<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>\u00a0<\/td>\n        <td>$\\leq 1$<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>bit 3<\/th>\n        <th>bit 2<\/th>\n        <th>bit 1<\/th>\n        <th>bit 0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>This mapping is easy to perform because it\u2019s just the logical complement of the input values masked by how many bits we care about, so <code class=\"language-plaintext highlighter-rouge\">x -&gt; ~x &amp; 0xF<\/code> in this case.<\/p>\n\n<p>Now there is a bitset in each slice:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>0<\/th>\n        <th>1<\/th>\n        <th>2<\/th>\n        <th>3<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td><code class=\"language-plaintext highlighter-rouge\">100110011011000<\/code><\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">000111100110010<\/code><\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">110111001100111<\/code><\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">010111111100100<\/code><\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.<\/p>\n\n<p>A predicate $x \\leq t$ can be evaluated against the slices, assuming $n$ rows were indexed as follows:<\/p>\n\n<ol>\n  <li>Assume all rows match, initialise bitmap <code class=\"language-plaintext highlighter-rouge\">state<\/code> to $[0, n)$, that is set every bit between 0 and $n$.<\/li>\n  <li>for each bit in <code class=\"language-plaintext highlighter-rouge\">t<\/code>:\n    <ol>\n      <li>if <code class=\"language-plaintext highlighter-rouge\">t[i]<\/code> is set, set <code class=\"language-plaintext highlighter-rouge\">state<\/code> = <code class=\"language-plaintext highlighter-rouge\">state | slices[i]<\/code>\n        <ol>\n          <li>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<\/li>\n        <\/ol>\n      <\/li>\n      <li>if <code class=\"language-plaintext highlighter-rouge\">t[i]<\/code> is not set, set <code class=\"language-plaintext highlighter-rouge\">state<\/code> = <code class=\"language-plaintext highlighter-rouge\">state &amp; slices[i]<\/code>\n        <ol>\n          <li>Bits absent in the slice need to be removed because it means the bit was present<\/li>\n        <\/ol>\n      <\/li>\n    <\/ol>\n  <\/li>\n<\/ol>\n\n<p>To evaluate an $x &gt; t$ predicate, just take the logical complement of the result of an $x \\leq t$ predicate.\nPredicates $x &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 &gt; t$ predicate respectively.\nA double ended range $t \\leq x \\leq u$ can be evaluated by intersecting $x &gt; t$ and $x \\leq u$.<\/p>\n\n<p>This algorithm has strengths and weaknesses. \nFirstly, it should be clear it can\u2019t compete with the potentialities of sorting the data: binary search over run-length encoded values or even the raw data has lower time complexity.\nThe 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.\nHowever, logical operations between bitmaps are very efficient and can be vectorised, so hundreds of rows can be evaluated in a handful of CPU instructions.\nSpace 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.\nThe various tree data structures which could also solve this problem can\u2019t 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.<\/p>\n\n<h3 id=\"generalisation-to-higher-bases\">Generalisation to higher bases<\/h3>\n\n<p>Any number can be expressed as the coefficients of a polynomial with base $b$, binary numbers just have coefficients 0 or 1.\nOther bases can be chosen, which create space\/time tradeoffs best visualised by considering the representations of numbers in different bases:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>base<\/th>\n        <th>representation<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>2<\/td>\n        <td>111010110111100110100010101<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>22121022020212200<\/td>\n      <\/tr>\n      <tr>\n        <td>4<\/td>\n        <td>13112330310111<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>726746425<\/td>\n      <\/tr>\n      <tr>\n        <td>10<\/td>\n        <td>123456789<\/td>\n      <\/tr>\n      <tr>\n        <td>16<\/td>\n        <td>75bcd15<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.\nAt the other end of the spectrum, in base-16 there are only 7 digits but 15 slices per digits.\nReducing the number of digits reduces the number of bitmap operations which need to be performed, but increase spatial overhead and write amplification.\n<a href=\"https:\/\/www.comp.nus.edu.sg\/~chancy\/sigmod98.pdf\">Bitmap Index Design and Evaluation<\/a> explores this tradeoff (see <a href=\"https:\/\/github.com\/richardstartin\/range-index\">here<\/a> for implementations).<\/p>\n\n<h3 id=\"example\">Example<\/h3>\n\n<p>This is an example of how to evaluate the predicate $x \\leq 9$ or $x &lt; 10$ against a 4-bit base-2 index. <br \/>\nThere is a table for each step of the algorithm:<\/p>\n<ul>\n  <li>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.<\/li>\n  <li>The next column represents the state bitmap, which changes on each step of the algorithm (and if it doesn\u2019t, this step should be eliminated if it can be detected).<\/li>\n  <li>The final two columns represent the <em>target<\/em>, the bitmap we know we want to have produced once the algorithm has terminated, and whether the state bitmap is currently accurate.\n    <ul>\n      <li>The far right column should give the intuition that the algorithm doesn\u2019t converge to a solution and randomly flips bits depending on what\u2019s in the slice. \nIn this example, the hamming distance between the state and the solution only increases until the last step.<\/li>\n    <\/ul>\n  <\/li>\n  <li>The bold column is the slice being operated on.<\/li>\n<\/ul>\n\n<p>The threshold 9 in binary is <code class=\"language-plaintext highlighter-rouge\">1001<\/code>, so the first and last slices will be united, and the middle slices will be intersected.<\/p>\n\n<h4 id=\"initialise-state\">Initialise state<\/h4>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<h4 id=\"union-with-slice-0\">Union with slice 0<\/h4>\n\n<p>Perform a destructive logical union between the first slice and the state, modifying the state bitmap.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>This step didn\u2019t actually change the state bitmap, and could have been eliminated by checking if the LSB is set.<\/p>\n\n<h4 id=\"intersection-with-slice-1\">Intersection with slice 1<\/h4>\n\n<p>Perform a destructive logical intersection between the second slice and the state bitmap, modifying the state bitmap.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<h4 id=\"intersection-with-slice-2\">Intersection with slice 2<\/h4>\n\n<p>Perform a destructive logical intersection between the third slice and the state bitmap, modifying the state bitmap.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td>1<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td>0<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>\u274c<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<h4 id=\"union-with-slice-3-and-terminate\">Union with slice 3 and terminate<\/h4>\n\n<p>Perform a destructive logical union between the fourth slice and the state bitmap, modifying the state bitmap.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>5<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>6<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>2<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>12<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>14<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>9<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>There are no more slices so the algorithm terminates here.<\/p>\n\n<h3 id=\"optimisations\">Optimisations<\/h3>\n\n<p>The algorithm\u2019s time complexity is super-linear, depending on both the number of indexed rows and how many slices are present in the indexed values.\nThis means that every slice which doesn\u2019t need to be operated on (the first step in the example above was redundant) should be eliminated.<\/p>\n\n<p>There are various fast-forwarding optimisations which eliminate bitmap operations between slices at the start of the evaluation.\nSome of these depend on the threshold:<\/p>\n\n<ul>\n  <li>Evaluating the result for the first slice has redundancy: if the LSB is set in the threshold, the first union is redundant. \nSimilarly, if it isn\u2019t set, the <code class=\"language-plaintext highlighter-rouge\">state<\/code> bitmap can be initialised to the values of the first slice; initialising to <code class=\"language-plaintext highlighter-rouge\">[0, max)<\/code> beforehand is redundant.<\/li>\n  <li>If there is a run of $k$ set bits starting from 0, all $k$ operations can be eliminated.<\/li>\n<\/ul>\n\n<p>Others depend on the data:<\/p>\n<ul>\n  <li>If there is an empty slice at position $k$ and the bit $k$ is absent from the threshold $x$, the <code class=\"language-plaintext highlighter-rouge\">state<\/code> bitmap will be empty afterwards.\nAll $k$ operations can be replaced by just setting <code class=\"language-plaintext highlighter-rouge\">state<\/code> to $\\emptyset$.<\/li>\n  <li>If there is a full slice at position $k$ and the bit $k$ is present in the threshold $x$, the <code class=\"language-plaintext highlighter-rouge\">state<\/code> bitmap will be full afterwards, so all $k$ operations can be replaced by setting <code class=\"language-plaintext highlighter-rouge\">state<\/code> to $[0, max)$<\/li>\n<\/ul>\n\n<p>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.\nHowever, allocating capacity for values much larger than the largest indexed value leads to unnecessary slices and slower evaluations.\nKnowing the maximum value reduces the number of slices which need to be operated on.<\/p>\n\n<p>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})$.\nThis is especially important for indexing timestamps, where the minimum timestamp in a data set will often have a large offset from 1970-01-01.\nAt 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]$.\nThe 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.<\/p>\n\n<h3 id=\"horizontal-and-vertical-evaluations\">Horizontal and Vertical Evaluations<\/h3>\n\n<p>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).<\/p>\n\n<p>In the step by step example of the algorithm above, the evaluation was vertical.\nInstead, let\u2019s 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.\nThe evaluation would proceed as follows:<\/p>\n\n<h4 id=\"or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-0-1\">OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 0, 1<\/h4>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td><strong>10<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>3<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>15<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>-<\/td>\n        <td>0<\/td>\n        <td>\u2753<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2753\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2753\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2753\ufe0f<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<h4 id=\"or-slice-0-and-slice-1-and-slice-2-or-slice-3-append-result-for-rows-2-3\">OR slice 0, AND slice 1, AND slice 2, OR slice 3, append result for rows 2, 3<\/h4>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>value<\/th>\n        <th>slice 3<\/th>\n        <th>slice 2<\/th>\n        <th>slice 1<\/th>\n        <th>slice 0<\/th>\n        <th>state<\/th>\n        <th>target<\/th>\n        <th>\u00a0<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>10<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>-<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>3<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>15<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>0<\/strong><\/td>\n        <td>0<\/td>\n        <td>0<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td><strong>0<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td><strong>1<\/strong><\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>\u2714\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>0<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2753\ufe0f<\/td>\n      <\/tr>\n      <tr>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>1<\/td>\n        <td>0<\/td>\n        <td>-<\/td>\n        <td>1<\/td>\n        <td>\u2753\ufe0f<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>In reality, hundreds or thousands of bits would be operated on at a time; two was just chosen to make it possible to visualise.<\/p>\n\n<p>There are pros and cons either way.\nOwen Kaser and Daniel Lemire describe the trade-offs in <a href=\"https:\/\/arxiv.org\/pdf\/1402.4073.pdf\">Threshold and Symmetric Functions over Bitmaps<\/a>:<\/p>\n\n<blockquote>\n  <p>\u201cImplementations of bitmap operations can be described as \u201chorizontal\u201d or \u201cvertical\u201d. \nThe former implementation produces its output incrementally, and can be stopped early. \nThe latter implementation produces the entire completed bitmap. \nHorizontal 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. \nAs well, the producer and consumer can run in parallel. Vertical implementations may be simpler and have less overhead. \nIf 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.\nWith uncompressed bitmaps, a particularly horizontal implementation exists: a word of each input is read, after which one word of the output is produced.\u201d<\/p>\n<\/blockquote>\n\n<p>There are good reasons to prefer a horizontal approach beyond the ability to iterate over the result before it has been computed.<\/p>\n<ul>\n  <li><strong>Cache efficiency<\/strong>: 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.<\/li>\n  <li><strong>Density<\/strong>: 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.\nThis means that the ability of compressed bitmaps like RoaringBitmap (see <a href=\"http:\/\/roaringbitmap.org\">here<\/a> or <a href=\"\/posts\/roaringbitmap-performance-tricks\">here<\/a>) to exploit sparseness to prune intersections will be unreliable. \nIf there are regions where some slices are sparse, so long as fast-forward optimisations are implemented, some work can be pruned anyway.<\/li>\n<\/ul>\n\n<p>My benchmarks in the <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> library showed that <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> usually outperforms a vertical implementation using <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> by more than 2x.<\/p>\n\n<h2 id=\"rangebitmap-design\">RangeBitmap design<\/h2>\n\n<p><a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\/blob\/master\/RoaringBitmap\/src\/main\/java\/org\/roaringbitmap\/RangeBitmap.java\"><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code><\/a> in the <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\">RoaringBitmap Java library<\/a> implements this algorithm.\nThe data structure reuses some of RoaringBitmap\u2019s building blocks, so here is a quick recap.\n<code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> 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.\nThere are different kinds of <code class=\"language-plaintext highlighter-rouge\">Container<\/code> for storing the lower 16 bits, depending on the distribution of values within the bucket.\nIf there are fewer than $2^{12}$ values in the bucket, an <code class=\"language-plaintext highlighter-rouge\">ArrayContainer<\/code> - just a sorted array of 16 bit values - is used to store the values. \nOtherwise a <code class=\"language-plaintext highlighter-rouge\">BitmapContainer<\/code> - an 8KB bitset - is used instead.\nHowever, if the values can be run-length encoded into fewer than $2^{11}$ runs, a <code class=\"language-plaintext highlighter-rouge\">RunContainer<\/code> 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. \nThe array is sorted by the starting positions of the runs.<\/p>\n\n<h3 id=\"layout\">Layout<\/h3>\n\n<p><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> uses <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code>\u2019s container implementations to represent the slices, but does away with prefix compression.\nThis is based on a few heuristics which appear to hold for real-world datasets:<\/p>\n\n<ol>\n  <li>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).<\/li>\n  <li>Unless the values are entirely random, higher order slices will be very dense or very sparse as values will tend to cluster.<\/li>\n<\/ol>\n\n<p>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.\nIf every $2^{16}$ range is partially populated, the prefix can be represented implicitly by the container\u2019s position.\nA <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> of size $n$ has $ \\lceil \\frac{n}{2^{16}} \\rceil $ 16 bit values to store integer prefixes.\nWe should expect <em>at least<\/em> 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.\nGetting 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.<\/p>\n\n<p>For each $2^{16}$ range, there is a mask which has the <code class=\"language-plaintext highlighter-rouge\">n<\/code>th bit set if slice <code class=\"language-plaintext highlighter-rouge\">n<\/code> is non-empty.\nThese 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.\nThe 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).<\/p>\n\n<p>After the masks, <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> containers are stored in ascending slice order.\nIf 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.<\/p>\n\n<p>There is a small header before the masks which helps with memory-mapping:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>field<\/th>\n        <th>size (bytes)<\/th>\n        <th>purpose<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>cookie<\/td>\n        <td>2<\/td>\n        <td>allows checks whether a supplied buffer is not a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, as well as evolution<\/td>\n      <\/tr>\n      <tr>\n        <td>base<\/td>\n        <td>1<\/td>\n        <td>this allows evolution to e.g. base 4 or base 8 slicing<\/td>\n      <\/tr>\n      <tr>\n        <td>slice count<\/td>\n        <td>1<\/td>\n        <td>determines how many slices exist, which allows the size of the mask to<\/td>\n      <\/tr>\n      <tr>\n        <td>number of masks<\/td>\n        <td>2<\/td>\n        <td>this is actually redundant and could have been derived from the next field<\/td>\n      <\/tr>\n      <tr>\n        <td>max row<\/td>\n        <td>4<\/td>\n        <td>the index of the last value, e.g. 10M if 10M values are indexed<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<h3 id=\"construction-and-memory-mapping\">Construction and Memory Mapping<\/h3>\n\n<p>A <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> is immutable, and there are good reasons for this:<\/p>\n\n<ol>\n  <li>Slice pruning optimisations work better when the range of values is known ahead of time.<\/li>\n  <li>There\u2019s not much point in supporting mutability without supporting concurrent updates, and there\u2019s a lot of state to keep consistent, even though every update is an append. \nFast query execution relies on the ability to vectorise operations, which would be hampered by supporting row level concurrent appends.<\/li>\n  <li>It just isn\u2019t necessary for OLAP where mutable data structures are short-lived and immutable data structures are long-lived. \nIt would be better to choose a data structure which simplifies concurrent updates at the expense of query efficiency for indexing live data.<\/li>\n<\/ol>\n\n<p>A two phase construction process where values are first buffered and then sealed models this best:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">var<\/span> <span class=\"n\">appender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">maxValue<\/span><span class=\"o\">);<\/span>\n<span class=\"n\">getLongValues<\/span><span class=\"o\">().<\/span><span class=\"na\">forEach<\/span><span class=\"o\">(<\/span><span class=\"nl\">appender:<\/span><span class=\"o\">:<\/span><span class=\"n\">add<\/span><span class=\"o\">);<\/span>\n<span class=\"nc\">RangeBitmap<\/span> <span class=\"n\">bitmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">build<\/span><span class=\"o\">();<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>To write the data to disk so that it can be memory mapped (or otherwise loaded from disk into a <code class=\"language-plaintext highlighter-rouge\">ByteBuffer<\/code>), the process is a little more convoluted:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">var<\/span> <span class=\"n\">appender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">maxValue<\/span><span class=\"o\">);<\/span>\n<span class=\"n\">getLongValues<\/span><span class=\"o\">().<\/span><span class=\"na\">forEach<\/span><span class=\"o\">(<\/span><span class=\"nl\">appender:<\/span><span class=\"o\">:<\/span><span class=\"n\">add<\/span><span class=\"o\">);<\/span>\n<span class=\"nc\">ByteBuffer<\/span> <span class=\"n\">buffer<\/span> <span class=\"o\">=<\/span> <span class=\"n\">allocateByteBuffer<\/span><span class=\"o\">(<\/span><span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">serializedSizeInBytes<\/span><span class=\"o\">());<\/span>\n<span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">serialize<\/span><span class=\"o\">(<\/span><span class=\"n\">buffer<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>After executing the code above, the <code class=\"language-plaintext highlighter-rouge\">ByteBuffer<\/code> will contain the header described above, the masks, and some <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> containers laid out so that it can be queried without deserialization.\nA <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> can be loaded from a <code class=\"language-plaintext highlighter-rouge\">ByteBuffer<\/code> in a matter of nanoseconds as follows:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kt\">var<\/span> <span class=\"n\">bitmap<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">map<\/span><span class=\"o\">(<\/span><span class=\"n\">buffer<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The usual caveats of memory mapping apply, but <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> doesn\u2019t care how the bytes get from disk into the <code class=\"language-plaintext highlighter-rouge\">ByteBuffer<\/code>.<\/p>\n\n<h3 id=\"encoding\">Encoding<\/h3>\n\n<p>Values need to be encoded into <code class=\"language-plaintext highlighter-rouge\">long<\/code>s before indexing, which allows any primitive type to be indexed with the same API.\nMoreover, <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> interprets <code class=\"language-plaintext highlighter-rouge\">long<\/code> values in unsigned order, which simplifies slice layout.\nThis makes using the data structure fairly user-unfriendly, but this was a suitable API for its use in Apache Pinot\u2019s range index.\nFor example, a <code class=\"language-plaintext highlighter-rouge\">double<\/code> is mapped to an unsigned <code class=\"language-plaintext highlighter-rouge\">long<\/code> so that ordering is preserved as follows:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">long<\/span> <span class=\"nf\">ordinalOf<\/span><span class=\"o\">(<\/span><span class=\"kt\">double<\/span> <span class=\"n\">value<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n  <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">value<\/span> <span class=\"o\">==<\/span> <span class=\"nc\">Double<\/span><span class=\"o\">.<\/span><span class=\"na\">POSITIVE_INFINITY<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"mh\">0xFFFFFFFFFFFFFFFF<\/span><span class=\"no\">L<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n  <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">value<\/span> <span class=\"o\">==<\/span> <span class=\"nc\">Double<\/span><span class=\"o\">.<\/span><span class=\"na\">NEGATIVE_INFINITY<\/span> <span class=\"o\">||<\/span> <span class=\"nc\">Double<\/span><span class=\"o\">.<\/span><span class=\"na\">isNaN<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n  <span class=\"kt\">long<\/span> <span class=\"n\">bits<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Double<\/span><span class=\"o\">.<\/span><span class=\"na\">doubleToLongBits<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">);<\/span>\n  <span class=\"c1\">\/\/ need negatives to come before positives<\/span>\n  <span class=\"k\">if<\/span> <span class=\"o\">((<\/span><span class=\"n\">bits<\/span> <span class=\"o\">&amp;<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">)<\/span> <span class=\"o\">==<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"c1\">\/\/ conflate 0\/-0, or reverse order of negatives<\/span>\n    <span class=\"n\">bits<\/span> <span class=\"o\">=<\/span> <span class=\"n\">bits<\/span> <span class=\"o\">==<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span> <span class=\"o\">?<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span> <span class=\"o\">:<\/span> <span class=\"o\">~<\/span><span class=\"n\">bits<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span> <span class=\"k\">else<\/span> <span class=\"o\">{<\/span>\n    <span class=\"c1\">\/\/ positives after negatives<\/span>\n    <span class=\"n\">bits<\/span> <span class=\"o\">^=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n  <span class=\"k\">return<\/span> <span class=\"n\">bits<\/span><span class=\"o\">;<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Subtracting the minimum value from every other value, as is done in Apache Pinot\u2019s range index for the sake of reducing the number of slices, automatically anchors the value range to zero and makes all values implicitly unsigned.<\/p>\n\n<h3 id=\"querying-a-rangebitmap\">Querying a <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code><\/h3>\n\n<p>Querying a mapped <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> is straightforward, with the API described by the table:<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>operation<\/th>\n        <th>method<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>$ \\{ x : x &lt; t \\} $<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap lt(long threshold)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x &lt; t \\} \\cap C$<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap lt(long threshold, RoaringBitmap context)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x \\leq t \\} $<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap lte(long threshold)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x \\leq t \\} \\cap C$<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap lte(long threshold, RoaringBitmap context)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x &gt; t \\} $<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap gt(long threshold)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x &gt; t \\} \\cap C$<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap gt(long threshold, RoaringBitmap context)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x \\geq t \\}$<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap gte(long threshold)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : x \\geq t \\} \u2229 C$<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap gte(long threshold, RoaringBitmap context)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : t \\leq x \\leq u \\} $<\/td>\n        <td><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap between(long min, long max)<\/code><\/td>\n      <\/tr>\n      <tr>\n        <td>$ \\{ x : t \\leq x \\leq u \\} \\cap C$<\/td>\n        <td>Currently unimplemented<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>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.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nc\">RangeBitmap<\/span> <span class=\"n\">rangeBitmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">createRangeBitmap<\/span><span class=\"o\">();<\/span>\n<span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">context<\/span> <span class=\"o\">=<\/span> <span class=\"o\">...<\/span>\n<span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">range<\/span> <span class=\"o\">=<\/span> <span class=\"n\">rangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">lt<\/span><span class=\"o\">(<\/span><span class=\"n\">threshold<\/span><span class=\"o\">);<\/span>\n<span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">intersection<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RoarinBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">and<\/span><span class=\"o\">(<\/span><span class=\"n\">context<\/span><span class=\"o\">,<\/span> <span class=\"n\">range<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The following is equivalent to the code above, but what\u2019s below is more efficient because it doesn\u2019t need to materialise an intermediate bitmap and skips doing range evaluations where the intersection would definitely be empty:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nc\">RangeBitmap<\/span> <span class=\"n\">rangeBitmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">createRangeBitmap<\/span><span class=\"o\">();<\/span>\n<span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">context<\/span> <span class=\"o\">=<\/span> <span class=\"o\">...<\/span>\n<span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">intersection<\/span> <span class=\"o\">=<\/span> <span class=\"n\">rangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">lt<\/span><span class=\"o\">(<\/span><span class=\"n\">threshold<\/span><span class=\"o\">,<\/span> <span class=\"n\">context<\/span><span class=\"o\">);<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h3 id=\"performance-evaluation\">Performance Evaluation<\/h3>\n\n<p>I was reluctant to add this section to the post because it can\u2019t really be a fair comparison and is more of a sanity check.\nPinot already had range indexes, and the goal of implementing <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> was to improve on the old implementation, and very early measurements indicated that <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> would improve performance by 5-10x, so alternatives were not evaluated exhaustively.\nI haven\u2019t included every possible implementation in this comparison, but I feel it\u2019s reasonably balanced because I demonstrate how much faster queries over sorted data are.<\/p>\n\n<p>Various implementations of this interface are compared with values of different distributions, the size of the input is 76MB (10M <code class=\"language-plaintext highlighter-rouge\">long<\/code>s) in each case:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">interface<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">);<\/span>\n\n  <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">();<\/span>\n\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The distributions are varied just to show that it actually matters, rather than systematically. \nThe 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.\nThis means that results are comparable given a choice of distribution, but it also demonstrates that <em>your mileage may vary<\/em> as peculiarities of data sets can be an important factor. \nFor implementations which only work with sorted inputs, the data is sorted beforehand.<\/p>\n\n<p>As a baseline, imagine you\u2019re lucky enough to have sorted data and can perform an out-of-the-box binary search.\n<code class=\"language-plaintext highlighter-rouge\">Arrays.binarySearch<\/code> doesn\u2019t 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).<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">BinarySearch<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">BinarySearch<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">data<\/span> <span class=\"o\">=<\/span> <span class=\"n\">data<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">,<\/span> <span class=\"n\">min<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">start<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">start<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">while<\/span> <span class=\"o\">(<\/span><span class=\"n\">begin<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">data<\/span><span class=\"o\">[<\/span><span class=\"n\">begin<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">min<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">begin<\/span><span class=\"o\">--;<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">end<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">,<\/span> <span class=\"n\">max<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">=<\/span> <span class=\"n\">end<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">end<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">end<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">while<\/span> <span class=\"o\">(<\/span><span class=\"n\">finish<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">data<\/span><span class=\"o\">[<\/span><span class=\"n\">finish<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">finish<\/span><span class=\"o\">++;<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmapOfRange<\/span><span class=\"o\">(<\/span><span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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)$).<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-binarySearch.png\" alt=\"Binary search latency\" \/><\/p>\n\n<p>To get around this, the data structure below is a naive approximation to Apache Pinot\u2019s sorted index:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">IntervalsEvaluator<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">ranges<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">IntervalsEvaluator<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">values<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">unique<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[<\/span><span class=\"mi\">16<\/span><span class=\"o\">];<\/span>\n    <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">ranges<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[<\/span><span class=\"mi\">16<\/span><span class=\"o\">];<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">numRanges<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">current<\/span> <span class=\"o\">=<\/span> <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"mi\">0<\/span><span class=\"o\">];<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">values<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"kt\">long<\/span> <span class=\"n\">value<\/span> <span class=\"o\">=<\/span> <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">current<\/span> <span class=\"o\">!=<\/span> <span class=\"n\">value<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">numRanges<\/span> <span class=\"o\">==<\/span> <span class=\"n\">unique<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n          <span class=\"n\">unique<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">unique<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">2<\/span><span class=\"o\">);<\/span>\n          <span class=\"n\">ranges<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">ranges<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">2<\/span><span class=\"o\">);<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"n\">unique<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">current<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">ranges<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">numRanges<\/span><span class=\"o\">++;<\/span>\n        <span class=\"n\">current<\/span> <span class=\"o\">=<\/span> <span class=\"n\">value<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"n\">i<\/span><span class=\"o\">;<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">unique<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">current<\/span><span class=\"o\">;<\/span>\n    <span class=\"n\">ranges<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span><span class=\"o\">;<\/span>\n    <span class=\"n\">numRanges<\/span><span class=\"o\">++;<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">uniqueValues<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">unique<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">ranges<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">ranges<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">uniqueValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">min<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">start<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">start<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">end<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">uniqueValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">,<\/span> <span class=\"n\">max<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">=<\/span> <span class=\"n\">end<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">end<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">end<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">return<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmapOfRange<\/span><span class=\"o\">(<\/span><span class=\"n\">ranges<\/span><span class=\"o\">[<\/span><span class=\"n\">start<\/span><span class=\"o\">],<\/span> <span class=\"n\">ranges<\/span><span class=\"o\">[<\/span><span class=\"n\">finish<\/span><span class=\"o\">]);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">BYTES<\/span> <span class=\"o\">+<\/span> <span class=\"n\">ranges<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">.<\/span><span class=\"na\">BYTES<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-intervals.png\" alt=\"Intervals latency\" \/><\/p>\n\n<p>This could be micro-optimised for locality, but it\u2019s hard to beat for this problem, even though it has a spatial overhead of up to 1.5x the size of the input.\nNote 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.\n<img src=\"\/assets\/2022\/03\/range-bitmap-index\/serialized-size-intervals.png\" alt=\"Intervals serialized size\" \/>\nIn fact, none of the approaches which support range queries over unsorted data will even get close to this.<\/p>\n\n<p>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.\nThis is included as a proxy for the way trees which sort the values but not the indexes which I never found justification to implement.\nThese would also need to store and post-process the indexes.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">SortedValuesUnsortedIndexesEvaluator<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n  \n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">indexes<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">SortedValuesUnsortedIndexesEvaluator<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"nc\">List<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">LongIntPair<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">pairs<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">IntStream<\/span><span class=\"o\">.<\/span><span class=\"na\">range<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">)<\/span>\n        <span class=\"o\">.<\/span><span class=\"na\">mapToObj<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">LongIntPair<\/span><span class=\"o\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">],<\/span> <span class=\"n\">i<\/span><span class=\"o\">))<\/span>\n        <span class=\"o\">.<\/span><span class=\"na\">sorted<\/span><span class=\"o\">()<\/span>\n        <span class=\"o\">.<\/span><span class=\"na\">collect<\/span><span class=\"o\">(<\/span><span class=\"nc\">Collectors<\/span><span class=\"o\">.<\/span><span class=\"na\">toList<\/span><span class=\"o\">());<\/span>\n    <span class=\"n\">sortedValues<\/span> <span class=\"o\">=<\/span> <span class=\"n\">pairs<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">().<\/span><span class=\"na\">mapToLong<\/span><span class=\"o\">(<\/span><span class=\"n\">pair<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">pair<\/span><span class=\"o\">.<\/span><span class=\"na\">value<\/span><span class=\"o\">).<\/span><span class=\"na\">toArray<\/span><span class=\"o\">();<\/span>\n    <span class=\"n\">indexes<\/span> <span class=\"o\">=<\/span> <span class=\"n\">pairs<\/span><span class=\"o\">.<\/span><span class=\"na\">stream<\/span><span class=\"o\">().<\/span><span class=\"na\">mapToInt<\/span><span class=\"o\">(<\/span><span class=\"n\">pair<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">pair<\/span><span class=\"o\">.<\/span><span class=\"na\">index<\/span><span class=\"o\">).<\/span><span class=\"na\">toArray<\/span><span class=\"o\">();<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">sortedValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">min<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">start<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">start<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">while<\/span> <span class=\"o\">(<\/span><span class=\"n\">begin<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">[<\/span><span class=\"n\">begin<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">min<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">begin<\/span><span class=\"o\">--;<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">end<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">sortedValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">,<\/span> <span class=\"n\">max<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">=<\/span> <span class=\"n\">end<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">end<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">end<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">while<\/span> <span class=\"o\">(<\/span><span class=\"n\">finish<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">[<\/span><span class=\"n\">finish<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">]<\/span> <span class=\"o\">==<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">finish<\/span><span class=\"o\">++;<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">result<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">();<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">begin<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">finish<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">indexes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">result<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">BYTES<\/span> <span class=\"o\">+<\/span> <span class=\"n\">indexes<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">.<\/span><span class=\"na\">BYTES<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">final<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">LongIntPair<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">Comparable<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">LongIntPair<\/span><span class=\"o\">&gt;<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">value<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">index<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"kd\">private<\/span> <span class=\"nf\">LongIntPair<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">value<\/span><span class=\"o\">,<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">index<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">value<\/span> <span class=\"o\">=<\/span> <span class=\"n\">value<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">index<\/span> <span class=\"o\">=<\/span> <span class=\"n\">index<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">compareTo<\/span><span class=\"o\">(<\/span><span class=\"nc\">LongIntPair<\/span> <span class=\"n\">o<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">compare<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">,<\/span> <span class=\"n\">o<\/span><span class=\"o\">.<\/span><span class=\"na\">value<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">boolean<\/span> <span class=\"nf\">equals<\/span><span class=\"o\">(<\/span><span class=\"nc\">Object<\/span> <span class=\"n\">o<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"k\">this<\/span> <span class=\"o\">==<\/span> <span class=\"n\">o<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"kc\">true<\/span><span class=\"o\">;<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">o<\/span> <span class=\"o\">==<\/span> <span class=\"kc\">null<\/span> <span class=\"o\">||<\/span> <span class=\"n\">getClass<\/span><span class=\"o\">()<\/span> <span class=\"o\">!=<\/span> <span class=\"n\">o<\/span><span class=\"o\">.<\/span><span class=\"na\">getClass<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"kc\">false<\/span><span class=\"o\">;<\/span>\n      <span class=\"o\">}<\/span>\n\n      <span class=\"nc\">LongIntPair<\/span> <span class=\"n\">that<\/span> <span class=\"o\">=<\/span> <span class=\"o\">(<\/span><span class=\"nc\">LongIntPair<\/span><span class=\"o\">)<\/span> <span class=\"n\">o<\/span><span class=\"o\">;<\/span>\n\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">value<\/span> <span class=\"o\">!=<\/span> <span class=\"n\">that<\/span><span class=\"o\">.<\/span><span class=\"na\">value<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">return<\/span> <span class=\"kc\">false<\/span><span class=\"o\">;<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">index<\/span> <span class=\"o\">==<\/span> <span class=\"n\">that<\/span><span class=\"o\">.<\/span><span class=\"na\">index<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">hashCode<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"kt\">int<\/span> <span class=\"n\">result<\/span> <span class=\"o\">=<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span><span class=\"o\">)<\/span> <span class=\"o\">(<\/span><span class=\"n\">value<\/span> <span class=\"o\">^<\/span> <span class=\"o\">(<\/span><span class=\"n\">value<\/span> <span class=\"o\">&gt;&gt;&gt;<\/span> <span class=\"mi\">32<\/span><span class=\"o\">));<\/span>\n      <span class=\"n\">result<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">31<\/span> <span class=\"o\">*<\/span> <span class=\"n\">result<\/span> <span class=\"o\">+<\/span> <span class=\"n\">index<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">result<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This doesn\u2019t perform very well, and storing it requires 1.5x the size of the data, despite finding the boundaries quickly when there are few duplicates.<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-sortedValuesUnsortedIndexes.png\" alt=\"Sorted Values Unsorted Indexes latency\" \/>\n<img src=\"\/assets\/2022\/03\/range-bitmap-index\/serialized-size-sortedValuesUnsortedIndexes.png\" alt=\"Sorted Values Unsorted Indexes serialized  size\" \/><\/p>\n\n<p>Part of the problem is it\u2019s not very efficient to build a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> from unsorted data, and maybe Pinot needing a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> is an artificial constraint.\nIf building a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> weren\u2019t the bottleneck, copying and sorting a section of the <code class=\"language-plaintext highlighter-rouge\">indexes<\/code> array might be.<\/p>\n\n<p><code class=\"language-plaintext highlighter-rouge\">InvertedIndexEvaluator<\/code> is a natural extension of <code class=\"language-plaintext highlighter-rouge\">IntervalsEvaluator<\/code>, instead of storing the start of the range, it stores a bitmap of row indexes which have the value.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">InvertedIndexEvaluator<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">[]<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">serializedSize<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">InvertedIndexEvaluator<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">values<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">unique<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[<\/span><span class=\"mi\">16<\/span><span class=\"o\">];<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">numRanges<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">current<\/span> <span class=\"o\">=<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">[<\/span><span class=\"mi\">0<\/span><span class=\"o\">];<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"kt\">long<\/span> <span class=\"n\">value<\/span> <span class=\"o\">=<\/span> <span class=\"n\">sortedValues<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">current<\/span> <span class=\"o\">!=<\/span> <span class=\"n\">value<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">numRanges<\/span> <span class=\"o\">==<\/span> <span class=\"n\">unique<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n          <span class=\"n\">unique<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">unique<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">2<\/span><span class=\"o\">);<\/span>\n        <span class=\"o\">}<\/span>\n        <span class=\"n\">unique<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">current<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">numRanges<\/span><span class=\"o\">++;<\/span>\n        <span class=\"n\">current<\/span> <span class=\"o\">=<\/span> <span class=\"n\">value<\/span><span class=\"o\">;<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">unique<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">current<\/span><span class=\"o\">;<\/span>\n    <span class=\"n\">numRanges<\/span><span class=\"o\">++;<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">uniqueValues<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">copyOf<\/span><span class=\"o\">(<\/span><span class=\"n\">unique<\/span><span class=\"o\">,<\/span> <span class=\"n\">numRanges<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">RoaringBitmapWriter<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">&gt;[]<\/span> <span class=\"n\">writers<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">RoaringBitmapWriter<\/span><span class=\"o\">[<\/span><span class=\"n\">numRanges<\/span><span class=\"o\">];<\/span>\n    <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">setAll<\/span><span class=\"o\">(<\/span><span class=\"n\">writers<\/span><span class=\"o\">,<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"nc\">RoaringBitmapWriter<\/span><span class=\"o\">.<\/span><span class=\"na\">writer<\/span><span class=\"o\">().<\/span><span class=\"na\">get<\/span><span class=\"o\">());<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">values<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">writers<\/span><span class=\"o\">[<\/span><span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">uniqueValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">])].<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">[]<\/span> <span class=\"n\">bitmaps<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">[<\/span><span class=\"n\">writers<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">];<\/span>\n    <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">setAll<\/span><span class=\"o\">(<\/span><span class=\"n\">bitmaps<\/span><span class=\"o\">,<\/span> <span class=\"n\">i<\/span> <span class=\"o\">-&gt;<\/span> <span class=\"n\">writers<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">].<\/span><span class=\"na\">get<\/span><span class=\"o\">());<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmaps<\/span> <span class=\"o\">=<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">ss<\/span> <span class=\"o\">=<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">8<\/span> <span class=\"o\">+<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">4<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">bitmap<\/span> <span class=\"o\">:<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">ss<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">bitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">serializedSizeInBytes<\/span><span class=\"o\">();<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">serializedSize<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ss<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">start<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">uniqueValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">min<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">=<\/span> <span class=\"n\">start<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">start<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">start<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">end<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">binarySearch<\/span><span class=\"o\">(<\/span><span class=\"n\">uniqueValues<\/span><span class=\"o\">,<\/span> <span class=\"n\">begin<\/span><span class=\"o\">,<\/span> <span class=\"n\">uniqueValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">,<\/span> <span class=\"n\">max<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">=<\/span> <span class=\"n\">end<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"mi\">0<\/span> <span class=\"o\">?<\/span> <span class=\"n\">end<\/span> <span class=\"o\">:<\/span> <span class=\"o\">-<\/span><span class=\"n\">end<\/span> <span class=\"o\">-<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">RoaringBitmap<\/span> <span class=\"n\">bitmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">[<\/span><span class=\"n\">begin<\/span><span class=\"o\">].<\/span><span class=\"na\">clone<\/span><span class=\"o\">();<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"n\">begin<\/span> <span class=\"o\">+<\/span> <span class=\"mi\">1<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">finish<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">bitmaps<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">or<\/span><span class=\"o\">(<\/span><span class=\"n\">bitmaps<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">bitmap<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">serializedSize<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.\n<img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-invertedIndex.png\" alt=\"Inverted index latency\" \/><\/p>\n\n<p>The index is always smaller than the data:\n<img src=\"\/assets\/2022\/03\/range-bitmap-index\/serialized-size-invertedIndex.png\" alt=\"Inverted index serialized size\" \/><\/p>\n\n<p>Pinot\u2019s 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.\nBy 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.<\/p>\n\n<p>The simplest approach is a scan:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">Scan<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">Scan<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">data<\/span> <span class=\"o\">=<\/span> <span class=\"n\">data<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"nc\">RoaringBitmapWriter<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">RoaringBitmap<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">writer<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RoaringBitmapWriter<\/span><span class=\"o\">.<\/span><span class=\"na\">writer<\/span><span class=\"o\">().<\/span><span class=\"na\">get<\/span><span class=\"o\">();<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">&gt;=<\/span> <span class=\"n\">min<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">data<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">writer<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">writer<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">();<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-scan.png\" alt=\"Scan latency\" \/><\/p>\n\n<p>Finally, <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">RangeBitmapEvaluator<\/span> <span class=\"kd\">implements<\/span> <span class=\"nc\">RangeEvaluator<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"nc\">RangeBitmap<\/span> <span class=\"n\">bitmap<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kd\">final<\/span> <span class=\"kt\">int<\/span> <span class=\"n\">serializedSize<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"nf\">RangeBitmapEvaluator<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">min<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">MIN_VALUE<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">datum<\/span> <span class=\"o\">:<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">min<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">min<\/span><span class=\"o\">(<\/span><span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"n\">datum<\/span><span class=\"o\">);<\/span>\n      <span class=\"n\">max<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Math<\/span><span class=\"o\">.<\/span><span class=\"na\">max<\/span><span class=\"o\">(<\/span><span class=\"n\">max<\/span><span class=\"o\">,<\/span> <span class=\"n\">datum<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">var<\/span> <span class=\"n\">appender<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">RangeBitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">appender<\/span><span class=\"o\">(<\/span><span class=\"n\">max<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">datum<\/span> <span class=\"o\">:<\/span> <span class=\"n\">data<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">add<\/span><span class=\"o\">(<\/span><span class=\"n\">datum<\/span> <span class=\"o\">-<\/span> <span class=\"n\">min<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">serializedSize<\/span> <span class=\"o\">=<\/span> <span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">serializedSizeInBytes<\/span><span class=\"o\">();<\/span>\n    <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">bitmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">appender<\/span><span class=\"o\">.<\/span><span class=\"na\">build<\/span><span class=\"o\">();<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"nc\">RoaringBitmap<\/span> <span class=\"nf\">between<\/span><span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"kt\">long<\/span> <span class=\"n\">max<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">bitmap<\/span><span class=\"o\">.<\/span><span class=\"na\">between<\/span><span class=\"o\">(<\/span><span class=\"n\">min<\/span><span class=\"o\">,<\/span> <span class=\"n\">max<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Override<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">serializedSize<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">serializedSize<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>It doesn\u2019t get close to <code class=\"language-plaintext highlighter-rouge\">IntervalsEvaluator<\/code>, but it\u2019s the least bad considered option for unsorted values, it is always smaller than the data.<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-rangeBitmap.png\" alt=\"RangeBitmap latency\" \/>\n<img src=\"\/assets\/2022\/03\/range-bitmap-index\/serialized-size-rangeBitmap.png\" alt=\"RangeBitmap serialized size\" \/><\/p>\n\n<p>Excluding the implementations which only work on sorted inputs, <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> looks like the least bad option.\nIt 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.<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/range-latency-all.png\" alt=\"RangeBitmap latency\" \/><\/p>\n\n<p>It also has the least spatial overhead except for scanning which has none.<\/p>\n\n<p><img src=\"\/assets\/2022\/03\/range-bitmap-index\/serialized-size-all.png\" alt=\"RangeBitmap latency\" \/><\/p>\n\n<p>The source code for these benchmarks is located <a href=\"http:\/\/github.com\/richardstartin\/range-benchmark\">here<\/a><\/p>\n\n<h2 id=\"usage-in-apache-pinot\">Usage in Apache Pinot<\/h2>\n\n<p>Apache Pinot allows range indexing of immutable segments, and since 0.9.0 these have been implemented in terms of <code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code>, enabled via a feature flag.\nFrom 0.10.0, this feature flag is enabled by default, and just needs the configuration in the documentation <a href=\"https:\/\/docs.pinot.apache.org\/basics\/indexing\/range-index\">here<\/a>.\nUsers who have enabled the feature flag (which I won\u2019t name: just upgrade to 0.10.0) have reported improvements of upwards of 5x on Pinot\u2019s old range index implementation.<\/p>\n\n<h2 id=\"links\">Links:<\/h2>\n<ul>\n  <li><a href=\"https:\/\/www.startree.ai\/blogs\">StarTree blog<\/a> covers Apache Pinot<\/li>\n  <li><a href=\"https:\/\/www.youtube.com\/c\/StarTree\">StarTree YouTube channel<\/a> curates Apache Pinot related content<\/li>\n  <li><a href=\"https:\/\/communityinviter.com\/apps\/apache-pinot\/apache-pinot\">Apache Pinot Slack channel<\/a><\/li>\n  <li>Apache Pinot project on <a href=\"https:\/\/github.com\/apache\/pinot\">github<\/a><\/li>\n  <li>Apache Pinot <a href=\"https:\/\/docs.pinot.apache.org\">documentation<\/a><\/li>\n  <li>Apache Pinot Range index <a href=\"https:\/\/docs.google.com\/document\/d\/1se2OgqXJiD7r7S7U6SUmTIAApO66QIrAYosxvXHEXlw\/edit\">design document<\/a><\/li>\n  <li>Apache Pinot range index <a href=\"https:\/\/github.com\/apache\/pinot\/blob\/f9ab252980e4f973d60b9db2a0f5e7d5764bdaf2\/pinot-segment-local\/src\/main\/java\/org\/apache\/pinot\/segment\/local\/segment\/creator\/impl\/inv\/BitSlicedRangeIndexCreator.java\">creator<\/a> and <a href=\"https:\/\/github.com\/apache\/pinot\/blob\/0031f2444c8684eb515245d9298ae8b336268aa4\/pinot-segment-local\/src\/main\/java\/org\/apache\/pinot\/segment\/local\/segment\/index\/readers\/BitSlicedRangeIndexReader.java\">reader<\/a><\/li>\n  <li>RoaringBitmap <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\">Java Library<\/a><\/li>\n  <li><code class=\"language-plaintext highlighter-rouge\">RangeBitmap<\/code> <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\/blob\/master\/RoaringBitmap\/src\/main\/java\/org\/roaringbitmap\/RangeBitmap.java\">implementation<\/a><\/li>\n  <li>High level <a href=\"https:\/\/www.markhneedham.com\/blog\/2021\/12\/07\/apache-pinot-exploring-range-queries\/\">overview<\/a> of Pinot range queries<\/li>\n<\/ul>","author":{"name":{}},"category":[{"@attributes":{"term":"java"}},{"@attributes":{"term":"roaring"}},{"@attributes":{"term":"pinot"}}],"summary":"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."},{"title":"Performance Myths and Continuous Profiling","link":{"@attributes":{"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","updated":"2021-12-27T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/perf-myths-and-continuous-profiling","content":"<p>My <a href=\"\/posts\/5-java-mundane-performance-tricks\">last post<\/a> was about five very simple things you can do to avoid Java programs from being slower than they need to be.\nThe reception to this post was mixed.\nSome readers agreed that the problems mentioned in the post were indeed very common, while others suggested more common inefficient patterns to avoid.\nFor example, I could have suggested to precompile regular expressions, or not to program by exception, or to avoid <code class=\"language-plaintext highlighter-rouge\">String.format<\/code>, but I felt this was all well covered already.\nThe 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.\nHowever, several readers dismissed the content of the post as premature optimisation.<\/p>\n\n<p>It\u2019s interesting how negative responses to posts about minor software efficiencies can be.\nThere will be various causes for this reaction, but a common theme is the existence of performance myths perpetuated by bad blog posts.\nI certainly hope that I\u2019m not perpetuating performance myths but this position is understandable.<\/p>\n\n<p>Performance myths can be split into two categories, and I\u2019ll give an example of each:<\/p>\n\n<ul>\n  <li>Those that were never true<\/li>\n  <li>Those that used to be true<\/li>\n<\/ul>\n\n<p>I think it\u2019s the second category which cause the strongest reactions because they counteract future improvements.\nIn this post I take a look at examples of both before discussing continuous profiling since the items I chose were based on things I\u2019ve seen show up in profiles a lot.<\/p>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables\" id=\"markdown-toc-never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables\">Never true: prefer pre-increment to post-increment for loop induction variables<\/a><\/li>\n  <li><a href=\"#true-once-prefer-stringbuilder-to-string-concatenation\" id=\"markdown-toc-true-once-prefer-stringbuilder-to-string-concatenation\">True once: prefer StringBuilder to string concatenation<\/a><\/li>\n  <li><a href=\"#continuous-profiling\" id=\"markdown-toc-continuous-profiling\">Continuous profiling<\/a><\/li>\n<\/ol>\n\n<h3 id=\"never-true-prefer-pre-increment-to-post-increment-for-loop-induction-variables\">Never true: prefer pre-increment to post-increment for loop induction variables<\/h3>\n\n<p>I have encountered claims that loops which use pre-increment for loop induction variables are faster than those which use post-increment.\nSo:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n   <span class=\"n\">doIt<\/span><span class=\"o\">();<\/span>  \n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>would be slightly slower than:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n   <span class=\"n\">doIt<\/span><span class=\"o\">();<\/span>  \n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Even if this were true, the loop body would dominate the cost of control flow in most cases.\nIt\u2019s worth looking at the origin of the myth and the reasoning behind it though.\nThe myth comes from C++, where it\u2019s possible to overload increment operators.\nAs you are probably aware, post-increment yields the value before performing the increment.\nThis may lead to copying the value, so unless the compiler eliminates the copy because it\u2019s not observable, pre-increment would have an advantage.\nI\u2019m 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.<\/p>\n\n<p>Since the JIT compiler optimises loops, it\u2019s very unlikely that it will generate code corresponding to either  <code class=\"language-plaintext highlighter-rouge\">i++<\/code> or <code class=\"language-plaintext highlighter-rouge\">++i<\/code>.\nThis can be seen by analysing a simple benchmark:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">Increments<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nd\">@Param<\/span><span class=\"o\">(<\/span><span class=\"s\">\"1024\"<\/span><span class=\"o\">)<\/span>\n  <span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">input<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">output<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"n\">input<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">ThreadLocalRandom<\/span><span class=\"o\">.<\/span><span class=\"na\">current<\/span><span class=\"o\">().<\/span><span class=\"na\">ints<\/span><span class=\"o\">(<\/span><span class=\"n\">size<\/span><span class=\"o\">).<\/span><span class=\"na\">toArray<\/span><span class=\"o\">();<\/span>\n    <span class=\"n\">output<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">autovecPre<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">output<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">input<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">output<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">autovecPost<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">output<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">+=<\/span> <span class=\"n\">input<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">output<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">reducePre<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">sum<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">sum<\/span> <span class=\"o\">+=<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">.<\/span><span class=\"na\">bitCount<\/span><span class=\"o\">(<\/span><span class=\"n\">input<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">sum<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">reducePost<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">sum<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">sum<\/span> <span class=\"o\">+=<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">.<\/span><span class=\"na\">bitCount<\/span><span class=\"o\">(<\/span><span class=\"n\">input<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">sum<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">blackholedPre<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">blackholedPost<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">input<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The benchmark consists of a pre- and post-increment version of three loops selected carefully.\nLoops <code class=\"language-plaintext highlighter-rouge\">autovecPre<\/code> and <code class=\"language-plaintext highlighter-rouge\">autovecPost<\/code> can be autovectorised. \nLoops <code class=\"language-plaintext highlighter-rouge\">reducePre<\/code> and <code class=\"language-plaintext highlighter-rouge\">reducePost<\/code> can\u2019t be autovectorised (because the operation in the loop body can\u2019t, but fortunately this will no longer be the case on some hardware, see <a href=\"https:\/\/github.com\/openjdk\/jdk\/pull\/6857\">JDK-8278868<\/a>) but can be unrolled.\nUsing <code class=\"language-plaintext highlighter-rouge\">Blackhold::consume<\/code> prevents loop unrolling, so different code will be generated in <code class=\"language-plaintext highlighter-rouge\">blackholedPre<\/code> and <code class=\"language-plaintext highlighter-rouge\">blackholedPost<\/code> than in the other loops.<\/p>\n\n<p>Firstly, there is no difference in the scores for these benchmarks (<code class=\"language-plaintext highlighter-rouge\">pre<\/code> is <em>slower<\/em> once but it\u2019s just noise):<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>Benchmark                  (size)  Mode  Cnt     Score   Error  Units\nIncrements.autovecPost       1024  avgt    5   156.744 \u00b1 0.416  ns\/op\nIncrements.autovecPre        1024  avgt    5   158.390 \u00b1 0.096  ns\/op\nIncrements.blackholedPost    1024  avgt    5  2798.470 \u00b1 2.657  ns\/op\nIncrements.blackholedPre     1024  avgt    5  2798.259 \u00b1 2.553  ns\/op\nIncrements.reducePost        1024  avgt    5   456.012 \u00b1 0.267  ns\/op\nIncrements.reducePre         1024  avgt    5   455.922 \u00b1 0.299  ns\/op\n<\/code><\/pre><\/div><\/div>\n\n<p>This is because the loops are JIT-compiled exactly the same way.<\/p>\n\n<p>Here\u2019s <code class=\"language-plaintext highlighter-rouge\">autovecPre<\/code> and <code class=\"language-plaintext highlighter-rouge\">autovecPost<\/code>, where <code class=\"language-plaintext highlighter-rouge\">i<\/code> is incremented by 64 at a time: <code class=\"language-plaintext highlighter-rouge\">add $0x40,%edx<\/code>.<\/p>\n\n<pre><code class=\"language-asm\">....[Hottest Region 1]..............................................................................\nc2, level 4, inc.generated.Increments_autovecPost_jmhTest::autovecPost_avgt_jmhStub, version 472 (464 bytes) \n\n                       0x00007fd6f435aff4: vzeroupper \n                       0x00007fd6f435aff7: add    $0x30,%rsp\n                       0x00007fd6f435affb: pop    %rbp\n                       0x00007fd6f435affc: mov    0x108(%r15),%r10\n                       0x00007fd6f435b003: test   %eax,(%r10)        \n                       0x00007fd6f435b006: retq   \n                       0x00007fd6f435b007: nop                       \n                                                                     \n                                                                     \n         \u2197         \u2197   0x00007fd6f435b008: vmovdqu 0x10(%r8,%rdx,4),%ymm0\n  0.73%  \u2502         \u2502   0x00007fd6f435b00f: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0\n  0.75%  \u2502         \u2502   0x00007fd6f435b016: vmovdqu %ymm0,0x10(%r8,%rdx,4)\n         \u2502         \u2502                                                 \n         \u2502         \u2502                                                 \n         \u2502         \u2502   0x00007fd6f435b01d: add    $0x8,%edx          \n         \u2502         \u2502                                                 \n         \u2502         \u2502                                                 \n  0.66%  \u2502         \u2502   0x00007fd6f435b020: cmp    %r9d,%edx\n         \u2570         \u2502   0x00007fd6f435b023: jl     0x00007fd6f435b008 \n                   \u2502                                                 \n                   \u2502                                                 \n                 \u2197 \u2502\u2197  0x00007fd6f435b025: cmp    %r11d,%edx\n          \u256d      \u2502 \u2502\u2502  0x00007fd6f435b028: jge    0x00007fd6f435b03d\n  0.02%   \u2502      \u2502 \u2502\u2502  0x00007fd6f435b02a: xchg   %ax,%ax            \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2502\u2197     \u2502 \u2502\u2502  0x00007fd6f435b02c: mov    0x10(%r10,%rdx,4),%ebx\n  0.54%   \u2502\u2502     \u2502 \u2502\u2502  0x00007fd6f435b031: add    %ebx,0x10(%r8,%rdx,4)\n          \u2502\u2502     \u2502 \u2502\u2502                                                \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n  0.58%   \u2502\u2502     \u2502 \u2502\u2502  0x00007fd6f435b036: inc    %edx               \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n  0.54%   \u2502\u2502     \u2502 \u2502\u2502  0x00007fd6f435b038: cmp    %r11d,%edx\n          \u2502\u2570     \u2502 \u2502\u2502  0x00007fd6f435b03b: jl     0x00007fd6f435b02c \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2198 \u2197    \u2502 \u2502\u2502  0x00007fd6f435b03d: mov    %rcx,%rdx\n  0.23%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b040: shl    $0x3,%rdx          \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b044: mov    0x10(%rsp),%rsi\n  0.10%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b049: data16 xchg %ax,%ax\n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b04c: vzeroupper \n  0.29%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b04f: callq  0x00007fd6ec89cb00 \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b054: mov    0x40(%rsp),%r10\n  0.17%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b059: movzbl 0x94(%r10),%r10d   \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.10%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b061: mov    0x108(%r15),%r11\n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b068: add    $0x1,%rbp          \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.08%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b06c: test   %eax,(%r11)        \n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b06f: test   %r10d,%r10d\n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b072: jne    0x00007fd6f435afcf \n            \u2502    \u2502 \u2502\u2502                                                \n  0.02%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b078: mov    0x50(%rsp),%r10\n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b07d: mov    0x10(%r10),%r10d   \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.12%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b081: mov    0xc(%r12,%r10,8),%r11d  \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b086: mov    0x50(%rsp),%r8\n  0.06%     \u2502    \u2502 \u2502\u2502  0x00007fd6f435b08b: mov    0x14(%r8),%ecx     \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007fd6f435b08f: test   %r11d,%r11d\n            \u2570    \u2502 \u2502\u2502  0x00007fd6f435b092: jbe    0x00007fd6f435b03d  \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n  0.23%          \u2502 \u2502\u2502  0x00007fd6f435b094: mov    0xc(%r12,%rcx,8),%r8d  \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502  0x00007fd6f435b099: test   %r8d,%r8d\n             \u256d   \u2502 \u2502\u2502  0x00007fd6f435b09c: jbe    0x00007fd6f435b1ed\n  0.08%      \u2502   \u2502 \u2502\u2502  0x00007fd6f435b0a2: mov    %r11d,%r9d\n             \u2502   \u2502 \u2502\u2502  0x00007fd6f435b0a5: dec    %r9d\n  0.17%      \u2502   \u2502 \u2502\u2502  0x00007fd6f435b0a8: cmp    %r8d,%r9d\n             \u2502\u256d  \u2502 \u2502\u2502  0x00007fd6f435b0ab: jae    0x00007fd6f435b1ed\n             \u2502\u2502  \u2502 \u2502\u2502  0x00007fd6f435b0b1: cmp    %r11d,%r9d\n             \u2502\u2502\u256d \u2502 \u2502\u2502  0x00007fd6f435b0b4: jae    0x00007fd6f435b1ed\n  0.08%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0ba: shl    $0x3,%r10\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0be: lea    (%r12,%rcx,8),%r8\n  0.19%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0c2: mov    %r8d,%r9d\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0c5: shr    $0x2,%r9d\n  0.06%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0c9: and    $0x7,%r9d\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0cd: mov    $0x3,%ebx\n  0.19%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0d2: sub    %r9d,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0d5: and    $0x7,%ebx\n  0.06%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0d8: inc    %ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0da: cmp    %r11d,%ebx\n  0.08%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0dd: cmovg  %r11d,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0e1: xor    %edx,%edx          \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n  0.48%      \u2502\u2502\u2502\u2197\u2502 \u2502\u2502  0x00007fd6f435b0e3: mov    0x10(%r10,%rdx,4),%edi\n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007fd6f435b0e8: add    %edi,0x10(%r8,%rdx,4)  \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n  0.95%      \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007fd6f435b0ed: inc    %edx               \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007fd6f435b0ef: cmp    %ebx,%edx\n             \u2502\u2502\u2502\u2570\u2502 \u2502\u2502  0x00007fd6f435b0f1: jl     0x00007fd6f435b0e3  \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n  0.02%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0f3: mov    %r11d,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0f6: add    $0xffffffc1,%ebx\n  0.21%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007fd6f435b0f9: cmp    %ebx,%edx\n             \u2502\u2502\u2502 \u2570 \u2502\u2502  0x00007fd6f435b0fb: jge    0x00007fd6f435b025  \n             \u2502\u2502\u2502   \u2502\u2502                                                \n             \u2502\u2502\u2502   \u2502\u2502                                                \n  2.90%      \u2502\u2502\u2502  \u2197\u2502\u2502  0x00007fd6f435b101: vmovdqu 0x10(%r8,%rdx,4),%ymm0\n  0.04%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b108: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0\n 10.08%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b10f: vmovdqu %ymm0,0x10(%r8,%rdx,4)\n  1.00%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b116: vmovdqu 0x30(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b11d: vpaddd 0x30(%r10,%rdx,4),%ymm0,%ymm0\n  9.27%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b124: vmovdqu %ymm0,0x30(%r8,%rdx,4)\n  0.79%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b12b: vmovdqu 0x50(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b132: vpaddd 0x50(%r10,%rdx,4),%ymm0,%ymm0\n  8.07%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b139: vmovdqu %ymm0,0x50(%r8,%rdx,4)\n  0.89%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b140: vmovdqu 0x70(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b147: vpaddd 0x70(%r10,%rdx,4),%ymm0,%ymm0\n  9.58%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b14e: vmovdqu %ymm0,0x70(%r8,%rdx,4)\n  0.52%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b155: vmovdqu 0x90(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b15f: vpaddd 0x90(%r10,%rdx,4),%ymm0,%ymm0\n  8.34%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b169: vmovdqu %ymm0,0x90(%r8,%rdx,4)\n  0.64%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b173: vmovdqu 0xb0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b17d: vpaddd 0xb0(%r10,%rdx,4),%ymm0,%ymm0\n  9.71%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b187: vmovdqu %ymm0,0xb0(%r8,%rdx,4)\n  0.58%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b191: vmovdqu 0xd0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b19b: vpaddd 0xd0(%r10,%rdx,4),%ymm0,%ymm0\n 14.68%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1a5: vmovdqu %ymm0,0xd0(%r8,%rdx,4)\n  0.54%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1af: vmovdqu 0xf0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1b9: vpaddd 0xf0(%r10,%rdx,4),%ymm0,%ymm0\n  9.46%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1c3: vmovdqu %ymm0,0xf0(%r8,%rdx,4)  \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n  0.52%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1cd: add    $0x40,%edx         \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007fd6f435b1d0: cmp    %ebx,%edx\n             \u2502\u2502\u2502  \u2570\u2502\u2502  0x00007fd6f435b1d2: jl     0x00007fd6f435b101\n  0.23%      \u2502\u2502\u2502   \u2502\u2502  0x00007fd6f435b1d8: mov    %r11d,%r9d\n             \u2502\u2502\u2502   \u2502\u2502  0x00007fd6f435b1db: add    $0xfffffff9,%r9d\n  0.10%      \u2502\u2502\u2502   \u2502\u2502  0x00007fd6f435b1df: cmp    %r9d,%edx\n             \u2502\u2502\u2502   \u2570\u2502  0x00007fd6f435b1e2: jl     0x00007fd6f435b008\n             \u2502\u2502\u2502    \u2570  0x00007fd6f435b1e8: jmpq   0x00007fd6f435b025 \n             \u2502\u2502\u2502                                                     \n             \u2502\u2502\u2502                                                     \n             \u2198\u2198\u2198       0x00007fd6f435b1ed: mov    $0xffffff7e,%esi\n                       0x00007fd6f435b1f2: mov    %r11d,0x18(%rsp)\n                       0x00007fd6f435b1f7: nop\n                       0x00007fd6f435b1f8: vzeroupper \n                       0x00007fd6f435b1fb: callq  0x00007fd6ec89d280  \n                                                                     \n....................................................................................................\n 95.73%  &lt;total for region 1&gt;\n<\/code><\/pre>\n\n<pre><code class=\"language-asm\">....[Hottest Region 1]..............................................................................\nc2, level 4, inc.generated.Increments_autovecPre_jmhTest::autovecPre_avgt_jmhStub, version 460 (464 bytes) \n             \n                       0x00007f44cc35d2f4: vzeroupper \n                       0x00007f44cc35d2f7: add    $0x30,%rsp\n                       0x00007f44cc35d2fb: pop    %rbp\n                       0x00007f44cc35d2fc: mov    0x108(%r15),%r10\n                       0x00007f44cc35d303: test   %eax,(%r10)        \n                       0x00007f44cc35d306: retq   \n                       0x00007f44cc35d307: nop                       \n                                                                     \n                                                                     \n  0.77%  \u2197         \u2197   0x00007f44cc35d308: vmovdqu 0x10(%r8,%rdx,4),%ymm0\n  0.25%  \u2502         \u2502   0x00007f44cc35d30f: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0\n  3.27%  \u2502         \u2502   0x00007f44cc35d316: vmovdqu %ymm0,0x10(%r8,%rdx,4)\n         \u2502         \u2502                                                 \n         \u2502         \u2502                                                 \n  0.29%  \u2502         \u2502   0x00007f44cc35d31d: add    $0x8,%edx          \n         \u2502         \u2502                                                 \n         \u2502         \u2502                                                 \n  0.10%  \u2502         \u2502   0x00007f44cc35d320: cmp    %r9d,%edx\n         \u2570         \u2502   0x00007f44cc35d323: jl     0x00007f44cc35d308  \n                   \u2502                                                 \n                   \u2502                                                 \n  0.19%          \u2197 \u2502\u2197  0x00007f44cc35d325: cmp    %r11d,%edx\n          \u256d      \u2502 \u2502\u2502  0x00007f44cc35d328: jge    0x00007f44cc35d33d\n          \u2502      \u2502 \u2502\u2502  0x00007f44cc35d32a: xchg   %ax,%ax            \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2502      \u2502 \u2502\u2502                                                \n  0.50%   \u2502\u2197     \u2502 \u2502\u2502  0x00007f44cc35d32c: mov    0x10(%r10,%rdx,4),%ebx\n          \u2502\u2502     \u2502 \u2502\u2502  0x00007f44cc35d331: add    %ebx,0x10(%r8,%rdx,4)\n          \u2502\u2502     \u2502 \u2502\u2502                                                \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n  1.06%   \u2502\u2502     \u2502 \u2502\u2502  0x00007f44cc35d336: inc    %edx               \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n          \u2502\u2502     \u2502 \u2502\u2502                                                \n          \u2502\u2502     \u2502 \u2502\u2502  0x00007f44cc35d338: cmp    %r11d,%edx\n          \u2502\u2570     \u2502 \u2502\u2502  0x00007f44cc35d33b: jl     0x00007f44cc35d32c \n          \u2502      \u2502 \u2502\u2502                                                \n          \u2502      \u2502 \u2502\u2502                                                \n  0.10%   \u2198 \u2197    \u2502 \u2502\u2502  0x00007f44cc35d33d: mov    %rcx,%rdx\n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d340: shl    $0x3,%rdx          \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.17%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d344: mov    0x10(%rsp),%rsi\n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d349: data16 xchg %ax,%ax\n  0.10%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d34c: vzeroupper \n  0.10%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d34f: callq  0x00007f44c489cb00 \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                               \n            \u2502    \u2502 \u2502\u2502                                               \n            \u2502    \u2502 \u2502\u2502                                               \n  0.02%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d354: mov    0x40(%rsp),%r10\n  0.08%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d359: movzbl 0x94(%r10),%r10d   \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d361: mov    0x108(%r15),%r11\n  0.04%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d368: add    $0x1,%rbp          \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d36c: test   %eax,(%r11)        \n  0.25%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d36f: test   %r10d,%r10d\n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d372: jne    0x00007f44cc35d2cf \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d378: mov    0x50(%rsp),%r10\n  0.04%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d37d: mov    0x10(%r10),%r10d   \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d381: mov    0xc(%r12,%r10,8),%r11d\n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.15%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d386: mov    0x50(%rsp),%r8\n            \u2502    \u2502 \u2502\u2502  0x00007f44cc35d38b: mov    0x14(%r8),%ecx     \n            \u2502    \u2502 \u2502\u2502                                                \n            \u2502    \u2502 \u2502\u2502                                                \n  0.04%     \u2502    \u2502 \u2502\u2502  0x00007f44cc35d38f: test   %r11d,%r11d\n            \u2570    \u2502 \u2502\u2502  0x00007f44cc35d392: jbe    0x00007f44cc35d33d \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n  0.06%          \u2502 \u2502\u2502  0x00007f44cc35d394: mov    0xc(%r12,%rcx,8),%r8d\n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n                 \u2502 \u2502\u2502                                                \n  0.27%          \u2502 \u2502\u2502  0x00007f44cc35d399: test   %r8d,%r8d\n             \u256d   \u2502 \u2502\u2502  0x00007f44cc35d39c: jbe    0x00007f44cc35d4ed\n  0.15%      \u2502   \u2502 \u2502\u2502  0x00007f44cc35d3a2: mov    %r11d,%r9d\n             \u2502   \u2502 \u2502\u2502  0x00007f44cc35d3a5: dec    %r9d\n  0.17%      \u2502   \u2502 \u2502\u2502  0x00007f44cc35d3a8: cmp    %r8d,%r9d\n             \u2502\u256d  \u2502 \u2502\u2502  0x00007f44cc35d3ab: jae    0x00007f44cc35d4ed\n  0.08%      \u2502\u2502  \u2502 \u2502\u2502  0x00007f44cc35d3b1: cmp    %r11d,%r9d\n             \u2502\u2502\u256d \u2502 \u2502\u2502  0x00007f44cc35d3b4: jae    0x00007f44cc35d4ed\n  0.19%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3ba: shl    $0x3,%r10\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3be: lea    (%r12,%rcx,8),%r8\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3c2: mov    %r8d,%r9d\n  0.12%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3c5: shr    $0x2,%r9d\n  0.04%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3c9: and    $0x7,%r9d\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3cd: mov    $0x3,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3d2: sub    %r9d,%ebx\n  0.10%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3d5: and    $0x7,%ebx\n  0.17%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3d8: inc    %ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3da: cmp    %r11d,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3dd: cmovg  %r11d,%ebx\n  0.06%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3e1: xor    %edx,%edx          \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n  0.19%      \u2502\u2502\u2502\u2197\u2502 \u2502\u2502  0x00007f44cc35d3e3: mov    0x10(%r10,%rdx,4),%edi\n  0.21%      \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007f44cc35d3e8: add    %edi,0x10(%r8,%rdx,4)\n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n  0.50%      \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007f44cc35d3ed: inc    %edx               \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502\u2502\u2502 \u2502\u2502                                                \n  0.21%      \u2502\u2502\u2502\u2502\u2502 \u2502\u2502  0x00007f44cc35d3ef: cmp    %ebx,%edx\n             \u2502\u2502\u2502\u2570\u2502 \u2502\u2502  0x00007f44cc35d3f1: jl     0x00007f44cc35d3e3 \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n             \u2502\u2502\u2502 \u2502 \u2502\u2502                                                \n  0.08%      \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3f3: mov    %r11d,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3f6: add    $0xffffffc1,%ebx\n             \u2502\u2502\u2502 \u2502 \u2502\u2502  0x00007f44cc35d3f9: cmp    %ebx,%edx\n             \u2502\u2502\u2502 \u2570 \u2502\u2502  0x00007f44cc35d3fb: jge    0x00007f44cc35d325  \n             \u2502\u2502\u2502   \u2502\u2502                                                \n             \u2502\u2502\u2502   \u2502\u2502                                                \n  3.25%      \u2502\u2502\u2502  \u2197\u2502\u2502  0x00007f44cc35d401: vmovdqu 0x10(%r8,%rdx,4),%ymm0\n  0.42%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d408: vpaddd 0x10(%r10,%rdx,4),%ymm0,%ymm0\n  9.16%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d40f: vmovdqu %ymm0,0x10(%r8,%rdx,4)\n  1.29%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d416: vmovdqu 0x30(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d41d: vpaddd 0x30(%r10,%rdx,4),%ymm0,%ymm0\n  8.47%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d424: vmovdqu %ymm0,0x30(%r8,%rdx,4)\n  0.60%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d42b: vmovdqu 0x50(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d432: vpaddd 0x50(%r10,%rdx,4),%ymm0,%ymm0\n 13.43%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d439: vmovdqu %ymm0,0x50(%r8,%rdx,4)\n  1.04%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d440: vmovdqu 0x70(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d447: vpaddd 0x70(%r10,%rdx,4),%ymm0,%ymm0\n  7.91%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d44e: vmovdqu %ymm0,0x70(%r8,%rdx,4)\n  0.58%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d455: vmovdqu 0x90(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d45f: vpaddd 0x90(%r10,%rdx,4),%ymm0,%ymm0\n  9.66%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d469: vmovdqu %ymm0,0x90(%r8,%rdx,4)\n  0.58%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d473: vmovdqu 0xb0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d47d: vpaddd 0xb0(%r10,%rdx,4),%ymm0,%ymm0\n  9.14%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d487: vmovdqu %ymm0,0xb0(%r8,%rdx,4)\n  0.50%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d491: vmovdqu 0xd0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d49b: vpaddd 0xd0(%r10,%rdx,4),%ymm0,%ymm0\n  9.08%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4a5: vmovdqu %ymm0,0xd0(%r8,%rdx,4)\n  0.83%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4af: vmovdqu 0xf0(%r8,%rdx,4),%ymm0\n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4b9: vpaddd 0xf0(%r10,%rdx,4),%ymm0,%ymm0\n  8.52%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4c3: vmovdqu %ymm0,0xf0(%r8,%rdx,4)\n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n  0.37%      \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4cd: add    $0x40,%edx         \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502                                                \n             \u2502\u2502\u2502  \u2502\u2502\u2502  0x00007f44cc35d4d0: cmp    %ebx,%edx\n             \u2502\u2502\u2502  \u2570\u2502\u2502  0x00007f44cc35d4d2: jl     0x00007f44cc35d401\n  0.15%      \u2502\u2502\u2502   \u2502\u2502  0x00007f44cc35d4d8: mov    %r11d,%r9d\n             \u2502\u2502\u2502   \u2502\u2502  0x00007f44cc35d4db: add    $0xfffffff9,%r9d\n             \u2502\u2502\u2502   \u2502\u2502  0x00007f44cc35d4df: cmp    %r9d,%edx\n             \u2502\u2502\u2502   \u2570\u2502  0x00007f44cc35d4e2: jl     0x00007f44cc35d308\n             \u2502\u2502\u2502    \u2570  0x00007f44cc35d4e8: jmpq   0x00007f44cc35d325 \n             \u2502\u2502\u2502                                                     \n             \u2502\u2502\u2502                                                     \n             \u2198\u2198\u2198       0x00007f44cc35d4ed: mov    $0xffffff7e,%esi\n                       0x00007f44cc35d4f2: mov    %r11d,0x18(%rsp)\n                       0x00007f44cc35d4f7: nop\n                       0x00007f44cc35d4f8: vzeroupper \n....................................................................................................\n 95.13%  &lt;total for region 1&gt;\n<\/code><\/pre>\n\n<p>I haven\u2019t included the perfasm output for the other loops, but the compiled code is also identical.\nIn <code class=\"language-plaintext highlighter-rouge\">reducePre<\/code> and <code class=\"language-plaintext highlighter-rouge\">reducePost<\/code> the loop is unrolled so <code class=\"language-plaintext highlighter-rouge\">i<\/code> is incremented 8 at a time (<code class=\"language-plaintext highlighter-rouge\">add $0x8,%esi<\/code>).\nIn the loops contrived not to be unrolled, the increments are performed one at a time using <code class=\"language-plaintext highlighter-rouge\">inc %ebp<\/code>, but the code is still identical.<\/p>\n\n<p>Even though this is a myth, does it actually cause any harm? \nOf course not, loops optimised under this delusion are compiled the same way. \nThe only negative scenario would be if a misguided team member were to insist on this practice being followed.<\/p>\n\n<h3 id=\"true-once-prefer-stringbuilder-to-string-concatenation\">True once: prefer StringBuilder to string concatenation<\/h3>\n\n<p>A very long time ago, in JDK1.4, when concatenating <code class=\"language-plaintext highlighter-rouge\">String<\/code>s, a <code class=\"language-plaintext highlighter-rouge\">StringBuffer<\/code> was used.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">StringExample<\/span> <span class=\"o\">{<\/span>\n  \n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"nc\">String<\/span> <span class=\"nf\">concatenate<\/span><span class=\"o\">(<\/span><span class=\"nc\">String<\/span> <span class=\"n\">left<\/span><span class=\"o\">,<\/span> <span class=\"nc\">String<\/span> <span class=\"n\">right<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">left<\/span> <span class=\"o\">+<\/span> <span class=\"n\">right<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"nc\">String<\/span> <span class=\"nf\">concatenateStringBuilder<\/span><span class=\"o\">(<\/span><span class=\"nc\">String<\/span> <span class=\"n\">left<\/span><span class=\"o\">,<\/span> <span class=\"nc\">String<\/span> <span class=\"n\">right<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">return<\/span> <span class=\"k\">new<\/span> <span class=\"nf\">StringBuilder<\/span><span class=\"o\">(<\/span><span class=\"n\">left<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">()<\/span> <span class=\"o\">+<\/span> <span class=\"n\">right<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">())<\/span>\n            <span class=\"o\">.<\/span><span class=\"na\">append<\/span><span class=\"o\">(<\/span><span class=\"n\">left<\/span><span class=\"o\">).<\/span><span class=\"na\">append<\/span><span class=\"o\">(<\/span><span class=\"n\">right<\/span><span class=\"o\">).<\/span><span class=\"na\">toString<\/span><span class=\"o\">();<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The top method above would use <code class=\"language-plaintext highlighter-rouge\">StringBuffer<\/code>, which has <code class=\"language-plaintext highlighter-rouge\">synchronized<\/code> methods:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  public static java.lang.String concatenate(java.lang.String, java.lang.String);\n    Code:\n       0: new           #2                  \/\/ class java\/lang\/StringBuffer\n       3: dup\n       4: invokespecial #3                  \/\/ Method java\/lang\/StringBuffer.\"&lt;init&gt;\":()V\n       7: aload_0\n       8: invokevirtual #4                  \/\/ Method java\/lang\/StringBuffer.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuffer;\n      11: aload_1\n      12: invokevirtual #4                  \/\/ Method java\/lang\/StringBuffer.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuffer;\n      15: invokevirtual #5                  \/\/ Method java\/lang\/StringBuffer.toString:()Ljava\/lang\/String;\n      18: areturn\n\n  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);\n    Code:\n       0: new           #6                  \/\/ class java\/lang\/StringBuilder\n       3: dup\n       4: aload_0\n       5: invokevirtual #7                  \/\/ Method java\/lang\/String.length:()I\n       8: aload_1\n       9: invokevirtual #7                  \/\/ Method java\/lang\/String.length:()I\n      12: iadd\n      13: invokespecial #8                  \/\/ Method java\/lang\/StringBuilder.\"&lt;init&gt;\":(I)V\n      16: aload_0\n      17: invokevirtual #9                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      20: aload_1\n      21: invokevirtual #9                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      24: invokevirtual #10                 \/\/ Method java\/lang\/StringBuilder.toString:()Ljava\/lang\/String;\n<\/code><\/pre><\/div><\/div>\n<p>This meant that using a <code class=\"language-plaintext highlighter-rouge\">StringBuilder<\/code> at language level 1.4 would be beneficial.\nIf you learnt Java when 1.4 was commonly used, you might think this is an essential optimisation.<\/p>\n\n<p>Later, the generated bytecode was improved, so that <code class=\"language-plaintext highlighter-rouge\">StringBuilder<\/code> would be used instead.\nIn JDK1.8 (and earlier) the bytecode looks like this:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  public static java.lang.String concatenate(java.lang.String, java.lang.String);\n    Code:\n       0: new           #2                  \/\/ class java\/lang\/StringBuilder\n       3: dup\n       4: invokespecial #3                  \/\/ Method java\/lang\/StringBuilder.\"&lt;init&gt;\":()V\n       7: aload_0\n       8: invokevirtual #4                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      11: aload_1\n      12: invokevirtual #4                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      15: invokevirtual #5                  \/\/ Method java\/lang\/StringBuilder.toString:()Ljava\/lang\/String;\n      18: areturn\n\n  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);\n    Code:\n       0: new           #2                  \/\/ class java\/lang\/StringBuilder\n       3: dup\n       4: aload_0\n       5: invokevirtual #6                  \/\/ Method java\/lang\/String.length:()I\n       8: aload_1\n       9: invokevirtual #6                  \/\/ Method java\/lang\/String.length:()I\n      12: iadd\n      13: invokespecial #7                  \/\/ Method java\/lang\/StringBuilder.\"&lt;init&gt;\":(I)V\n      16: aload_0\n      17: invokevirtual #4                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      20: aload_1\n      21: invokevirtual #4                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      24: invokevirtual #5                  \/\/ Method java\/lang\/StringBuilder.toString:()Ljava\/lang\/String;\n<\/code><\/pre><\/div><\/div>\n<p>Maybe there\u2019s a tiny benefit in sizing the <code class=\"language-plaintext highlighter-rouge\">StringBuilder<\/code>, but now the code is harder to read for marginal gain.\nIn JDK9, <code class=\"language-plaintext highlighter-rouge\">String<\/code> concatenation was reworked, so the <code class=\"language-plaintext highlighter-rouge\">StringBuilder<\/code> code is at a disadvantage to the more readable concatenation code:<\/p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>  public static java.lang.String concatenate(java.lang.String, java.lang.String);\n    Code:\n       0: aload_0\n       1: aload_1\n       2: invokedynamic #2,  0              \/\/ InvokeDynamic #0:makeConcatWithConstants:(Ljava\/lang\/String;Ljava\/lang\/String;)Ljava\/lang\/String;\n       7: areturn\n\n  public static java.lang.String concatenateStringBuilder(java.lang.String, java.lang.String);\n    Code:\n       0: new           #3                  \/\/ class java\/lang\/StringBuilder\n       3: dup\n       4: aload_0\n       5: invokevirtual #4                  \/\/ Method java\/lang\/String.length:()I\n       8: aload_1\n       9: invokevirtual #4                  \/\/ Method java\/lang\/String.length:()I\n      12: iadd\n      13: invokespecial #5                  \/\/ Method java\/lang\/StringBuilder.\"&lt;init&gt;\":(I)V\n      16: aload_0\n      17: invokevirtual #6                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      20: aload_1\n      21: invokevirtual #6                  \/\/ Method java\/lang\/StringBuilder.append:(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;\n      24: invokevirtual #7                  \/\/ Method java\/lang\/StringBuilder.toString:()Ljava\/lang\/String;\n<\/code><\/pre><\/div><\/div>\n\n<p>The <code class=\"language-plaintext highlighter-rouge\">invokedynamic<\/code> bytecode instruction, which can be used to drive better optimisations, is used instead of the <code class=\"language-plaintext highlighter-rouge\">StringBuilder<\/code>.\nThe confusing thing is this only applies when the number of strings is known, so not in loops, but in most cases it\u2019s now better just to use <code class=\"language-plaintext highlighter-rouge\">+<\/code>.\nI won\u2019t 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.<\/p>\n\n<p>In my last post, I tried to avoid putting forward items which might become untrue in the future, but didn\u2019t quite manage this.\nI predict that four of the five items will stand the test of time, but item three (avoid iterating over <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code>) may eventually become a myth if <a href=\"https:\/\/openjdk.java.net\/jeps\/8261007\">frozen arrays<\/a> become a reality in the future.<\/p>\n\n<h3 id=\"continuous-profiling\">Continuous profiling<\/h3>\n\n<p>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.\nThere are more important factors to system efficiency than low-level programming details.\nFor instance, being required by specification to do something wasteful by an external system is a problem which should be solved architecturally.\nIf architectural blunders create bottlenecks, it\u2019s very costly to fix.<\/p>\n\n<p>Assuming a sensible architecture, the causes of many costs are neither dictated by nor are observable to external systems, so can be changed easily. \nEfficiency at this level depends on choices of algorithm, data structure, and level of mechanical sympathy.\nThese kinds of problems can be surfaced by using continuous profiling, and are often surprising.\nContinuous profiling, by its nature, tends to highlight what\u2019s important to reducing overall cost, and filters out distracting and obvious fixed costs in favour of unexpected unit costs.\nMy impression is that many engineers believe profiling to be too expensive to run in production, but I think these engineers\u2019 minds would be blown by how much CPU time is spent collecting logs, metrics, and traces if they tried it.\nThe 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.<\/p>\n\n<p>I think a diagram, drawn by Aleksey Shipil\u0451v several years ago, explains where continuous profiling is useful:<\/p>\n\n<blockquote class=\"twitter-tweet\"><p lang=\"en\" dir=\"ltr\">I relax by drawing stuff on my large whiteboard. Here&#39;s &quot;Perf Work Phase Diagram&quot; for you. <a href=\"http:\/\/t.co\/RnITiLoNFH\">pic.twitter.com\/RnITiLoNFH<\/a><\/p>&mdash; Aleksey Shipil\u00ebv (@shipilev) <a href=\"https:\/\/twitter.com\/shipilev\/status\/578193813946134529?ref_src=twsrc%5Etfw\">March 18, 2015<\/a><\/blockquote>\n<script async=\"\" src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script>\n\n<p>Essentially, continuous profiling will help you get from an <em>Amazingly stupid program<\/em> to <em>Beauty Peak<\/em> by making it obvious where the costs are; the majority of programs needn\u2019t go beyond <em>Beauty Peak<\/em>.\nThis process doesn\u2019t consist entirely of making the kinds of mundane point substitutions in my last post.\nIt\u2019s 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.<\/p>\n\n<p>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.\nWhen there\u2019s no increase in complexity incurred in using a more efficient idiom, there\u2019s no need to wait for its necessity to be proven by a profiler.\nIn any case, continuous profiling should help calibrate your sense of which idioms are costly, and avoid the myths.<\/p>","author":{"name":{}},"category":{"@attributes":{"term":"java"}},"summary":"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."},{"title":"5 Mundane Java Performance Tips","link":{"@attributes":{"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","updated":"2021-11-28T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/5-java-mundane-performance-tricks","content":"<p>Most of the time it isn\u2019t really necessary to <em>optimise<\/em> software, but this post contains 5 tips to avoid making software written in Java slower for the sake of it.<\/p>\n\n<blockquote>\n  <p><strong>How to read this post:<\/strong> it didn\u2019t take long for the term <em>premature optimisation<\/em> 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\u2019t write during my career, and all of these things have shown up in profiles several times. That is, these things <strong>do<\/strong> 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\u2019t <strong>need<\/strong> to be resizing <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code>s at least once per request if you have the information to size them properly in hand. If you can, size the <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code> properly, and it will never become a bottleneck showing up in other people\u2019s profiles down the line.<\/p>\n<\/blockquote>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#size-hashmaps-whenever-possible\" id=\"markdown-toc-size-hashmaps-whenever-possible\">Size HashMaps whenever possible<\/a><\/li>\n  <li><a href=\"#use-wrappers-for-composite-hashmap-keys\" id=\"markdown-toc-use-wrappers-for-composite-hashmap-keys\">Use wrappers for composite HashMap keys<\/a><\/li>\n  <li><a href=\"#dont-iterate-over-enumvalues\" id=\"markdown-toc-dont-iterate-over-enumvalues\">Don\u2019t iterate over Enum.values()<\/a><\/li>\n  <li><a href=\"#use-enums-instead-of-constant-strings\" id=\"markdown-toc-use-enums-instead-of-constant-strings\">Use enums instead of constant Strings<\/a><\/li>\n  <li><a href=\"#stop-using-jdk8\" id=\"markdown-toc-stop-using-jdk8\">Stop using JDK8<\/a><\/li>\n<\/ol>\n\n<h3 id=\"size-hashmaps-whenever-possible\">Size HashMaps whenever possible<\/h3>\n\n<p>Even if most of its operations are quite fast, resizing <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code>s is slow and hard to optimise, so the size should be calculated before building the map.\nThe 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.<\/p>\n\n<p>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.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">HashMapResize<\/span> <span class=\"o\">{<\/span>\n  \n    <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"10\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"14\"<\/span><span class=\"o\">})<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">keys<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"16\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"24\"<\/span><span class=\"o\">})<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">capacity<\/span><span class=\"o\">;<\/span>\n    \n    <span class=\"nd\">@Benchmark<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">Integer<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">&gt;<\/span> <span class=\"nf\">loadHashMap<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">Integer<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">map<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;&gt;(<\/span><span class=\"n\">capacity<\/span><span class=\"o\">);<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">keys<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"n\">i<\/span><span class=\"o\">,<\/span> <span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">map<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>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.\nTo 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.\nSince 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.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: capacity<\/th>\n        <th>Param: keys<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>HashMapResize.loadHashMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>191.729672<\/td>\n        <td>9.744065<\/td>\n        <td>ns\/op<\/td>\n        <td>16<\/td>\n        <td>10<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>448.000079<\/td>\n        <td>0.000010<\/td>\n        <td>B\/op<\/td>\n        <td>16<\/td>\n        <td>10<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>330.267466<\/td>\n        <td>14.739395<\/td>\n        <td>ns\/op<\/td>\n        <td>16<\/td>\n        <td>14<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>720.000139<\/td>\n        <td>0.000025<\/td>\n        <td>B\/op<\/td>\n        <td>16<\/td>\n        <td>14<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>221.798264<\/td>\n        <td>97.696476<\/td>\n        <td>ns\/op<\/td>\n        <td>24<\/td>\n        <td>10<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>512.000092<\/td>\n        <td>0.000038<\/td>\n        <td>B\/op<\/td>\n        <td>24<\/td>\n        <td>10<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>292.934060<\/td>\n        <td>13.489139<\/td>\n        <td>ns\/op<\/td>\n        <td>24<\/td>\n        <td>14<\/td>\n      <\/tr>\n      <tr>\n        <td>HashMapResize.loadHashMap:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>640.000121<\/td>\n        <td>0.000016<\/td>\n        <td>B\/op<\/td>\n        <td>24<\/td>\n        <td>14<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>It takes 70% longer to put 14 keys into a <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code> with capacity 16 than it does to insert 10 keys, and allocation rate per constructed map is 60% higher.\nIncreasing the capacity to 24 saves 11% in allocation rate and 34% in build time per <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code> by avoiding a resize in this benchmark.\nExact 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.<\/p>\n\n<h3 id=\"use-wrappers-for-composite-hashmap-keys\">Use wrappers for composite HashMap keys<\/h3>\n\n<p>Whenever a <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code> has composite <code class=\"language-plaintext highlighter-rouge\">String<\/code> keys, use a wrapper instead of concatenating the strings to make a key.\nDoing so will make the lookup much faster and reduce allocation rate, as the benchmark below demonstrates.\nTwo maps, one with concatenated keys and the other with keys in a <code class=\"language-plaintext highlighter-rouge\">Pair<\/code> object are constructed so lookup time can be compared.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">CompositeLookup<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"nd\">@Param<\/span><span class=\"o\">(<\/span><span class=\"s\">\"1024\"<\/span><span class=\"o\">)<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nc\">Map<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">String<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Object<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">concatMap<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">Map<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">Pair<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Object<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">pairMap<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">String<\/span><span class=\"o\">[]<\/span> <span class=\"n\">prefixes<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">String<\/span><span class=\"o\">[]<\/span> <span class=\"n\">suffixes<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">prefixes<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n      <span class=\"n\">suffixes<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n      <span class=\"n\">concatMap<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;&gt;();<\/span>\n      <span class=\"n\">pairMap<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;&gt;();<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">prefixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"no\">UUID<\/span><span class=\"o\">.<\/span><span class=\"na\">randomUUID<\/span><span class=\"o\">().<\/span><span class=\"na\">toString<\/span><span class=\"o\">();<\/span>\n        <span class=\"n\">suffixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"no\">UUID<\/span><span class=\"o\">.<\/span><span class=\"na\">randomUUID<\/span><span class=\"o\">().<\/span><span class=\"na\">toString<\/span><span class=\"o\">();<\/span>\n        <span class=\"n\">concatMap<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"n\">prefixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">+<\/span> <span class=\"s\">\";\"<\/span> <span class=\"o\">+<\/span> <span class=\"n\">suffixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">],<\/span> <span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n        <span class=\"c1\">\/\/ use new String to avoid reference equality speeding up the equals calls <\/span>\n        <span class=\"n\">pairMap<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"k\">new<\/span> <span class=\"nc\">Pair<\/span><span class=\"o\">(<\/span><span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">(<\/span><span class=\"n\">prefixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]),<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">(<\/span><span class=\"n\">suffixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">])),<\/span> <span class=\"n\">i<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Benchmark<\/span>\n    <span class=\"nd\">@OperationsPerInvocation<\/span><span class=\"o\">(<\/span><span class=\"mi\">1024<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">concatenate<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span> \n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">prefixes<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">concatMap<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">prefixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">+<\/span> <span class=\"s\">\";\"<\/span> <span class=\"o\">+<\/span> <span class=\"n\">suffixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]));<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Benchmark<\/span>\n    <span class=\"nd\">@OperationsPerInvocation<\/span><span class=\"o\">(<\/span><span class=\"mi\">1024<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">wrap<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">prefixes<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">pairMap<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"k\">new<\/span> <span class=\"nc\">Pair<\/span><span class=\"o\">(<\/span><span class=\"n\">prefixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">],<\/span> <span class=\"n\">suffixes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">])));<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>CompositeLookup.concatenate<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>158.160452<\/td>\n        <td>5.622590<\/td>\n        <td>ns\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>CompositeLookup.concatenate:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>120.000066<\/td>\n        <td>0.000010<\/td>\n        <td>B\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>CompositeLookup.wrap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>43.550144<\/td>\n        <td>0.932947<\/td>\n        <td>ns\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>CompositeLookup.wrap:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>24.000018<\/td>\n        <td>0.000003<\/td>\n        <td>B\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Concatenating the keys takes 3.7x longer per lookup and allocates five times more (this depends on the size of the keys though).\nThe reason wrapping outperforms concatenation is that a <code class=\"language-plaintext highlighter-rouge\">String<\/code> instance caches its hash code, and constructing a new <code class=\"language-plaintext highlighter-rouge\">String<\/code> requires calculation of a new hash code and <code class=\"language-plaintext highlighter-rouge\">String<\/code>\u2019s hash code algorithm also isn\u2019t very efficient. \nThe difference is actually huge, and I have seen this make a big difference in real applications many times - this idiom should be more common.<\/p>\n\n<p><a href=\"https:\/\/twitter.com\/VladimirSitnikv\">Vladimir Sitnikov<\/a> <a href=\"https:\/\/github.com\/spring-projects\/spring-framework\/pull\/913\">points out<\/a> that this pattern turned up as a bottleneck in Spring framework.<\/p>\n\n<h3 id=\"dont-iterate-over-enumvalues\">Don\u2019t iterate over Enum.values()<\/h3>\n\n<p>An array is allocated every time <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code> is called, which can really add up.\nIf you own the code, and the class won\u2019t 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 <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code>, on the basis that your own code won\u2019t mutate it.\nOtherwise it can be stashed in a local variable wherever needed, which should alleviate concerns about mutability.<\/p>\n\n<p>The benchmark below compares iteration over <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code>, a preallocated array populated from <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code> and over an <code class=\"language-plaintext highlighter-rouge\">EnumSet<\/code> for enums of different sizes.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">EnumIterationBenchmark<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">valuesFour<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Four<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Four<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">valuesEight<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Eight<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Eight<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">valuesSixteen<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Sixteen<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Sixteen<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">())<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">cachedFour<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Four<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Four<\/span><span class=\"o\">.<\/span><span class=\"na\">VALUES<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">cachedEight<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Eight<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Eight<\/span><span class=\"o\">.<\/span><span class=\"na\">VALUES<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">cachedSixteen<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Sixteen<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">Sixteen<\/span><span class=\"o\">.<\/span><span class=\"na\">VALUES<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">enumSetFour<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Four<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">EnumSet<\/span><span class=\"o\">.<\/span><span class=\"na\">allOf<\/span><span class=\"o\">(<\/span><span class=\"nc\">Four<\/span><span class=\"o\">.<\/span><span class=\"na\">class<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">enumSetEight<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Eight<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">EnumSet<\/span><span class=\"o\">.<\/span><span class=\"na\">allOf<\/span><span class=\"o\">(<\/span><span class=\"nc\">Eight<\/span><span class=\"o\">.<\/span><span class=\"na\">class<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">enumSetSixteen<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">Sixteen<\/span> <span class=\"n\">it<\/span> <span class=\"o\">:<\/span> <span class=\"nc\">EnumSet<\/span><span class=\"o\">.<\/span><span class=\"na\">allOf<\/span><span class=\"o\">(<\/span><span class=\"nc\">Sixteen<\/span><span class=\"o\">.<\/span><span class=\"na\">class<\/span><span class=\"o\">))<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">it<\/span><span class=\"o\">.<\/span><span class=\"na\">ordinal<\/span><span class=\"o\">());<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p><img src=\"\/assets\/2021\/11\/5-mundane-java-performance-tips\/enumit.png\" alt=\"enum iteration\" \/><\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>EnumIterationBenchmark.cachedEight<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>28.644844<\/td>\n        <td>0.171543<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.cachedEight:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>0.000012<\/td>\n        <td>0.000000<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.cachedFour<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>14.774474<\/td>\n        <td>0.072698<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.cachedFour:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>0.000006<\/td>\n        <td>0.000000<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.cachedSixteen<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>56.613368<\/td>\n        <td>0.296960<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.cachedSixteen:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>0.000023<\/td>\n        <td>0.000000<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetEight<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>53.622149<\/td>\n        <td>1.782352<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetEight:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>32.000020<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetFour<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>29.840317<\/td>\n        <td>1.830070<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetFour:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>32.000011<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetSixteen<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>105.247332<\/td>\n        <td>49.049506<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.enumSetSixteen:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>32.000040<\/td>\n        <td>0.000018<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesEight<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>33.237779<\/td>\n        <td>2.077222<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesEight:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>48.000014<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesFour<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>16.610465<\/td>\n        <td>0.906318<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesFour:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>32.000007<\/td>\n        <td>0.000000<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesSixteen<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>56.557226<\/td>\n        <td>3.012126<\/td>\n        <td>ns\/op<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumIterationBenchmark.valuesSixteen:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>80.000023<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Calling <code class=\"language-plaintext highlighter-rouge\">Enum.values()<\/code> will allocate an easily avoidable 80 bytes per call for a 16 element enum, though doesn\u2019t pose a time penalty.\n<code class=\"language-plaintext highlighter-rouge\">EnumSet<\/code>s are tiny, so using them reduces allocation rate, but the iteration code is slower than iterating over an array so they don\u2019t offer a good stand in.<\/p>\n\n<p>I found a <a href=\"https:\/\/github.com\/spring-projects\/spring-framework\/issues\/26842\">case<\/a> of this which had existed in Spring framework for over a decade earlier in the year, where resolving HTTP status code to Spring\u2019s <code class=\"language-plaintext highlighter-rouge\">HttpStatus<\/code> enum was allocating ~1MB\/s at very low request rate.<\/p>\n\n<h3 id=\"use-enums-instead-of-constant-strings\">Use enums instead of constant Strings<\/h3>\n\n<p>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 <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code> is fast, <code class=\"language-plaintext highlighter-rouge\">EnumMap<\/code> is faster.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">EnumMapBenchmark<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">abstract<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">BaseState<\/span> <span class=\"o\">{<\/span>\n    <span class=\"nd\">@Param<\/span><span class=\"o\">(<\/span><span class=\"s\">\"10000\"<\/span><span class=\"o\">)<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Param<\/span><span class=\"o\">(<\/span><span class=\"s\">\"42\"<\/span><span class=\"o\">)<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">seed<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"nc\">SplittableRandom<\/span> <span class=\"n\">random<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">SplittableRandom<\/span><span class=\"o\">(<\/span><span class=\"n\">seed<\/span><span class=\"o\">);<\/span>\n      <span class=\"n\">randomValues<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">int<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">randomValues<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">random<\/span><span class=\"o\">.<\/span><span class=\"na\">nextInt<\/span><span class=\"o\">(<\/span><span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Integer<\/span><span class=\"o\">.<\/span><span class=\"na\">MAX_VALUE<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"n\">fill<\/span><span class=\"o\">(<\/span><span class=\"n\">randomValues<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">abstract<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">fill<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">EnumMapState<\/span> <span class=\"kd\">extends<\/span> <span class=\"nc\">BaseState<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"nc\">EnumMap<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">AnEnum<\/span><span class=\"o\">,<\/span> <span class=\"nc\">String<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">map<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">AnEnum<\/span><span class=\"o\">[]<\/span> <span class=\"n\">values<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kt\">void<\/span> <span class=\"nf\">fill<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">map<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">EnumMap<\/span><span class=\"o\">&lt;&gt;(<\/span><span class=\"nc\">AnEnum<\/span><span class=\"o\">.<\/span><span class=\"na\">class<\/span><span class=\"o\">);<\/span>\n      <span class=\"n\">values<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">AnEnum<\/span><span class=\"o\">[<\/span><span class=\"n\">randomValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">];<\/span>\n      <span class=\"nc\">AnEnum<\/span><span class=\"o\">[]<\/span> <span class=\"n\">enumValues<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">AnEnum<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">();<\/span>\n      <span class=\"kt\">int<\/span> <span class=\"n\">pos<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">:<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"n\">pos<\/span><span class=\"o\">++]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">%<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">];<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">AnEnum<\/span> <span class=\"n\">value<\/span> <span class=\"o\">:<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">,<\/span> <span class=\"no\">UUID<\/span><span class=\"o\">.<\/span><span class=\"na\">randomUUID<\/span><span class=\"o\">().<\/span><span class=\"na\">toString<\/span><span class=\"o\">());<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">MixedState<\/span> <span class=\"kd\">extends<\/span> <span class=\"nc\">BaseState<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"nc\">EnumMap<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">AnEnum<\/span><span class=\"o\">,<\/span> <span class=\"nc\">String<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">map<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">String<\/span><span class=\"o\">[]<\/span> <span class=\"n\">values<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kt\">void<\/span> <span class=\"nf\">fill<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">map<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">EnumMap<\/span><span class=\"o\">&lt;&gt;(<\/span><span class=\"nc\">AnEnum<\/span><span class=\"o\">.<\/span><span class=\"na\">class<\/span><span class=\"o\">);<\/span>\n      <span class=\"n\">values<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">[<\/span><span class=\"n\">randomValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">];<\/span>\n      <span class=\"nc\">AnEnum<\/span><span class=\"o\">[]<\/span> <span class=\"n\">enumValues<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">AnEnum<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">();<\/span>\n      <span class=\"kt\">int<\/span> <span class=\"n\">pos<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">:<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"n\">pos<\/span><span class=\"o\">++]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">%<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">].<\/span><span class=\"na\">toString<\/span><span class=\"o\">();<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">AnEnum<\/span> <span class=\"n\">value<\/span> <span class=\"o\">:<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">,<\/span> <span class=\"no\">UUID<\/span><span class=\"o\">.<\/span><span class=\"na\">randomUUID<\/span><span class=\"o\">().<\/span><span class=\"na\">toString<\/span><span class=\"o\">());<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kd\">static<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">HashMapState<\/span> <span class=\"kd\">extends<\/span> <span class=\"nc\">BaseState<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;<\/span><span class=\"nc\">String<\/span><span class=\"o\">,<\/span> <span class=\"nc\">String<\/span><span class=\"o\">&gt;<\/span> <span class=\"n\">map<\/span><span class=\"o\">;<\/span>\n    <span class=\"nc\">String<\/span><span class=\"o\">[]<\/span> <span class=\"n\">values<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Override<\/span>\n    <span class=\"kt\">void<\/span> <span class=\"nf\">fill<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span><span class=\"o\">[]<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">map<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">HashMap<\/span><span class=\"o\">&lt;&gt;();<\/span>\n      <span class=\"n\">values<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">[<\/span><span class=\"n\">randomValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">];<\/span>\n      <span class=\"nc\">AnEnum<\/span><span class=\"o\">[]<\/span> <span class=\"n\">enumValues<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">AnEnum<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">();<\/span>\n      <span class=\"kt\">int<\/span> <span class=\"n\">pos<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">:<\/span> <span class=\"n\">randomValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">values<\/span><span class=\"o\">[<\/span><span class=\"n\">pos<\/span><span class=\"o\">++]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span> <span class=\"o\">%<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">].<\/span><span class=\"na\">toString<\/span><span class=\"o\">();<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">AnEnum<\/span> <span class=\"n\">value<\/span> <span class=\"o\">:<\/span> <span class=\"n\">enumValues<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">put<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">.<\/span><span class=\"na\">toString<\/span><span class=\"o\">(),<\/span> <span class=\"no\">UUID<\/span><span class=\"o\">.<\/span><span class=\"na\">randomUUID<\/span><span class=\"o\">().<\/span><span class=\"na\">toString<\/span><span class=\"o\">());<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">enumMap<\/span><span class=\"o\">(<\/span><span class=\"nc\">EnumMapState<\/span> <span class=\"n\">state<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">AnEnum<\/span> <span class=\"n\">value<\/span> <span class=\"o\">:<\/span> <span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">));<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">hashMap<\/span><span class=\"o\">(<\/span><span class=\"nc\">HashMapState<\/span> <span class=\"n\">state<\/span><span class=\"o\">,<\/span> <span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"nc\">String<\/span> <span class=\"n\">value<\/span> <span class=\"o\">:<\/span> <span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">values<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">state<\/span><span class=\"o\">.<\/span><span class=\"na\">map<\/span><span class=\"o\">.<\/span><span class=\"na\">get<\/span><span class=\"o\">(<\/span><span class=\"n\">value<\/span><span class=\"o\">));<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: seed<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>EnumMapBenchmark.enumMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>65.225800<\/td>\n        <td>7.990521<\/td>\n        <td>us\/op<\/td>\n        <td>42<\/td>\n        <td>10000<\/td>\n      <\/tr>\n      <tr>\n        <td>EnumMapBenchmark.hashMap<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>151.394872<\/td>\n        <td>3.564463<\/td>\n        <td>us\/op<\/td>\n        <td>42<\/td>\n        <td>10000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>Naturally, there is a one time conversion cost to produce the <code class=\"language-plaintext highlighter-rouge\">enum<\/code> (which ironically uses a <code class=\"language-plaintext highlighter-rouge\">HashMap<\/code>) but once that\u2019s been done it will pay for itself by allowing the use of <code class=\"language-plaintext highlighter-rouge\">EnumMap<\/code> (and <code class=\"language-plaintext highlighter-rouge\">EnumSet<\/code>) unless the value is basically inert.\nDon\u2019t contort your code or risk being unable to consume data you don\u2019t control just because <code class=\"language-plaintext highlighter-rouge\">EnumMap<\/code> is fast, but if your data naturally fits an <code class=\"language-plaintext highlighter-rouge\">enum<\/code>, then be sure to make use of <code class=\"language-plaintext highlighter-rouge\">EnumMap<\/code> and <code class=\"language-plaintext highlighter-rouge\">EnumSet<\/code>.<\/p>\n\n<h3 id=\"stop-using-jdk8\">Stop using JDK8<\/h3>\n\n<p>All Java applications use a lot of <code class=\"language-plaintext highlighter-rouge\">String<\/code>s, and <code class=\"language-plaintext highlighter-rouge\">String<\/code> was just a lot fatter and slower in JDK8.\nFor example, consider constructing <code class=\"language-plaintext highlighter-rouge\">String<\/code>s from ASCII encoded <code class=\"language-plaintext highlighter-rouge\">byte[]<\/code>s, which happens a lot if you do things like parse JSON or load classes.<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">UTF8Benchmark<\/span> <span class=\"o\">{<\/span>\n\n    <span class=\"nd\">@Param<\/span><span class=\"o\">(<\/span><span class=\"s\">\"UTF-8\"<\/span><span class=\"o\">)<\/span>\n    <span class=\"nc\">String<\/span> <span class=\"n\">charsetName<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"4\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"20\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"200\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"2000\"<\/span><span class=\"o\">})<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nc\">Charset<\/span> <span class=\"n\">charset<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"n\">bytes<\/span><span class=\"o\">;<\/span>\n    <span class=\"kd\">private<\/span> <span class=\"nc\">String<\/span> <span class=\"n\">string<\/span><span class=\"o\">;<\/span>\n\n    <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">charset<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">Charset<\/span><span class=\"o\">.<\/span><span class=\"na\">forName<\/span><span class=\"o\">(<\/span><span class=\"n\">charsetName<\/span><span class=\"o\">);<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">bytes<\/span> <span class=\"o\">=<\/span> <span class=\"n\">asciiBytes<\/span><span class=\"o\">(<\/span><span class=\"n\">size<\/span><span class=\"o\">);<\/span>\n      <span class=\"k\">this<\/span><span class=\"o\">.<\/span><span class=\"na\">string<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">String<\/span><span class=\"o\">(<\/span><span class=\"n\">bytes<\/span><span class=\"o\">,<\/span> <span class=\"n\">charset<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"nd\">@Benchmark<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"nc\">String<\/span> <span class=\"nf\">stringFromBytes<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"k\">new<\/span> <span class=\"nf\">String<\/span><span class=\"o\">(<\/span><span class=\"n\">bytes<\/span><span class=\"o\">,<\/span> <span class=\"n\">charset<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n   \n    <span class=\"nd\">@Benchmark<\/span>\n    <span class=\"kd\">public<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"nf\">bytesFromString<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">string<\/span><span class=\"o\">.<\/span><span class=\"na\">getBytes<\/span><span class=\"o\">(<\/span><span class=\"n\">charset<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n\n    <span class=\"kd\">private<\/span> <span class=\"kd\">static<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"nf\">asciiByted<\/span><span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"n\">bytes<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n      <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">bytes<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"o\">(<\/span><span class=\"kt\">byte<\/span><span class=\"o\">)<\/span> <span class=\"o\">(<\/span><span class=\"n\">i<\/span> <span class=\"o\">&amp;<\/span> <span class=\"mh\">0x7F<\/span><span class=\"o\">);<\/span>\n      <span class=\"o\">}<\/span>\n      <span class=\"k\">return<\/span> <span class=\"n\">bytes<\/span><span class=\"o\">;<\/span>\n    <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>The benchmark was run on JDK8 and JDK11.\nASCII <code class=\"language-plaintext highlighter-rouge\">String<\/code>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.<\/p>\n\n<p><img src=\"\/assets\/2021\/11\/5-mundane-java-performance-tips\/decode-time.png\" alt=\"UTF-8 decode time\" \/>\n<img src=\"\/assets\/2021\/11\/5-mundane-java-performance-tips\/decode-allocation.png\" alt=\"UTF-8 decode allocation\" \/><\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>JDK<\/th>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: charsetName<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>18.355847<\/td>\n        <td>0.674115<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>51.693575<\/td>\n        <td>1.610212<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>48.000008<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>88.000023<\/td>\n        <td>0.000003<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>20.827067<\/td>\n        <td>3.695586<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>60.232549<\/td>\n        <td>1.767569<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>64.000009<\/td>\n        <td>0.000002<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>120.000026<\/td>\n        <td>0.000004<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>34.451863<\/td>\n        <td>9.095702<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>159.427489<\/td>\n        <td>8.422851<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>240.000014<\/td>\n        <td>0.000004<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>480.000070<\/td>\n        <td>0.000011<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>285.590238<\/td>\n        <td>75.077635<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1350.229058<\/td>\n        <td>69.483579<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2040.000118<\/td>\n        <td>0.000030<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.stringFromBytes:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>4080.000601<\/td>\n        <td>0.000117<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>It\u2019s a similar story when encoding <code class=\"language-plaintext highlighter-rouge\">String<\/code>s to <code class=\"language-plaintext highlighter-rouge\">byte[]<\/code> too, which is something your application will do if it does any kind of serialization, logging, or tracing.<\/p>\n\n<p><img src=\"\/assets\/2021\/11\/5-mundane-java-performance-tips\/encode-time.png\" alt=\"UTF-8 encode time\" \/>\n<img src=\"\/assets\/2021\/11\/5-mundane-java-performance-tips\/encode-allocation.png\" alt=\"UTF-8 encode allocation\" \/><\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>JDK<\/th>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: charsetName<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>13.835438<\/td>\n        <td>0.661028<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>62.958213<\/td>\n        <td>1.832734<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>24.000006<\/td>\n        <td>0.000001<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>160.000028<\/td>\n        <td>0.000004<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>4<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>17.457885<\/td>\n        <td>2.175969<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>75.760063<\/td>\n        <td>3.000610<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>40.000007<\/td>\n        <td>0.000002<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>224.000033<\/td>\n        <td>0.000004<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>20<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>30.758900<\/td>\n        <td>8.336043<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>227.269922<\/td>\n        <td>18.536450<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>216.000013<\/td>\n        <td>0.000003<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>936.000100<\/td>\n        <td>0.000014<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>200<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>280.886995<\/td>\n        <td>61.136388<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2138.703149<\/td>\n        <td>203.825640<\/td>\n        <td>ns\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>11<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2016.000116<\/td>\n        <td>0.000024<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n      <tr>\n        <td>8<\/td>\n        <td>UTF8Benchmark.bytesFromString:\u00b7gc.alloc.rate.norm<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>8136.000937<\/td>\n        <td>0.000113<\/td>\n        <td>B\/op<\/td>\n        <td>UTF-8<\/td>\n        <td>2000<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p>These operations are so fundamental that it\u2019s impossible to escape them.<\/p>","author":{"name":{}},"category":{"@attributes":{"term":"java"}},"summary":"Most of the time it isn\u2019t really necessary to optimise software, but this post contains 5 tips to avoid making software written in Java slower for the sake of it."},{"title":"Loop Fission","link":{"@attributes":{"href":"https:\/\/richardstartin.github.io\/posts\/loop-fission","rel":"alternate","type":"text\/html","title":"Loop Fission"}},"published":"2021-11-27T00:00:00+00:00","updated":"2021-11-27T00:00:00+00:00","id":"https:\/\/richardstartin.github.io\/posts\/loop-fission","content":"<p>Loop fission is a process normally applied by a compiler to loops to make them faster.\nThe 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. \nAs far as I\u2019m aware, C2, Hotspot\u2019s JIT compiler, doesn\u2019t do this so you have to do it yourself.\nI came across a couple of cases where this was profitable recently, and this post uses these as examples.<\/p>\n\n<ol id=\"markdown-toc\">\n  <li><a href=\"#xor\" id=\"markdown-toc-xor\">XOR<\/a><\/li>\n  <li><a href=\"#group-by\" id=\"markdown-toc-group-by\">Group By<\/a><\/li>\n<\/ol>\n\n<h3 id=\"xor\">XOR<\/h3>\n\n<p>One of the operations which can be performed between two <a href=\"https:\/\/github.com\/RoaringBitmap\/RoaringBitmap\"><code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code><\/a> objects is an XOR or symmetric difference.\nAfter 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.\nOne of the <a href=\"\/posts\/a-quick-look-at-roaringbitmap\">three container types<\/a> in a <code class=\"language-plaintext highlighter-rouge\">RoaringBitmap<\/code> is a <code class=\"language-plaintext highlighter-rouge\">BitmapContainer<\/code> which contains a bitmap, a dense collection of bits.\nWhen a destructive XOR is applied between two <code class=\"language-plaintext highlighter-rouge\">BitmapContainer<\/code>s, the mutated container must have its bits updated, but they also need to be counted.\nThis can be done in one loop or two separate loops, as in the representative benchmark below:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">XorCount<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"256\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"512\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"1024\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"2048\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"4096\"<\/span><span class=\"o\">})<\/span>\n  <span class=\"kt\">int<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">left<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[]<\/span> <span class=\"n\">right<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"n\">left<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n    <span class=\"n\">right<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">long<\/span><span class=\"o\">[<\/span><span class=\"n\">size<\/span><span class=\"o\">];<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">size<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">left<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">ThreadLocalRandom<\/span><span class=\"o\">.<\/span><span class=\"na\">current<\/span><span class=\"o\">().<\/span><span class=\"na\">nextLong<\/span><span class=\"o\">();<\/span>\n      <span class=\"n\">right<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"nc\">ThreadLocalRandom<\/span><span class=\"o\">.<\/span><span class=\"na\">current<\/span><span class=\"o\">().<\/span><span class=\"na\">nextLong<\/span><span class=\"o\">();<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">fused<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">count<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">left<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">right<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">left<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">^=<\/span> <span class=\"n\">right<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n      <span class=\"n\">count<\/span> <span class=\"o\">+=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">bitCount<\/span><span class=\"o\">(<\/span><span class=\"n\">left<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">count<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">int<\/span> <span class=\"nf\">fissured<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">left<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">right<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">left<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">^=<\/span> <span class=\"n\">right<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">count<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">l<\/span> <span class=\"o\">:<\/span> <span class=\"n\">left<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">count<\/span> <span class=\"o\">+=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">bitCount<\/span><span class=\"o\">(<\/span><span class=\"n\">l<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">count<\/span><span class=\"o\">;<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>It seems intuitive that <code class=\"language-plaintext highlighter-rouge\">fused<\/code> would be faster; there is only one pass over the data, but this isn\u2019t necessarily the case.<\/p>\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: size<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>XorCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>146.324870<\/td>\n        <td>0.166643<\/td>\n        <td>ns\/op<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>283.583423<\/td>\n        <td>0.156367<\/td>\n        <td>ns\/op<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>564.507047<\/td>\n        <td>0.512299<\/td>\n        <td>ns\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1154.083245<\/td>\n        <td>6.005591<\/td>\n        <td>ns\/op<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2805.933619<\/td>\n        <td>10.880235<\/td>\n        <td>ns\/op<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>229.713707<\/td>\n        <td>0.138824<\/td>\n        <td>ns\/op<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>462.435821<\/td>\n        <td>1.015399<\/td>\n        <td>ns\/op<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>908.624781<\/td>\n        <td>0.759116<\/td>\n        <td>ns\/op<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1802.717233<\/td>\n        <td>3.106054<\/td>\n        <td>ns\/op<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>XorCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>3585.279127<\/td>\n        <td>6.716832<\/td>\n        <td>ns\/op<\/td>\n        <td>4096<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2021\/11\/loop-fission\/xorcount.png\" alt=\"XorCount\" \/><\/p>\n\n<p>For small sizes, the fissured loop is noticeably faster, with convergence as the lengths grow. \nEventually, 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 <code class=\"language-plaintext highlighter-rouge\">BitmapContainer<\/code> the length is fixed to 1024 elements, so this is immaterial.<\/p>\n\n<p>Why is the fissured loop faster?\nThis is because of the loop which performs the XOR operation:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">left<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">right<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">left<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">^=<\/span> <span class=\"n\">right<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n    <span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This loop can be autovectorised - many XORs can be computed in a single instruction. \nOn my laptop, running JDK11 on Ubuntu, 256 bits or 4 <code class=\"language-plaintext highlighter-rouge\">long<\/code>s are operated on from each array at a time.<\/p>\n\n<pre><code class=\"language-asm\">  1.11%  \u2502    \u2502\u2502\u2502 \u2502\u2197      \u2502\u2502  0x00007f1bc835b892: vmovdqu 0x10(%rdi,%rdx,8),%ymm0\n  0.31%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b898: vpxor  0x10(%r11,%rdx,8),%ymm0,%ymm0\n  0.40%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b89f: vmovdqu %ymm0,0x10(%r11,%rdx,8)\n  1.09%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8a6: vmovdqu 0x30(%rdi,%rdx,8),%ymm0\n  0.44%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8ac: vpxor  0x30(%r11,%rdx,8),%ymm0,%ymm0\n  1.69%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8b3: vmovdqu %ymm0,0x30(%r11,%rdx,8)\n  0.42%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8ba: vmovdqu 0x50(%rdi,%rdx,8),%ymm0\n  0.31%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8c0: vpxor  0x50(%r11,%rdx,8),%ymm0,%ymm0\n  0.77%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8c7: vmovdqu %ymm0,0x50(%r11,%rdx,8)\n  0.54%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8ce: vmovdqu 0x70(%rdi,%rdx,8),%ymm0\n  0.42%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8d4: vpxor  0x70(%r11,%rdx,8),%ymm0,%ymm0\n  2.28%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8db: vmovdqu %ymm0,0x70(%r11,%rdx,8)\n  0.33%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8e2: vmovdqu 0x90(%rdi,%rdx,8),%ymm0\n  0.46%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8eb: vpxor  0x90(%r11,%rdx,8),%ymm0,%ymm0\n  1.13%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8f5: vmovdqu %ymm0,0x90(%r11,%rdx,8)\n  0.19%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b8ff: vmovdqu 0xb0(%rdi,%rdx,8),%ymm0\n  0.36%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b908: vpxor  0xb0(%r11,%rdx,8),%ymm0,%ymm0\n  2.38%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b912: vmovdqu %ymm0,0xb0(%r11,%rdx,8)\n  0.13%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b91c: vmovdqu 0xd0(%rdi,%rdx,8),%ymm0\n  0.40%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b925: vpxor  0xd0(%r11,%rdx,8),%ymm0,%ymm0\n  1.44%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b92f: vmovdqu %ymm0,0xd0(%r11,%rdx,8)\n  0.15%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b939: vmovdqu 0xf0(%rdi,%rdx,8),%ymm0\n  0.23%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b942: vpxor  0xf0(%r11,%rdx,8),%ymm0,%ymm0\n  2.84%  \u2502    \u2502\u2502\u2502 \u2502\u2502      \u2502\u2502  0x00007f1bc835b94c: vmovdqu %ymm0,0xf0(%r11,%rdx,8)\n<\/code><\/pre>\n\n<p>The other loop doesn\u2019t get the same treatment from the compiler:<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>    <span class=\"kt\">int<\/span> <span class=\"n\">count<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">long<\/span> <span class=\"n\">l<\/span> <span class=\"o\">:<\/span> <span class=\"n\">left<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">count<\/span> <span class=\"o\">+=<\/span> <span class=\"nc\">Long<\/span><span class=\"o\">.<\/span><span class=\"na\">bitCount<\/span><span class=\"o\">(<\/span><span class=\"n\">l<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">count<\/span><span class=\"o\">;<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Each of the <code class=\"language-plaintext highlighter-rouge\">popcnt<\/code> instructions below operate on 64 bits each.<\/p>\n\n<pre><code class=\"language-asm\">  0.09%  \u2502\u2502\u2502              \u2197   0x00007f3b1c35bd02: popcnt 0x28(%r11,%rbx,8),%r8\n 12.50%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd09: popcnt 0x20(%r11,%rbx,8),%rdi\n  0.02%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd10: popcnt 0x48(%r11,%rbx,8),%rdx\n 12.86%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd17: popcnt 0x40(%r11,%rbx,8),%r9\n  0.02%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd1e: popcnt 0x38(%r11,%rbx,8),%rax\n  0.02%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd25: popcnt 0x30(%r11,%rbx,8),%rsi\n         \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd2c: popcnt 0x18(%r11,%rbx,8),%r13\n  7.61%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd33: popcnt 0x10(%r11,%rbx,8),%rbp\n         \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd3a: add    %ecx,%ebp\n  0.07%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd3c: add    %ebp,%r13d\n  0.02%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd3f: add    %edi,%r13d\n  8.25%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd42: add    %r8d,%r13d\n  0.07%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd45: add    %r13d,%esi\n  0.07%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd48: add    %esi,%eax\n  7.94%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd4a: add    %eax,%r9d\n  8.10%  \u2502\u2502\u2502              \u2502   0x00007f3b1c35bd4d: add    %r9d,%edx \n<\/code><\/pre>\n\n<p>When the loops are fused (or aren\u2019t fissured) the XORs aren\u2019t vectorised and only operate on 64 bits at a time.<\/p>\n\n<pre><code class=\"language-asm\">  0.31%    \u2197\u2502\u2502\u2502   0x00007f6eac35baa0: mov    0x10(%r10,%r11,8),%rsi\n  2.28%    \u2502\u2502\u2502\u2502   0x00007f6eac35baa5: xor    0x10(%rbx,%r11,8),%rsi  \n           \u2502\u2502\u2502\u2502                                                 \n           \u2502\u2502\u2502\u2502                                                 \n  7.69%    \u2502\u2502\u2502\u2502   0x00007f6eac35baaa: mov    %rsi,0x10(%rbx,%r11,8)\n           \u2502\u2502\u2502\u2502                                                 \n           \u2502\u2502\u2502\u2502                                                 \n  2.06%    \u2502\u2502\u2502\u2502   0x00007f6eac35baaf: popcnt %rsi,%rsi\n  8.92%    \u2502\u2502\u2502\u2502   0x00007f6eac35bab4: add    %esi,%edx\n<\/code><\/pre>\n\n<p>In the fused loop, the loop advances at the pace of the slowest operation.\nAt 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.\nNotably, the <a href=\"\/posts\/population-count-in-java\">popcount<\/a> operation can be <a href=\"https:\/\/arxiv.org\/abs\/1611.07612\">vectorised<\/a> and clang <a href=\"https:\/\/reviews.llvm.org\/rG6ba9730a4ef3515653d1813fb716988398ca2c5d\">does so<\/a>, so the fused loop written in C++ and compiled with clang would be much faster.\nclang will also perform loop fission (which it calls <a href=\"https:\/\/reviews.llvm.org\/D19403\">distribution<\/a>).  <br \/>\nThis is a good example of performance intuition from one language not carrying over into another.<\/p>\n\n<h3 id=\"group-by\">Group By<\/h3>\n\n<p>I recently started working on <a href=\"https:\/\/pinot.apache.org\/\">Apache Pinot<\/a>, which is a scalable OLAP store.\nIn 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.\nOne 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.\nThe total number of groups for the entire column is known from statistics collected about the values, but not at the block level.\nThe fused and fissured operations look like this (resetting the array would not happen for real but is necessary to make the benchmark stable):<\/p>\n\n<div class=\"language-java highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nd\">@State<\/span><span class=\"o\">(<\/span><span class=\"nc\">Scope<\/span><span class=\"o\">.<\/span><span class=\"na\">Benchmark<\/span><span class=\"o\">)<\/span>\n<span class=\"kd\">public<\/span> <span class=\"kd\">class<\/span> <span class=\"nc\">GroupCount<\/span> <span class=\"o\">{<\/span>\n\n  <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"8\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"64\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"128\"<\/span><span class=\"o\">})<\/span>\n  <span class=\"kt\">int<\/span> <span class=\"n\">groups<\/span><span class=\"o\">;<\/span>\n  <span class=\"nd\">@Param<\/span><span class=\"o\">({<\/span><span class=\"s\">\"256\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"512\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"1024\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"2048\"<\/span><span class=\"o\">,<\/span> <span class=\"s\">\"4096\"<\/span><span class=\"o\">})<\/span>\n  <span class=\"kt\">int<\/span> <span class=\"n\">length<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"kd\">private<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"n\">source<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[]<\/span> <span class=\"n\">dest<\/span><span class=\"o\">;<\/span>\n  <span class=\"kd\">private<\/span> <span class=\"kt\">boolean<\/span><span class=\"o\">[]<\/span> <span class=\"n\">presence<\/span><span class=\"o\">;<\/span>\n\n  <span class=\"nd\">@Setup<\/span><span class=\"o\">(<\/span><span class=\"nc\">Level<\/span><span class=\"o\">.<\/span><span class=\"na\">Trial<\/span><span class=\"o\">)<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">setup<\/span><span class=\"o\">()<\/span> <span class=\"o\">{<\/span>\n    <span class=\"n\">source<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[<\/span><span class=\"n\">length<\/span><span class=\"o\">];<\/span>\n    <span class=\"n\">dest<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">byte<\/span><span class=\"o\">[<\/span><span class=\"n\">length<\/span><span class=\"o\">];<\/span>\n    <span class=\"n\">presence<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"kt\">boolean<\/span><span class=\"o\">[<\/span><span class=\"n\">groups<\/span><span class=\"o\">];<\/span>\n    <span class=\"nc\">SplittableRandom<\/span> <span class=\"n\">random<\/span> <span class=\"o\">=<\/span> <span class=\"k\">new<\/span> <span class=\"nc\">SplittableRandom<\/span><span class=\"o\">(<\/span><span class=\"mi\">42<\/span><span class=\"o\">);<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">source<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"o\">(<\/span><span class=\"kt\">byte<\/span><span class=\"o\">)<\/span> <span class=\"n\">random<\/span><span class=\"o\">.<\/span><span class=\"na\">nextInt<\/span><span class=\"o\">(<\/span><span class=\"n\">groups<\/span><span class=\"o\">);<\/span>\n    <span class=\"o\">}<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">fused<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">numGroups<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">source<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">dest<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"n\">dest<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">];<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(<\/span><span class=\"n\">numGroups<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">groups<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"o\">!<\/span><span class=\"n\">presence<\/span><span class=\"o\">[<\/span><span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]])<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">presence<\/span><span class=\"o\">[<\/span><span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">&amp;<\/span> <span class=\"mh\">0xFF<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">true<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">numGroups<\/span><span class=\"o\">++;<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">presence<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">fill<\/span><span class=\"o\">(<\/span><span class=\"n\">presence<\/span><span class=\"o\">,<\/span> <span class=\"kc\">false<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n\n  <span class=\"nd\">@Benchmark<\/span>\n  <span class=\"kd\">public<\/span> <span class=\"kt\">void<\/span> <span class=\"nf\">fissured<\/span><span class=\"o\">(<\/span><span class=\"nc\">Blackhole<\/span> <span class=\"n\">bh<\/span><span class=\"o\">)<\/span> <span class=\"o\">{<\/span>\n    <span class=\"nc\">System<\/span><span class=\"o\">.<\/span><span class=\"na\">arraycopy<\/span><span class=\"o\">(<\/span><span class=\"n\">source<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">dest<\/span><span class=\"o\">,<\/span> <span class=\"mi\">0<\/span><span class=\"o\">,<\/span> <span class=\"n\">source<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span><span class=\"o\">);<\/span>\n    <span class=\"kt\">int<\/span> <span class=\"n\">numGroups<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span>\n    <span class=\"k\">for<\/span> <span class=\"o\">(<\/span><span class=\"kt\">int<\/span> <span class=\"n\">i<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">source<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">i<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">dest<\/span><span class=\"o\">.<\/span><span class=\"na\">length<\/span> <span class=\"o\">&amp;<\/span> <span class=\"n\">numGroups<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">groups<\/span><span class=\"o\">;<\/span> <span class=\"n\">i<\/span><span class=\"o\">++)<\/span> <span class=\"o\">{<\/span>\n      <span class=\"k\">if<\/span> <span class=\"o\">(!<\/span><span class=\"n\">presence<\/span><span class=\"o\">[<\/span><span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]])<\/span> <span class=\"o\">{<\/span>\n        <span class=\"n\">presence<\/span><span class=\"o\">[<\/span><span class=\"n\">source<\/span><span class=\"o\">[<\/span><span class=\"n\">i<\/span><span class=\"o\">]<\/span> <span class=\"o\">&amp;<\/span> <span class=\"mh\">0xFF<\/span><span class=\"o\">]<\/span> <span class=\"o\">=<\/span> <span class=\"kc\">true<\/span><span class=\"o\">;<\/span>\n        <span class=\"n\">numGroups<\/span><span class=\"o\">++;<\/span>\n      <span class=\"o\">}<\/span>\n    <span class=\"o\">}<\/span>\n    <span class=\"n\">bh<\/span><span class=\"o\">.<\/span><span class=\"na\">consume<\/span><span class=\"o\">(<\/span><span class=\"n\">presence<\/span><span class=\"o\">);<\/span>\n    <span class=\"nc\">Arrays<\/span><span class=\"o\">.<\/span><span class=\"na\">fill<\/span><span class=\"o\">(<\/span><span class=\"n\">presence<\/span><span class=\"o\">,<\/span> <span class=\"kc\">false<\/span><span class=\"o\">);<\/span>\n  <span class=\"o\">}<\/span>\n<span class=\"o\">}<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This is a little more interesting than the other loop.\nFirstly, splitting the loops makes it obvious that the copy part is a manual <code class=\"language-plaintext highlighter-rouge\">System.arraycopy<\/code>.\nThe second loop can terminate whenever <code class=\"language-plaintext highlighter-rouge\">numGroups == groups<\/code> 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.\nWhilst fission makes the copy fast, and an obvious candidate for removal, fission allows the second loop to terminate early.<\/p>\n\n<p>Early termination isn\u2019t 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\u2019t exit early.\nWorse, 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\u2019t exit early.<\/p>\n\n<div class=\"table-holder\">\n\n  <table>\n    <thead>\n      <tr>\n        <th>Benchmark<\/th>\n        <th>Mode<\/th>\n        <th>Threads<\/th>\n        <th>Samples<\/th>\n        <th>Score<\/th>\n        <th>Score Error (99.9%)<\/th>\n        <th>Unit<\/th>\n        <th>Param: groups<\/th>\n        <th>Param: length<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>41.606830<\/td>\n        <td>0.187490<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>44.518117<\/td>\n        <td>0.183473<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>50.099552<\/td>\n        <td>0.263780<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>61.991607<\/td>\n        <td>0.254863<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>134.196339<\/td>\n        <td>0.138997<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>542.240261<\/td>\n        <td>1.993239<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>494.577003<\/td>\n        <td>0.331404<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>501.147187<\/td>\n        <td>0.265733<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>510.997369<\/td>\n        <td>1.264461<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>575.261847<\/td>\n        <td>0.583042<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>603.379813<\/td>\n        <td>1.389718<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1121.185683<\/td>\n        <td>26.927230<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1132.562254<\/td>\n        <td>36.868698<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1141.871722<\/td>\n        <td>18.612546<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fissured<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1198.111175<\/td>\n        <td>49.512727<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>197.811972<\/td>\n        <td>0.352569<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>344.481666<\/td>\n        <td>0.602220<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>642.021575<\/td>\n        <td>2.235174<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1236.629115<\/td>\n        <td>7.446193<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2478.591752<\/td>\n        <td>2.470746<\/td>\n        <td>ns\/op<\/td>\n        <td>8<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>380.130086<\/td>\n        <td>1.642884<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>441.293203<\/td>\n        <td>1.776462<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>793.848668<\/td>\n        <td>17.267290<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1265.961589<\/td>\n        <td>5.980144<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2340.019651<\/td>\n        <td>4.249029<\/td>\n        <td>ns\/op<\/td>\n        <td>64<\/td>\n        <td>4096<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>290.768967<\/td>\n        <td>9.365530<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>256<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>752.002294<\/td>\n        <td>0.905900<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>512<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1110.519415<\/td>\n        <td>1.109794<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>1024<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>1887.382144<\/td>\n        <td>48.908744<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>2048<\/td>\n      <\/tr>\n      <tr>\n        <td>GroupCount.fused<\/td>\n        <td>avgt<\/td>\n        <td>1<\/td>\n        <td>5<\/td>\n        <td>2550.872751<\/td>\n        <td>23.857060<\/td>\n        <td>ns\/op<\/td>\n        <td>128<\/td>\n        <td>4096<\/td>\n      <\/tr>\n    <\/tbody>\n  <\/table>\n\n<\/div>\n\n<p><img src=\"\/assets\/2021\/11\/loop-fission\/groupcount.png\" alt=\"results\" \/><\/p>\n\n<p>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.\nWhen the ratio between <code class=\"language-plaintext highlighter-rouge\">length<\/code> and <code class=\"language-plaintext highlighter-rouge\">groups<\/code> is high, the fissured implementation wins (see 4096\/8 134ns vs 2479ns).\nWhen the ratio is low, the effect is less extreme but in the opposite direction (see 512\/128 1121ns vs 752ns).\nIf you like looking at things like perfasm output to compare the counted and non\u2013counted loop, it is <a href=\"https:\/\/github.com\/richardstartin\/runtime-benchmarks\/blob\/master\/GroupCount.perfasm\">here<\/a>.\nGiven 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.<\/p>","author":{"name":{}},"category":{"@attributes":{"term":"java"}},"summary":"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\u2019m aware, C2, Hotspot\u2019s JIT compiler, doesn\u2019t 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."}]}