<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Soonil Nagarkar</title><link>https://sooniln.github.io/</link><description>Soonil Nagarkar</description><generator>Hugo 0.163.2 &amp; FixIt v0.4.5</generator><language>en-us</language><lastBuildDate>Sun, 19 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://sooniln.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Comprehensive JVM Primitive Hashtable Benchmarks 2026</title><link>https://sooniln.github.io/posts/hashmap-benchmarks-2026/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://sooniln.github.io/posts/hashmap-benchmarks-2026/</guid><description>&lt;h2 class="heading-element" id="introduction"&gt;&lt;span&gt;Introduction&lt;/span&gt;
 &lt;a href="#introduction" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Hashtables are one of the building blocks of computer science, and deservedly get a lot of attention - but less so
within the JVM ecosystem. Part of this may simply be that most JVM code is not written with performance top of mind,
or that the default JRE implementation is all-around reasonable - fast, efficient, resistant against attacks, why
fix it if it ain&amp;rsquo;t broke? Today we&amp;rsquo;re looking at libraries that are interested in squeezing out every last byte and
cycle of performance however, and we&amp;rsquo;ll be focusing specifically on primitive hashtables.&lt;/p&gt;
&lt;p&gt;If you need a hashtable with primitive keys and/or values, the default JRE HashMap will work fine, but since it
deals exclusively with references your keys and value will be boxed. This means a large amount of memory overhead,
and potentially additional CPU work (though as we&amp;rsquo;ll see, modern JVMs have done a good job of driving this down).
Using primitive values rather than boxed references allows dropping the costs of heap allocation and pointer
dereferencing, as well as denser memory layouts and lower GC pressure. The hope is that any analysis here will
continue to hold weight with upcoming &lt;a href="https://openjdk.org/projects/valhalla/" target="_blank" rel="external nofollow noopener noreferrer"&gt;Valhalla&lt;/a&gt; changes to the JVM, which
will allow a much broader range of value objects. On a selfish note, it&amp;rsquo;s also simpler to benchmark primitive
hashtables as we don&amp;rsquo;t have to worry about things like
&lt;a href="https://shipilev.net/jvm/anatomy-quarks/11-moving-gc-locality/" target="_blank" rel="external nofollow noopener noreferrer"&gt;garbage collection rearranging arrays of references&lt;/a&gt;
underneath us mid-benchmark and how that might affect results&amp;hellip;&lt;/p&gt;
&lt;p&gt;The JVM ecosystem has accumulated a handful of libraries that provide hashtables backed by primitive arrays in order
to address these performance concerns. The problem is that the performance tradeoffs and design choices between them
are neither obvious nor well-documented anywhere. Many different choices are made around hash functions, collision
resolution strategies, load factors, and those choices produce meaningfully different performance profiles yet there
is pretty much no modern data available on head-to-head performance. The goal of this post is to rectify that.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll benchmark and analyze the following libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#jre"&gt;JRE&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#fastcollect"&gt;FastCollect&lt;/a&gt; (I am also the author of FastCollect)&lt;/li&gt;
&lt;li&gt;&lt;a href="#fastutil"&gt;Fastutil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#androidx"&gt;AndroidX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#trove"&gt;Trove&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#koloboke"&gt;Koloboke&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#eclipse"&gt;Eclipse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hppc"&gt;HPPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#agrona"&gt;Agrona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#libgdx"&gt;LibGDX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of the questions we want to evaluate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How does load factor affect performance?&lt;/li&gt;
&lt;li&gt;How well does a JVM &lt;a href="https://abseil.io/about/design/swisstables" target="_blank" rel="external nofollow noopener noreferrer"&gt;Swiss Table&lt;/a&gt; (AndroidX) work?&lt;/li&gt;
&lt;li&gt;How does the novel (AFAIK) hash finalizer in FastCollect compare to more standard hash finalizers?&lt;/li&gt;
&lt;li&gt;How does the JRE HashMap compare to the more specialized libraries?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may note that several of the libraries benchmarked here are quite old and/or no longer maintained. Others are
not intended for general purpose usage. The larger purpose of this post is not to determine which is all-around
fastest (and it&amp;rsquo;s unlikely any one library could be characterized that way), but to investigate the design choices that
affect hashtable performance.&lt;/p&gt;
&lt;div class="details admonition note open disabled"&gt;
 &lt;div class="details-summary admonition-title"&gt;&lt;i class="icon fa-solid fa-pencil-alt" aria-hidden="true"&gt;&lt;/i&gt;Note&lt;/div&gt;&lt;div class="details-content"&gt;
 &lt;div class="admonition-content"&gt;&lt;p&gt;The JRE HashMap is the only hashtable here truly suited for external use (when an attacker can control the table).
Most of these libraries do not make any pretense at resisting DoS attacks and favor raw performance instead.&lt;/p&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;&lt;h3 class="heading-element" id="prior-benchmarks"&gt;&lt;span&gt;Prior Benchmarks&lt;/span&gt;
 &lt;a href="#prior-benchmarks" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;The only large scale benchmarking of primitive hashtables I could find in the past is
&lt;a href="https://dzone.com/articles/time-memory-tradeoff-example" target="_blank" rel="external nofollow noopener noreferrer"&gt;this article from 2014&lt;/a&gt; by Roman Leventov (also the author of
Koloboke). It&amp;rsquo;s short, and worth a quick glance to observe how far behind the performance of JRE HashMap was in this
older benchmark - compare that to its performance today. Many of the libraries benchmarked in that article are also
benchmarked here (though some have changed names: HFTC → Koloboke, Goldman Sachs → Eclipse).&lt;/p&gt;
&lt;p&gt;There is also the 2017 paper
&lt;a href="https://research.spec.org/icpe_proceedings/2017/proceedings/p389.pdf" target="_blank" rel="external nofollow noopener noreferrer"&gt;Empirical Study of Usage and Performance of Java Collections&lt;/a&gt;,
but the paper only shows a faster/slower comparison. I was unable to find the raw data referenced by the paper, but did
not attempt to contact the authors in reference to the data.&lt;/p&gt;
&lt;h2 class="heading-element" id="hashtable-design-choices"&gt;&lt;span&gt;Hashtable Design Choices&lt;/span&gt;
 &lt;a href="#hashtable-design-choices" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Honestly, this section could have been an entire blog post in its own right. However, it doesn&amp;rsquo;t feel right to drop
into results without contextualizing how hashtables work and the various trade-offs we need to think about when
designing them&amp;hellip; If you feel you have a good handle on this and want to skip straight to the next section, move on
to the &lt;a href="#benchmark-setup"&gt;Benchmark Setup&lt;/a&gt; section. If not, I&amp;rsquo;ll try to keep this short, but no guarantees.&lt;/p&gt;
&lt;p&gt;Fundamentally, all hashtables operate by mapping a very large universe of potential keys onto a much smaller number of
available slots (often represented by an array). As a trivial example, if we&amp;rsquo;re using 32-bit integers as keys, we would
need to map all the ~4 billion possible integers onto the much smaller structure in memory (for example a 128 slot
array perhaps, if we&amp;rsquo;re only expecting to map 100 integers total). This is accomplished via
&lt;a href="https://en.wikipedia.org/wiki/Hash_function" target="_blank" rel="external nofollow noopener noreferrer"&gt;hash functions&lt;/a&gt;. By definition, once we&amp;rsquo;ve computed such a mapping there
may be collisions - two different keys that are mapped by a hash function to the same slot. The second responsibility
of the hashtable is thus to handle collisions, and store multiple keys that map to the same slots. These basic
principles lead to certain tradeoffs.&lt;/p&gt;
&lt;h3 class="heading-element" id="tradeoffs"&gt;&lt;span&gt;Tradeoffs&lt;/span&gt;
 &lt;a href="#tradeoffs" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;The highest level of tradeoff in hashtables can be characterized as time vs memory. Use more memory, and you can access
entries in less time. Use less memory, and it takes more time to access entries. Easy, but also a pretty simplified
view of things. We can break this down further:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The CPU cost of mapping a key to a slot (complexity of the hash function)&lt;/li&gt;
&lt;li&gt;The layout of slots in memory (open addressing vs separate chaining)&lt;/li&gt;
&lt;li&gt;If the original slot mapping is a collision slot:
&lt;ol&gt;
&lt;li&gt;The CPU cost of searching colliding slots (probing strategy)&lt;/li&gt;
&lt;li&gt;The number and pattern of memory accesses while searching colliding slots (probing strategy, cache line effects,
prefetching effects, TLB effects)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Second order effects
&lt;ol&gt;
&lt;li&gt;Current load factor affects collision rates&lt;/li&gt;
&lt;li&gt;Key distributions affect collision rates&lt;/li&gt;
&lt;li&gt;Soft-deletion leaves behinds tombstones to reduce efficiency&lt;/li&gt;
&lt;li&gt;Size of keys/values determines how many fit in a cache line / cache&lt;/li&gt;
&lt;li&gt;Type of workload - reads vs writes, hits vs misses&lt;/li&gt;
&lt;li&gt;Etc, ad infinitum&amp;hellip;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these effects can vary on different invocations on the same hashtable, so the best way to think about them is
in terms of the cost + variance they introduce. With that high-level framework in mind, let&amp;rsquo;s dive into some quick-ish
definitions.&lt;/p&gt;
&lt;h3 class="heading-element" id="open-addressing-vs-separate-chaining"&gt;&lt;span&gt;Open Addressing vs. Separate Chaining&lt;/span&gt;
 &lt;a href="#open-addressing-vs-separate-chaining" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;The first architectural fork in hashtable design is how collisions are handled when two keys map to the same slot.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separate chaining&lt;/strong&gt; associates a separate collection of entries with each slot. All colliding entries thus are stored
within the same collection. The most common collection used here is a linked list, but other options are possible
(the JRE HashMap starts with a linked list for example, but converts to a red-black tree if the list grows too large).
This can be visualized as a two tier memory approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open addressing&lt;/strong&gt; stores all entries directly in the contiguous backing array. When a slot is occupied, the algorithm
probes for the next candidate slot according to some probing strategy (discussed in more detail below). Deletion in
an open-addressed table requires extra thought, and is discussed in more detail below. Where separate chaining can
be thought of a two tier approach, open addressing is a flat one tier approach.&lt;/p&gt;
&lt;p&gt;Every library in this benchmark except the JRE HashMap uses open addressing.&lt;/p&gt;
&lt;h3 class="heading-element" id="probing-strategies"&gt;&lt;span&gt;Probing Strategies&lt;/span&gt;
 &lt;a href="#probing-strategies" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;When a key is mapped to a slot that is already occupied (a hash collision), it must be handled somehow. In the case of
separate chaining, we simply add the new entry to the collection of entries already associated with the slot, but
open addressing must search for other unoccupied slots to store the entry. The sequence of slots tried in the
search for an open slot is called the &lt;em&gt;probe sequence&lt;/em&gt;, and its choice significantly impacts both throughput and
cache behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Linear_probing" target="_blank" rel="external nofollow noopener noreferrer"&gt;Linear probing&lt;/a&gt;&lt;/strong&gt; just tries consecutive slots: slot, slot+1, slot+2,
and so on (wrapping around the table) until an open slot is found. It is maximally cache-friendly (sequential memory
access) — but suffers from &lt;em&gt;primary clustering&lt;/em&gt;: once a run of occupied slots forms, any key that hashes into that run
lengthens it, creating a feedback loop that lengthens the run further and degrades performance as entries are put into
slots further and further away from their home slot, and thus requiring longer and longer probe sequences on lookup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;State: [ ][ ][A][B][C][ ][ ]

Insert D (hashes to slot 2): probe 2→3→4→5, insert at 5.
State: [ ][ ][A][B][C][D][ ]

Insert E (hashes to slot 5): probe 5→6, insert at 6. Cluster continues to grow...
State: [ ][ ][A][B][C][D][E]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Quadratic_probing" target="_blank" rel="external nofollow noopener noreferrer"&gt;Quadratic probing&lt;/a&gt;&lt;/strong&gt; uses offsets slot+0², slot+1², slot+2²,
slot+3²&amp;hellip; (the probe number squared). It avoids primary clustering but can instead produce secondary clustering
(keys with the same initial slot follow identical probe sequences) and over longer probe sequences results in more
cache misses than linear probing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Double_hashing" target="_blank" rel="external nofollow noopener noreferrer"&gt;Double hashing&lt;/a&gt;&lt;/strong&gt; uses a second hash function to calculate the stride
size based on the key: slot, slot+stride, slot+2*stride, slot+3*stride, etc. It avoids both primary and secondary
clustering - the cost is computing two hash functions and the non-sequential memory access pattern which also
results in additional cache misses.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Hash_table#Robin_Hood_hashing" target="_blank" rel="external nofollow noopener noreferrer"&gt;Robin Hood hashing&lt;/a&gt;&lt;/strong&gt; is a refinement of linear probing
that swaps entry positions (swapping a &amp;ldquo;poorer&amp;rdquo; [further from its home slot] entry with a &amp;ldquo;richer&amp;rdquo; [closer to
its home slot] entry - hence Robin Hood) during insertion to reduce probe length variance. This tends to equalize
probe lengths and allows much higher load factors without the worst-case blow-up of plain linear probing (Robin Hood
hashtables are often run at load factors of 80%+ without much performance impact).&lt;/p&gt;
&lt;p&gt;And finally of course, you can have arbitrarily complex combinations of any probing scheme. A table could use linear
probing for the first N elements, then switch to quadratic probing, or switch to linear probing with a different hash
function, etc&amp;hellip; The advantage to more complex probing schemes is that you can attempt to equalize the various pros
and cons of each. For example, by starting with linear probing you can take advantage of its cache friendly behavior
initially - linear probing for a cache line distance to avoid extra cache misses - then switch to quadratic probing
when you&amp;rsquo;re going to incur a cache miss anyway. The downside of these arbitrarily complex combinations is of course
code complexity - but also entry removal, which can become very difficult (and we&amp;rsquo;ll discuss later).&lt;/p&gt;
&lt;h3 class="heading-element" id="load-factor"&gt;&lt;span&gt;Load Factor&lt;/span&gt;
 &lt;a href="#load-factor" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Hash_table#Load_factor" target="_blank" rel="external nofollow noopener noreferrer"&gt;load factor&lt;/a&gt; of a hashtable is the ratio of occupied
slots to total capacity. At load factor 0.5, half the backing array is empty. At 0.9, only one slot in ten is free.
Most hashtables have a maximum load factor - when they reach that limit the backing array is expanded to reduce the
load factor again. This provides a convenient bound on performance, and allows the hashtable to scale memory usage
with the number of entries. It also means that performance varies as the hashtable grows from low load factors
to high load factors, and then resizes back into a low load factor, leading to a distinct sawtooth graph appearance
we&amp;rsquo;ll observe later.&lt;/p&gt;
&lt;p&gt;Higher load factors conserve memory but increase expected probe sequence length, which grows non-linearly as the table
fills. For a uniformly random hash function and a simple linear probe, expected probe length at load factor α is
approximately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Successful lookup (hit) ≈ &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence="true"&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mo fence="true"&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\frac{1}{2}\left(1 + \frac{1}{1-\alpha}\right)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.2533em;vertical-align:-0.4033em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8451em;"&gt;&lt;span style="top:-2.655em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.394em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.345em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.1667em;"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter" style="top:0em;"&gt;&lt;span class="delimsizing size1"&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8451em;"&gt;&lt;span style="top:-2.655em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;span class="mbin mtight"&gt;−&lt;/span&gt;&lt;span class="mord mathnormal mtight" style="margin-right:0.0037em;"&gt;α&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.394em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.4033em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose delimcenter" style="top:0em;"&gt;&lt;span class="delimsizing size1"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Unsuccessful lookup (miss) ≈ &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;mrow&gt;&lt;mo fence="true"&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mrow&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;α&lt;/mi&gt;&lt;msup&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;mo fence="true"&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;\frac{1}{2}\left(1 + \frac{1}{(1-\alpha)^2}\right)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.8em;vertical-align:-0.65em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8451em;"&gt;&lt;span style="top:-2.655em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.394em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.345em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.1667em;"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter" style="top:0em;"&gt;&lt;span class="delimsizing size2"&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.8451em;"&gt;&lt;span style="top:-2.655em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mopen mtight"&gt;(&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;span class="mbin mtight"&gt;−&lt;/span&gt;&lt;span class="mord mathnormal mtight" style="margin-right:0.0037em;"&gt;α&lt;/span&gt;&lt;span class="mclose mtight"&gt;&lt;span class="mclose mtight"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.7463em;"&gt;&lt;span style="top:-2.786em;margin-right:0.0714em;"&gt;&lt;span class="pstrut" style="height:2.5em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size3 size1 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.23em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="frac-line" style="border-bottom-width:0.04em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-3.394em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.52em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose delimcenter" style="top:0em;"&gt;&lt;span class="delimsizing size2"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At α=0.5, this means ~1.5 probes for hits and ~2.5 for misses. At α=0.75 that&amp;rsquo;s ~2.5 probes for hits and ~8.5 for
misses. And at α=0.9: ~5.5 for hits, ~50.5 for misses! This is a very simple calculation, assuming a perfect hash
function and the simplest possible version of linear probing, but it is useful for understanding general probe
length behavior.&lt;/p&gt;
&lt;h3 class="heading-element" id="backing-array-sizing"&gt;&lt;span&gt;Backing Array Sizing&lt;/span&gt;
 &lt;a href="#backing-array-sizing" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Array backed hashtables also need to decide how large of an array to use to hold some number of entries at a given
load factor. There are two main strategies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Power-of-two sizing&lt;/strong&gt; uses table sizes that are always powers of two: 16, 32, 64, 128, and so on. This is primarily
for performance reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Multiplying/dividing by two is a simple shift operation.&lt;/li&gt;
&lt;li&gt;Converting a hash to a slot index can be done with a single AND rather than a more expensive modulo operation:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;int capacity = 256; // 0b100000000
int mask = capacity - 1; // 0b011111111
int slot = hash &amp;amp; mask; // slot is now in [0, 256)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Prime sizing&lt;/strong&gt; keeps the table size as a prime number: 17, 37, 79, etc&amp;hellip; Converting a hash to a slot now requires a
modulo, and is thus slower. The case for prime sizing is that because the hash cannot have any hidden common factors
with the prime table size (by definition), this results in a much more even spread of keys within the table and thus
reduces clustering. Less clustering reduces average probe length, which reduces lookup times (theoretically).&lt;/p&gt;
&lt;p&gt;The simple implementations of both of these schemes require a large re-allocation and complete rehash (re-insertion
of all elements) whenever the hashtable runs out of room. All hashtables benchmarked here do complete rehashes on
running out of space (some allow for tombstone-removal-only rehashes in the event that they use tombstones - covered
later).&lt;/p&gt;
&lt;h3 class="heading-element" id="hashtable-memory-layout"&gt;&lt;span&gt;Hashtable Memory Layout&lt;/span&gt;
 &lt;a href="#hashtable-memory-layout" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;At a minimum, hashtables need to store keys and values. They can also associate extra metadata with keys/values -
often stored as a separate array if present. The overall goal for performance is to increase the memory density of
data required for lookup operations as much as possible. The question this leads to in hashtable design is then,
what data does your lookup require? Scan through keys, then load only the required value? Scan though metadata,
loading keys only if necessary, and then only the required value? Or scan through keys and values together?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parallel arrays&lt;/strong&gt; maintain one array for keys and a separate array for values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keys: [k0][k1][k2][k3]&amp;hellip;&lt;/li&gt;
&lt;li&gt;values: [v0][v1][v2][v3]&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Interleaved storage&lt;/strong&gt; packs key-value pairs together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;table: [k0][v0][k1][v1][k2][v2][k3][v3]&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="details admonition note open disabled"&gt;
 &lt;div class="details-summary admonition-title"&gt;&lt;i class="icon fa-solid fa-pencil-alt" aria-hidden="true"&gt;&lt;/i&gt;Note&lt;/div&gt;&lt;div class="details-content"&gt;
 &lt;div class="admonition-content"&gt;&lt;p&gt;If keys and values are not the same size then they will need to be padded to achieve interleaved storage. This
wastes space, and is difficult to achieve with the JVM anyway, so interleaved storage is generally only a
possibility when the keys and values are known to be the same size.&lt;/p&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The density of data affects the number of cache misses during probe sequences, and thus directly affects performance.&lt;/p&gt;
&lt;h3 class="heading-element" id="metadata"&gt;&lt;span&gt;Metadata&lt;/span&gt;
 &lt;a href="#metadata" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Some example of metadata stored by hashtables includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Robin_Hood_hashing" target="_blank" rel="external nofollow noopener noreferrer"&gt;Robin Hood&lt;/a&gt; hashtables may store probe sequence lengths (PSLs - how
far a key is located from the slot it hashed to).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://abseil.io/about/design/swisstables" target="_blank" rel="external nofollow noopener noreferrer"&gt;Swiss tables&lt;/a&gt; store key fingerprints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given that the metadata is usually substantially smaller than the key size, metadata cannot easily be interleaved with
keys and is usually a separate array. If probing now requires accessing both metadata and keys this will incur many
more cache misses, so when metadata is used, probing will almost always attempt to only access metadata (which is
what makes key fingerprints so useful), and access keys only when absolutely necessary.&lt;/p&gt;
&lt;p&gt;There are also hashtables that store indirect indices which point into separate key/value storage. This allows
key/value storage to be much denser than the main hashtable, potentially saving space, at the cost of an additional
level of indirection. This might allow a hashtable to use a load factor of 50% or lower, knowing that it will only
apply to a small metadata table, while keys/values are packed tightly. None of the hashtables being benchmarked use
this technique.&lt;/p&gt;
&lt;p&gt;Finally, there is one more advantage to metadata - it can help with representing empty slots.&lt;/p&gt;
&lt;h3 class="heading-element" id="empty-slots"&gt;&lt;span&gt;Empty Slots&lt;/span&gt;
 &lt;a href="#empty-slots" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;We&amp;rsquo;ve discussed the various memory layouts and tradeoffs, but no matter which is used, a hashtable needs some way to
indicate an empty slot, so that it knows where it can insert new entries or when it can stop probing during a lookup.
The problem of course, is that there is no guarantee any particular key value you might choose to represent
&amp;ldquo;unoccupied&amp;rdquo; won&amp;rsquo;t be inserted! There are naturally many possible solutions to this.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enforce that the user provides an illegal key value which will always represent empty - the user can never insert
that key into the map.&lt;/li&gt;
&lt;li&gt;Use a separate variable to track whether the empty key value has been added to the map and what its value is. In
every hashtable operation, check if the given key is the same as the empty key, and if so special-case the operation
to use the separate variable instead of looking in the table.&lt;/li&gt;
&lt;li&gt;Always allocate an array with 1 additional slot at the end, which is used to hold the value of the empty key.
In the rest of the array, the empty key means an empty slot.&lt;/li&gt;
&lt;li&gt;If the user attempts to insert the empty key, choose a new empty key which is not present in the table, and update
the table to use the new empty key before continuing the insertion.&lt;/li&gt;
&lt;li&gt;If using a metadata array, represent the empty slot in the metadata array, not in the key array.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are potential performance implications to all of these choices. If you need to add special cases, you&amp;rsquo;ll add
to code size - too large of a code block can prevent some JVM C2 compiler optimizations and also incur more code cache
misses. If you need to check a member variable that&amp;rsquo;s an additional load and register usage. Too much register usage
lead to register spilling and commensurate performance impacts on the hot path. No matter the choice there&amp;rsquo;s always a
variety of consequences to think through, and pretty much no way to anticipate the impact except through benchmarking.&lt;/p&gt;
&lt;h3 class="heading-element" id="entry-removal"&gt;&lt;span&gt;Entry Removal&lt;/span&gt;
 &lt;a href="#entry-removal" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Now that we&amp;rsquo;ve walked through the process of adding entries to the hashtable, how do we remove entries? Deleting a key
from an open-addressed map is more complex than insertion. We can&amp;rsquo;t simply find the slot in the array and clear
it - this would break the invariants various probing algorithms rely on to function. Clearing the slot would
leave a gap in a probe sequence, causing lookups for later keys to terminate early at the gap and incorrectly report
a key as not present in the table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Lazy_deletion" target="_blank" rel="external nofollow noopener noreferrer"&gt;Lazy deletion&lt;/a&gt;&lt;/strong&gt; is one of the more common implementations - rather
than clearing the slot, mark it with a special DELETED value (called a tombstone). Future lookups probe past
tombstones, and future insertions can reuse them. Tombstones are very easy to implement, but can accumulate over
time. A map with many insertions and deletions will fill up with tombstones, which probe sequences must scan through
even though they represent no data. This degrades all operations over time. Most implementations address this by
triggering a rehash when tombstones exceed some fraction of capacity, but this means delete-heavy workloads
periodically pay an additional full-table rehash cost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backwards-shift deletion&lt;/strong&gt; is an alternative that avoids tombstones entirely. After clearing a slot, the algorithm
looks at the next slot and checks if its occupant could shift back one probe position (i.e., its probe distance is
greater than zero — it&amp;rsquo;s not at its ideal slot). If so, it shifts back. This continues until an empty slot or a slot
whose occupant is already at its ideal position is reached.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Before (d = displacement from ideal):
[ ][A(d=0)][B(d=1)][C(d=2)][ ] 

Delete A:
[ ][ ][B(d=1)][C(d=2)][ ]

Shift B back (d=1, shift allowed):
[ ][B(d=0)][ ][C(d=2)][ ]

Shift C back (d=2, shift allowed):
[ ][B(d=0)][C(d=1)][ ][ ]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result is a table with no tombstones and no gaps — every probe sequence remains contiguous. Lookup and insertion
performance doesn&amp;rsquo;t degrade after heavy deletion. The cost is that deletion is slightly more expensive, and that
(1) element displacement must be linear (2) an element&amp;rsquo;s displacement must be calculable - not every insertion
strategy makes it possible to deterministically know an element&amp;rsquo;s displacement. Robin Hood hashing tends to maintain
this naturally, so backwards-shift deletion is commonly used with Robin Hood hashing, but it is possible with other
variations as well.&lt;/p&gt;
&lt;h3 class="heading-element" id="hash-functions-and-mixing-avalanche-and-bias"&gt;&lt;span&gt;Hash Functions And Mixing: Avalanche and Bias&lt;/span&gt;
 &lt;a href="#hash-functions-and-mixing-avalanche-and-bias" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;So far we&amp;rsquo;ve discussed the simple mechanics of how a hashtable works, but avoided thinking too much about the hash
function (often assuming a uniformly random hash function).&lt;/p&gt;
&lt;p&gt;Theoretically, the ideal is a &lt;strong&gt;perfect hash function&lt;/strong&gt;. This is a hash function that maps a set of keys to a set of
slots with no collisions at all. This is generally only achievable if the complete set of keys is known in advance
(which is not realistic for general-purpose hashtables, but may be in subdomains such as compilers which need to map
keywords, etc&amp;hellip;). There are a variety of algorithms for attempting to construct perfect hash functions, but as we are
concerned with general purpose tables we will not delve deeper or more greedily into the subject.&lt;/p&gt;
&lt;p&gt;For general purpose scenarios, the ideal is a &lt;strong&gt;uniformly random hash function&lt;/strong&gt;. Uniformly random hash functions are a
theoretical construct for analysis where the collision probability of any two distinct keys is 1/N (where N is the
number of slots). This is essentially the idealized limit that real hash functions try to approximate.&lt;/p&gt;
&lt;p&gt;Real hash functions can be evaluated on several axes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Collision rate&lt;/strong&gt;: How often distinct keys are mapped to the same slot.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniformity&lt;/strong&gt;: How evenly outputs are spread across the range of slots.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Independence&lt;/strong&gt;: Whether the slots chosen for related keys exhibit statistical independence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throughput&lt;/strong&gt;: Hashes/Second on bulk input.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: Cycles/Hash for a single key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Attack resistance&lt;/strong&gt;: Can a seed be provided so mapping is unpredictable to an attacker? Is cryptographic
strength required or unnecessary?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to construct hash functions with good uniformity and independence, we often use the terms:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avalanching&lt;/strong&gt;: Sensitivity to input changes - the ideal is that flipping a single bit in the input should cause
50% of the output bits to flip, and the bits that flip are effectively randomly chosen.
&lt;strong&gt;Bias&lt;/strong&gt;: Do any particular outputs or output patterns appear more often than they should (favoring some slots over
others and thus increasing collisions)?
&lt;strong&gt;Entropy&lt;/strong&gt;: Another way of discussing avalanching and bias (avalanching means each output bit should have an
entropy close to 1 bit conditionally on a single input bit flip, bias means for N bit output, ideal entropy is N
bits) - essentially just reframing these in information-theoretic terms.&lt;/p&gt;
&lt;h4 class="heading-element" id="knuth-multiplicative-hashing"&gt;&lt;span&gt;Knuth Multiplicative Hashing&lt;/span&gt;
 &lt;a href="#knuth-multiplicative-hashing" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;One of the most common choices of hash function is Knuth Multiplicative Hashing (introduced by Donald Knuth in TAOCP).
The idea is simply to multiply the input by a large &amp;ldquo;irrational-ish&amp;rdquo; constant and extract the high-order bits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;h&lt;/mi&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence="true"&gt;⌊&lt;/mo&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence="true"&gt;(&lt;/mo&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo lspace="0.22em" rspace="0.22em"&gt;&lt;mrow&gt;&lt;mi mathvariant="normal"&gt;m&lt;/mi&gt;&lt;mi mathvariant="normal"&gt;o&lt;/mi&gt;&lt;mi mathvariant="normal"&gt;d&lt;/mi&gt;&lt;/mrow&gt;&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo fence="true"&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mo fence="true"&gt;⌋&lt;/mo&gt;&lt;/mrow&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;h(k) = \left\lfloor m \cdot \left( kA \bmod 1 \right) \right\rfloor&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1em;vertical-align:-0.25em;"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;h&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.03148em;"&gt;k&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2778em;"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2778em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1em;vertical-align:-0.25em;"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter" style="top:0em;"&gt;⌊&lt;/span&gt;&lt;span class="mord mathnormal"&gt;m&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="minner"&gt;&lt;span class="mopen delimcenter" style="top:0em;"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.03148em;"&gt;k&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.0556em;"&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathrm"&gt;mod&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.0556em;"&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose delimcenter" style="top:0em;"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose delimcenter" style="top:0em;"&gt;⌋&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A: A fractional constant between 0 and 1. Knuth proved that the ideal distribution is achieved when
A ≈ &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy="false"&gt;(&lt;/mo&gt;&lt;msqrt&gt;&lt;mn&gt;5&lt;/mn&gt;&lt;/msqrt&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo stretchy="false"&gt;)&lt;/mo&gt;&lt;mi mathvariant="normal"&gt;/&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;(\sqrt{5}- 1) / 2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1.1572em;vertical-align:-0.25em;"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord sqrt"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.9072em;"&gt;&lt;span class="svg-align" style="top:-3em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="mord" style="padding-left:0.833em;"&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="top:-2.8672em;"&gt;&lt;span class="pstrut" style="height:3em;"&gt;&lt;/span&gt;&lt;span class="hide-tail" style="min-width:0.853em;height:1.08em;"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"&gt;&lt;path d="M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z"/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist" style="height:0.1328em;"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace" style="margin-right:0.2222em;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut" style="height:1em;vertical-align:-0.25em;"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord"&gt;/2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, which is the inverse of the golden ratio (φ⁻¹). Moving forward we&amp;rsquo;ll refer to φ as PHI
for simplicity.&lt;/li&gt;
&lt;li&gt;m: The number of slots to map.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When we use the specific inverse PHI (φ⁻¹) constant Knuth suggested for its maximally irrational properties, this is
known as Fibonacci hashing.&lt;/p&gt;
&lt;p&gt;Variants on Knuth multiplicative hashing are used by the FastCollect, Fastutil, AndroidX, Koloboke, and HPPC libraries.
Knuth multiplicative hashing by itself does not have great avalanching properties and tends to concentrate entropy
in higher bits (rather, the entropy is in the fractional part of the result, which since we are using integer
multiplication and PHI values adjusted by 2^32, ends up being the high bits of the result), so common variants add a
xor-shift operation to (1) improve avalanching (2) improve entropy in the lower bits:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final int INT_PHI = 0x9E3779B9 // 2^32 / PHI, where PHI is the golden ratio

int hash(int k) {
 int h = k * INT_PHI;
 return h ^ (h &amp;gt;&amp;gt;&amp;gt; 16); // fold high bits into low bits
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Knuth multiplicative hashing is only one hashing strategy however, and there exist many other possibilities, all
with their own lists of upsides and downsides.&lt;/p&gt;
&lt;h4 class="heading-element" id="hash-functions-vs-hashtable-needs"&gt;&lt;span&gt;Hash Functions vs Hashtable Needs&lt;/span&gt;
 &lt;a href="#hash-functions-vs-hashtable-needs" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Hash functions are useful far beyond hashtables themselves - and so the goals for an ideal hash function may not
mesh perfectly with the requirements of a hashtable. This exhibits itself in two common ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;General purpose hash functions treat each bit as equally important - we want to spread entropy around everywhere.
But in reality, when we convert a hash to an actual table slot (which ranges from 0-N), we&amp;rsquo;re usually throwing away
everything except the lower bits. So in reality, entropy everywhere is not actually what hashtables care about,
entropy in the bits the hashtable uses is more important. This is the realization that spurred the different hash
function in the FastCollect library (more detail in the FastCollect section).&lt;/li&gt;
&lt;li&gt;Methods of evaluating the strength of a hash function are generally predicated on the assumption that keys will
be drawn from random input. If there are stronger guarantees on the key space, then perhaps not all of these
properties are useful. A common example of this, what if we can assume integer keys can be mapped to [0-N)? This
is not an uncommon assumption in many hashtable domains, and in this case the identity hash function (i → i) is
actually a perfect hash function for a hashtable - even though the identity hash exhibits pretty much no
independence (linear key relationships are preserved) and awful avalanching (flipping 1 bit of input = 1 bit changed
in output).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With that, our not-so-short recap of hashtable designs and tradeoffs is complete - let&amp;rsquo;s move on to benchmarking!&lt;/p&gt;
&lt;h2 class="heading-element" id="benchmark-setup"&gt;&lt;span&gt;Benchmark Setup&lt;/span&gt;
 &lt;a href="#benchmark-setup" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;No CPU pinning, ran on idle machine.&lt;/li&gt;
&lt;li&gt;6 Core AMD Ryzen 5 9600X - Windows 11&lt;/li&gt;
&lt;li&gt;L1/L2/L3 Cache Sizes: 48KB/core, 1MB/core, 32MB shared&lt;/li&gt;
&lt;li&gt;Temurin JDK 21.0.11&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Benchmarking code itself can be found at
&lt;a href="https://github.com/sooniln/jvm-collections-benchmarks" target="_blank" rel="external nofollow noopener noreferrer"&gt;sooniln/jvm-collections-benchmarks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Benchmarks were run until standard deviation to mean ratios were at a reasonable level (usually &amp;lt; 15%), then the median
was used to score in order to avoid outliers.&lt;/p&gt;
&lt;p&gt;Two types of maps tested:&lt;/p&gt;
&lt;p&gt;Integer → Integer&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Same size for keys/values allows for interleaved memory layouts without penalty.&lt;/li&gt;
&lt;li&gt;Smaller keys pack more into cache.&lt;/li&gt;
&lt;li&gt;32-bit keys are likely the most common in production.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Long → Integer&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different size for keys/values prevents interleaved layouts.&lt;/li&gt;
&lt;li&gt;Larger keys increase cache pressure.&lt;/li&gt;
&lt;li&gt;64-bit keys are likely the second most common in production (it seems unlikely that byte/short keyed hashtables
are very common).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The hashtables under test come with a variety of different default load factors. We face a choice of whether to try and
force similar load factors for a more apples-to-apples comparison, or use the default load factors which may
unfairly advantage some implementations. Load factors have become an important part of hashtable design - it&amp;rsquo;s a
valid choice to use a low load factor for performance and recover memory in other ways (by ensuring the load factor
only affects the smaller metadata array and packing keys/value tightly outside the main array for example). However,
none of the hashtables benchmarked here apply any such memory optimizations, and in the interest of similar
comparisons we try to enforce that every hashtable has a load factor of at least 75% (higher is allowed). There is one
exception, Eclipse does not allow for load factor adjustments, and remains at 50% (and for this reason you&amp;rsquo;ll note
some asterixing of Eclipse results). We&amp;rsquo;ll dig into the performance implications of this later.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Library&lt;/th&gt;
					&lt;th&gt;Default&lt;/th&gt;
					&lt;th&gt;Benchmarked&lt;/th&gt;
					&lt;th&gt;Adjustable&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;JRE&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;FastCollect&lt;/td&gt;
					&lt;td&gt;83.3%&lt;/td&gt;
					&lt;td&gt;83.3%&lt;/td&gt;
					&lt;td&gt;No&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Fastutil&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AndroidX&lt;/td&gt;
					&lt;td&gt;87.5%&lt;/td&gt;
					&lt;td&gt;87.5%&lt;/td&gt;
					&lt;td&gt;No&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Trove&lt;/td&gt;
					&lt;td&gt;50%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Koloboke&lt;/td&gt;
					&lt;td&gt;66.7%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Eclipse&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;50%&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;50%&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;No&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;HPPC&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Agrona&lt;/td&gt;
					&lt;td&gt;65%&lt;/td&gt;
					&lt;td&gt;75%&lt;/td&gt;
					&lt;td&gt;Yes&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 class="heading-element" id="key-selection-and-ordering"&gt;&lt;span&gt;Key Selection and Ordering&lt;/span&gt;
 &lt;a href="#key-selection-and-ordering" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Because hash functions have such a large effect on the performance of hash tables, it&amp;rsquo;s important to test with data
that can represent real world scenarios. We specify 3 key orders to test under:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;random&lt;/strong&gt;: Keys are selected at random from the entire universe of possible keys. This might represent a scenario
where only a subset of keys are stored - for example if a hashtable is being used as a cache.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lowBits&lt;/strong&gt;: Keys are selected from 0 → table size and shuffled randomly. This has the effect that only the least
significant bits of the key tend to be set, and most significant bits are all zero. This might represent a scenario
where keys are generally linear and almost all keys are stored - for example internal id values.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;highBits&lt;/strong&gt;: The same as lowBits, but every key is bit-reversed, so that the most significant bits tend to be set,
and the least significant are all zero. This represents two scenarios - (1) an easy adversarial attack (since the
vast majority of hashtables are power-of-two sized, we know that they will usually truncate high bits in order to
calculate a slot - without good bit-mixing properties these tables may be vulnerable) (2) structures that have been
bit-packed into keys and thus have unusually structured bits (for example, a Point class might be trivially
encoded as a 64-bit value).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In practice, I suspect (with no evidence other than gut feeling) that random and lowBits are likely to represent the
majority of real-world scenarios in the JVM ecosystem.&lt;/p&gt;
&lt;h3 class="heading-element" id="benchmark-selection"&gt;&lt;span&gt;Benchmark Selection&lt;/span&gt;
 &lt;a href="#benchmark-selection" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;We benchmark the following operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GetHit&lt;/strong&gt;: The cost of looking up a value for a key present in the hashtable. Note that since different
hashtables have different conventions for representing keys that are not present (some of which may give that
hashtable an unfair advantage), we use getOrDefault() or equivalent APIs for a fair comparison.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GetMiss&lt;/strong&gt;: The cost of looking up a value for a key not present in the hashtable. Note that since different
hashtables have different conventions for representing keys that are not present (some of which may give that
hashtable an unfair advantage), we use getOrDefault() or equivalent APIs for a fair comparison.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PutHit&lt;/strong&gt;: The cost of changing the value for a key present in the hashtable. When offered, this will always use
an API which does not return a value in order to find the cheapest possible cost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PutMiss&lt;/strong&gt;: The cost of assigning a value to a key not present in the hashtable. When offered, this will always use
an API which does not return a value in order to find the cheapest possible cost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RemoveAndPutMiss&lt;/strong&gt;: The cost of removing a key and then assigning a value to a different key that is not present in
the table. The setup of JMH tests makes it difficult to accurately quantify the cost of just a remove operation -
this is a compromise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ForEach&lt;/strong&gt;: The cost of iterating over every key/value present in the hashtable using the fastest iteration
technique exposed by the hashtable (likely not iterators).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NaiveCopy&lt;/strong&gt;: The cost of copying the hashtable starting from empty and inserting elements until complete -
with no information given on the total number of elements to be expected, so no pre-allocation can be done.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PreAllocatedCopy&lt;/strong&gt;: The cost of copying the hashtable directly from another hashtable (so size and layout is
known - may just be a memcpy in the cheapest case).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 class="heading-element" id="micro-benchmarking-caveats"&gt;&lt;span&gt;Micro-Benchmarking Caveats&lt;/span&gt;
 &lt;a href="#micro-benchmarking-caveats" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Micro-benchmarking (as we&amp;rsquo;re performing here) does not give you real world performance results. In the best case, it
can give you a &amp;ldquo;platonic ideal&amp;rdquo; of performance for primitive operations (which real world results can approach, but
will likely never achieve). These methods are being benchmarked in pretty much the most optimal conditions possible,
branches are highly predictable, memory access patterns are similar every run, etc&amp;hellip; It is difficult to overstate
how much this can affect performance.&lt;/p&gt;
&lt;p&gt;The other major problem is that micro-benchmarking results are not composable: If &lt;code&gt;a()&lt;/code&gt; is benchmarked at 5ns, and
&lt;code&gt;b()&lt;/code&gt; is benchmarked at 10ns, is &lt;code&gt;a() + b()&lt;/code&gt; = 15ns? Absolutely not - microbenchmarking results fall apart when
you try to compose them. The benchmark of X.a() might be run with the precondition that condition Y is always true, and
the benchmark of X.b() might be run with the precondition that condition Y is always false&amp;hellip; The trivial example is
getHit() and getMiss() benchmarking in our results. The precondition of getHit() is that the lookup key is always in
the table. The precondition of getMiss() is that the lookup key is never in the table. Benchmarking for both can achieve
almost perfect branch prediction because of this. Real world performance is going to be highly dependent on the
exact pattern of hits/misses real world data gives you.&lt;/p&gt;
&lt;p&gt;In addition to the standard micro-benchmarking caveats, there are also hash table specific caveats. Notably,
hashtables that use tombstones for removal can have the tombstones substantially affect lookup times - but we
performed no benchmarking of removal + lookup scenarios. There are many other scenarios that could affect performance
that were also not covered in any benchmarking - it&amp;rsquo;s impossible to cover everything.&lt;/p&gt;
&lt;h3 class="heading-element" id="libraries-under-test"&gt;&lt;span&gt;Libraries Under Test&lt;/span&gt;
 &lt;a href="#libraries-under-test" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;h4 class="heading-element" id="jre"&gt;&lt;span&gt;JRE&lt;/span&gt;
 &lt;a href="#jre" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Java&amp;rsquo;s standard library hashtable uses separate chaining: each bucket holds a linked list that is automatically
promoted to a red-black tree once it grows too large, bounding worst-case lookup from O(n) to O(log n) per bucket
under adversarial key distributions. All keys are stored as boxed primitive objects on the heap; every key thus incurs
an allocation and a pointer indirection on access.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, array of node pointers holding boxed keys/values (can form a linked list or
red-black tree)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Separate chaining (linked list/red-black tree)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deletion Strategy&lt;/strong&gt;: Removal from linked list/red-black tree&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 75%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: The hash finalizer performs minimal mixing, relying on the red-black tree fallback
behavior to prevent pathological lookups.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return key ^ (key &amp;gt;&amp;gt;&amp;gt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="fastcollect"&gt;&lt;span&gt;FastCollect&lt;/span&gt;
 &lt;a href="#fastcollect" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;&lt;em&gt;Disclosure: I am the author of FastCollect.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;FastCollect is a Kotlin Multiplatform primitive collections library supporting JVM, JS, and native targets. The map
implementation currently uses Robin Hood hashtables because I wanted to play around with them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/sooniln/fastcollect" target="_blank" rel="external nofollow noopener noreferrer"&gt;sooniln/fastcollect&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;io.github.sooniln:fastcollect-kotlin-jvm:2.0.2&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: July 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, interleaved array if key/value size matches, parallel arrays otherwise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing (Robin Hood)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 83.3% (100% for very small tables)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: FastCollect takes a different approach to multiplicative hashing than most of the
other libraries benchmarked. Several of the other libraries all use the same hash finalizer (which appears to have
originally come from the Koloboke library many years ago - &lt;code&gt;(key * PHI) ^ ((key * PHI) &amp;gt;&amp;gt;&amp;gt; 16)&lt;/code&gt;). It seems worth
revisiting this finalizer as it exhibits several poor qualities that can be improved upon. While it is cheap, the
multiplication concentrates entropy in the high bits, yet it is only the low bits that will be used to derive the
slot. Some effort is made to ameliorate this with the xor-shift, but it seems insufficient. This finalizer exhibits
worse results when given non-random inputs such as highBits.&lt;/p&gt;
&lt;p&gt;For the FastCollect finalizer the key realization is that simply reversing the order of bits in the multiplication
result puts the most entropy exactly where we want it, in the low bits. The problem is that while bit-reversal is a
single instruction (RBIT) on ARM, x86-64 does not have a similar instruction. Divide and conquer + BSWAP approaches
and similar can make bit-reversal reasonably cheap, but not single cycle cheap like RBIT. Since benchmarking is
occurring on x86-64 we choose a slightly different approach: premix bits with xor-right-shift, multiply by PHI, and
then reverse the bytes (not bits) with BSWAP/REV.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = (key ^ (key &amp;gt;&amp;gt;&amp;gt; 16)) * 0x9E3779B9; // PHI
return Integer.reverseBytes(h);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It would be very interesting to look into RBIT finalizers more on ARM platforms, it seems a much cheaper way of
achieving higher levels of entropy in a targeted part of the bit pattern, rather than focusing on achieving even
levels of entropy in all parts of the pattern (as more complex functions like murmur3 and so forth appear to do). As
far as I can tell, using bit/byte reversal with Knuth multiplicative hashing appears to be novel, but it could be there
are hidden weaknesses to this approach that I am not aware of.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="fastutil"&gt;&lt;span&gt;Fastutil&lt;/span&gt;
 &lt;a href="#fastutil" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;One of the most comprehensive and widely-adopted JVM primitive collections libraries, Fastutil is very well known.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/vigna/fastutil" target="_blank" rel="external nofollow noopener noreferrer"&gt;vigna/fastutil&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;it.unimi.dsi:fastutil:8.5.18&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: October 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, parallel arrays for keys/values&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 75%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: Knuth multiplicative hashing with constant shift.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0x9E3779B9;
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="androidx"&gt;&lt;span&gt;AndroidX&lt;/span&gt;
 &lt;a href="#androidx" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Google&amp;rsquo;s Jetpack collection utilities library, now written in Kotlin and available as a Kotlin Multiplatform artifact.
The implementation is a Swiss Table design that uses SWAR (SIMD within a register) techniques to evaluate groups
of 8 entries at a time. AndroidX libraries are primarily intended for arm64, though they are distributed as
multi-platform. Since benchmarks were run on amd64, care should be taken not to generalize the results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://android.googlesource.com/platform/frameworks/support" target="_blank" rel="external nofollow noopener noreferrer"&gt;android.googlesource.com&lt;/a&gt; (&lt;a href="https://github.com/androidx/androidx" target="_blank" rel="external nofollow noopener noreferrer"&gt;GitHub mirror&lt;/a&gt;)&lt;br&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://developer.android.com/jetpack/androidx/releases/collection" target="_blank" rel="external nofollow noopener noreferrer"&gt;developer.android.com/jetpack/androidx/releases/collection&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;androidx.collection:collection-jvm:1.6.0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: March 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, metadata array of 1 byte/entry, parallel arrays for keys/values
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata Schema&lt;/strong&gt;: Stores low 7 bits of original hash as the metadata fingerprint, the high 25 bits are used to
drive the probe offset&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Quadratic Probing (where each probe checks a group of 8 linear entries)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Tombstones + rehash to remove tombstones at ~78% capacity&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 87.5%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: Murmur3 finalizer. Note that the left shift instead of right shift would seem to
concentrate hash entropy further in the upper bits. This makes some sense for the Swiss Table design, where the
initial probe position is calculated from the high bits and the fingerprint is derived from the low bits. But this
opens up the risk of perhaps too little entropy in the low bits and thus the fingerprint (see benchmarking results for
highBits order)&amp;hellip;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0xCC9E2D51; // MurmurHashC1
return h ^ (h &amp;lt;&amp;lt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="trove"&gt;&lt;span&gt;Trove&lt;/span&gt;
 &lt;a href="#trove" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;GNU Trove uses prime sizing and open addressing with double hashing: rather than a fixed linear step, each key
computes a secondary probe stride from its primary hash, so colliding keys scatter along independent paths and form
shorter clusters than linear probing would.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://bitbucket.org/trove4j/trove" target="_blank" rel="external nofollow noopener noreferrer"&gt;bitbucket.org/trove4j/trove&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;net.sf.trove4j:core:3.1.0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: August 2018 (due to module split - no active development since ~2012 apparently)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Prime sized, metadata array to track slot empty state + parallel arrays for keys/values
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata Schema&lt;/strong&gt;: Only tracks empty state, which means every probe step has to check both metadata array and
key arrays - would expect this to hurt performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Double hashing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Tombstones, rehash if there are no free slots (only tombstones) left, also rehash if removal
allows backing array to shrink to the previous prime size&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 50%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: The identity hash — no bit mixing is applied; distribution quality relies entirely on
the double-hashing probe and prime sized arrays to counteract clustering.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// double hashing
// slot - identity hash
return key;

// stride
return 1 &amp;#43; (key % (length - 2));&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="koloboke"&gt;&lt;span&gt;Koloboke&lt;/span&gt;
 &lt;a href="#koloboke" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;A high performance collections library from Roman Leventov, appears to have been designed with HFT in mind. It makes
use of deprecated Unsafe APIs for performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/leventov/Koloboke" target="_blank" rel="external nofollow noopener noreferrer"&gt;leventov/Koloboke&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;com.koloboke:koloboke-impl-jdk8:1.0.0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: May 2016&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, interleaved array if key/value size matches, parallel arrays otherwise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 66.7%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: Knuth multiplicative hashing with constant shift.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0x9E3779B9; // PHI
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="eclipse"&gt;&lt;span&gt;Eclipse&lt;/span&gt;
 &lt;a href="#eclipse" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Originally the Goldman Sachs Collections library, donated to the Eclipse Foundation in 2012. Eclipse forces a load
factor of 50% - much lower than any other library in this benchmark - and it cannot be adjusted. Beware assuming
an apples to apples comparison. Some experimentation with running other libraries with a 50% load factor indicates
they are competitive and outperform Eclipse, but full benchmarking has not been performed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/eclipse/eclipse-collections" target="_blank" rel="external nofollow noopener noreferrer"&gt;eclipse/eclipse-collections&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://eclipse.dev/collections/" target="_blank" rel="external nofollow noopener noreferrer"&gt;eclipse.dev/collections&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;org.eclipse.collections:eclipse-collections:13.0.0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: December 24, 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, interleaved array if key/value size matches, parallel arrays otherwise&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Combination (linear probing on hash1 for half a cache line → linear probing on hash2 for
half a cache line → double hashing)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Tombstones - tombstones count towards rehashing load factor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 50%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: 3 separate hash finalizers for the various probing steps. It&amp;rsquo;s not entirely clear where
the constants come from.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// hash1 - identity hash
return key;

// hash2 - murmur3 style finalizer
var h = key ^ (key &amp;gt;&amp;gt;&amp;gt; 14);
h *= 0xBA1CCD33;
h ^= h &amp;gt;&amp;gt;&amp;gt; 13;
h *= 0x9B6296CB;
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 12);

// --double hashing--
// double hashing slot
var h = key ^ (key &amp;gt;&amp;gt;&amp;gt; 15);
h *= 0xACAB2A4D;
h ^= h &amp;gt;&amp;gt;&amp;gt; 15;
h *= 0x5CC7DF53;
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 12);
// double hashing stride - odd stride ensures every slot is hit with power-of-two sized array
return Integer.reverse(hash2(key)) | 1;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="hppc"&gt;&lt;span&gt;HPPC&lt;/span&gt;
 &lt;a href="#hppc" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;High Performance Primitive Collections, maintained by Carrot Search.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/carrotsearch/hppc" target="_blank" rel="external nofollow noopener noreferrer"&gt;carrotsearch/hppc&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website&lt;/strong&gt;: &lt;a href="https://labs.carrotsearch.com/hppc.html" target="_blank" rel="external nofollow noopener noreferrer"&gt;labs.carrotsearch.com/hppc.html&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;com.carrotsearch:hppc:0.10.0&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release:&lt;/strong&gt; June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, parallel arrays for keys/values&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 75%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: Knuth multiplicative hashing with constant shift.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0x9E3779B9; // PHI
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="agrona"&gt;&lt;span&gt;Agrona&lt;/span&gt;
 &lt;a href="#agrona" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Agrona is Real Logic&amp;rsquo;s utility library, developed primarily to support the &lt;a href="https://github.com/aeron-io/aeron" target="_blank" rel="external nofollow noopener noreferrer"&gt;Aeron&lt;/a&gt;
messaging system. Agrona requires that every map must have an unrepresentable value passed in on initialization (to
mark empty slots); thus Agrona is incapable of representing the full range of values.&lt;/p&gt;
&lt;p&gt;Note: Since Agrona does not have a Long → Int map, benchmarking uses Long → Long maps instead, and widens values. Be
wary of apples to apples comparisons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/aeron-io/agrona" target="_blank" rel="external nofollow noopener noreferrer"&gt;aeron-io/agrona&lt;/a&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;org.agrona:agrona:2.4.1&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: April 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, interleaved arrays for keys/values (Agrona does not support keys and values
with different sizes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 65%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: Two-round XOR-shift/multiply from Chris Wellons&amp;rsquo; Hash Prospector.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key ^ (key &amp;gt;&amp;gt;&amp;gt; 16);
h = h * 0x119DE1F3;
h = h ^ (h &amp;gt;&amp;gt;&amp;gt; 16);
h = h * 0x119DE1F3;
return h ^ (h &amp;gt;&amp;gt;&amp;gt; 16);&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h4 class="heading-element" id="libgdx"&gt;&lt;span&gt;LibGDX&lt;/span&gt;
 &lt;a href="#libgdx" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;LibGDX is a cross-platform game development library, which as part of its utilities provides some primitive
collection implementations. It uses a default max load factor of 80%, which is higher than often found in linear
probing implementations.&lt;/p&gt;
&lt;p&gt;Note: LibGDX does not have a Long → Int map with primitive values, benchmarking uses Long → boxed&lt;!-- raw HTML omitted --&gt; maps instead.
Be wary of apples to apples comparisons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Source&lt;/strong&gt;: &lt;a href="https://github.com/libgdx/libgdx" target="_blank" rel="external nofollow noopener noreferrer"&gt;libgdx/libgdx&lt;/a&gt;
&lt;strong&gt;Maven&lt;/strong&gt;: &lt;code&gt;com.badlogicgames.gdx:gdx:1.14.2&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Last release&lt;/strong&gt;: June 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage Schema&lt;/strong&gt;: Power-of-two sized, parallel arrays for keys/values&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probing Strategy&lt;/strong&gt;: Linear probing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removal Strategy&lt;/strong&gt;: Backwards-shift deletion&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default Max Load Factor&lt;/strong&gt;: 80%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Integer key hash finalizer&lt;/strong&gt;: 64-bit widened Fibonacci hashing with size-based shift. This implementation of
Fibonacci hashing is more true to the original variant proposed by Knuth in that it uses a table size based shift.
The widening to a long helps increase entropy across the returned low bits more than the 32-bit version would.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return (int)(key * 0x9E3779B97F4A7C15L &amp;gt;&amp;gt;&amp;gt; Integer.countLeadingZeros(size - 1));&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 class="heading-element" id="benchmarks"&gt;&lt;span&gt;Benchmarks&lt;/span&gt;
 &lt;a href="#benchmarks" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Raw benchmark data can be downloaded &lt;a href="https://sooniln.github.io/libraries.csv"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While viewing graphs, you may use the button to switch the time axis between log scale and linear scale. Clicking on
a library in the legend will hide/show the data for that library. The mouse can be used to pan/zoom the graphs.
Double-clicking a graph will reset its pan/zoom. Always keep in mind that the graphs default to a logarithmic scale
which makes a relative comparison harder to eyeball. Hover over data points for actual numbers. You&amp;rsquo;ll have a better
time looking at these graphs on a larger display (not your phone).&lt;/p&gt;
&lt;h3 class="heading-element" id="overall-results"&gt;&lt;span&gt;Overall Results&lt;/span&gt;
 &lt;a href="#overall-results" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;Rather than forcing you to scroll past the excessive graphs and numbers below which go into individual scenarios,
might as well cut to the chase, right?&lt;/p&gt;
&lt;h4 class="heading-element" id="memory-usage"&gt;&lt;span&gt;Memory Usage&lt;/span&gt;
 &lt;a href="#memory-usage" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Memory usage is measured with the &lt;a href="https://github.com/openjdk/jol" target="_blank" rel="external nofollow noopener noreferrer"&gt;JOL (Java Object Layout)&lt;/a&gt; library. Note that
several libraries use pretty much the exact same amount of memory, and thus their data series are overlaid on top of
one another in the graph. Recall that clicking a library in the legend will hide/show its data series.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-0-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-0"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.memory";
 var ORDER = "";
 var CHART_ID = "bmchart-0";
 var TITLE = "Int → Int Map Memory Usage";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Results are pretty much as we&amp;rsquo;d expect - JRE maps which store objects take up 3-6.5x as much memory as maps which store
primitives. The effects of prime sizing (Trove) vs power-of-two sizing (everyone else) and different load factors
are also on display.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-1-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-1"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.memory";
 var ORDER = "";
 var CHART_ID = "bmchart-1";
 var TITLE = "Long → Int Map Memory Usage";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;For Long maps results are relatively the same, except that Agrona now uses more memory (recall that as a
special-purpose library Agrona does not support Long → Int maps, and thus we use its Long → Long map instead).&lt;/p&gt;
&lt;h4 class="heading-element" id="read-performance"&gt;&lt;span&gt;Read Performance&lt;/span&gt;
 &lt;a href="#read-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Read performance is calculated as the geometric mean of GetHit and GetMiss benchmarks. This weights both hits and
misses equally, an assumption which may not be true in various real world workloads. Results for the three orderings
(random, lowBits, highBits) follow:&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-2-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-2"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.readGM";
 var ORDER = "random";
 var CHART_ID = "bmchart-2";
 var TITLE = "Map Read Geometric Mean — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (random)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.537&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8.681&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.421*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8.442&lt;/td&gt;
					&lt;td style="text-align: right"&gt;10.475&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9.274&lt;/td&gt;
					&lt;td style="text-align: right"&gt;11.858&lt;/td&gt;
					&lt;td style="text-align: right"&gt;11.060&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9.578&lt;/td&gt;
					&lt;td style="text-align: right"&gt;17.717&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As this is the first graph we&amp;rsquo;re looking at, note the distinct regime changes as the hashtable size increases past
L1/L2/L3 cache sizes. For many of the power-of-two sized graphs we see the distinct sawtooth performance that comes
from different loads as the table is queried when more empty vs more full (the sizes benchmarked were explicitly
chosen to differentiate this).&lt;/p&gt;
&lt;p&gt;Looking at AndroidX, we can note a couple of things - (1) impressive read performance from ~32K entries onwards -
comparable with Eclipse operating at a much lower load factor (2) a more subdued change in performance as size
increases (thanks to a much smaller metadata array), and (3) it hits a performance cliff and times out around 32M
entries. Further analysis of AndroidX can be found later.&lt;/p&gt;
&lt;p&gt;We also note that JRE read performance is relatively competitive (although it uses far more memory of course).
This is a drastic improvement from the benchmarking performed in 2014.&lt;/p&gt;
&lt;p&gt;Eclipse is the obvious fastest across all sizes - however how much of its performance is due to the fact that it is
using an unfairly lower (for comparison purposes) load factor? We&amp;rsquo;ll dig into that later as well.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-3-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-3"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.readGM";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-3";
 var TITLE = "Map Read Geometric Mean — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (lowBits)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.554&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7.734&lt;/td&gt;
					&lt;td style="text-align: right"&gt;2.969*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;6.521&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9.390&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8.877&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7.100&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9.454&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.268&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.313&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Recall that lowBits keys concentrate their entropy in the lower bits, which is where most of these tables calculate
slot positions from. The biggest change we see here is with Trove performance (it was one of the slowest for random
keys, but is now among the fastest for lowBits keys). As covered earlier, Trove uses the identity hash (which is
&amp;ldquo;perfect&amp;rdquo; for lowBits keys), so it is unsurprising that it performs so well with them. Eclipse also improves its
performance - Eclipse&amp;rsquo;s first choice of hash finalizer is also the identity hash.&lt;/p&gt;
&lt;p&gt;LibGDX also performs quite well, beating Trove for most of the size range, until larger sizes. Fibonacci hashing
without modifications performs quite well.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-4-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-4"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.readGM";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-4";
 var TITLE = "Map Read Geometric Mean — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (highBits)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.619&lt;/td&gt;
					&lt;td style="text-align: right"&gt;71.019&lt;/td&gt;
					&lt;td style="text-align: right"&gt;8.944*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;6.565&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.002&lt;/td&gt;
					&lt;td style="text-align: right"&gt;61.927&lt;/td&gt;
					&lt;td style="text-align: right"&gt;17.151&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.079&lt;/td&gt;
					&lt;td style="text-align: right"&gt;6.464&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.083&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Recall that highBits keys concentrate their entropy in the higher bits, which most of these tables ignore in order
to calculate slot positions. So we expect this to be an interesting &amp;ldquo;adversarial&amp;rdquo; order, and the results bear that
out.&lt;/p&gt;
&lt;p&gt;AndroidX and HPPC in particular show pathological performance degradation, especially at smaller sizes, with
AndroidX managing to take ~1600ns on average for a lookup in the absolute worst case. Fastutil also sees performance
taking a hit, though not to the same extreme factor.&lt;/p&gt;
&lt;p&gt;Interestingly, if we look at JRE performance, not only is it quite reasonable at lower sizes (though it&amp;rsquo;s also
brought to its knees at higher sizes), it&amp;rsquo;s possible that we&amp;rsquo;re able to see the effect of JRE&amp;rsquo;s red-black tree fallback
ameliorating the performance hit at larger sizes (though I have not confirmed that&amp;rsquo;s what&amp;rsquo;s actually happening here)?&lt;/p&gt;
&lt;p&gt;FastCollect and LibGDX perform sufficiently well with highBits keys that they are able to out-perform even Eclipse,
though Eclipse is operating at a load factor of 50% and FastCollect/LibGDX at 83/80%.&lt;/p&gt;
&lt;h4 class="heading-element" id="write-performance"&gt;&lt;span&gt;Write Performance&lt;/span&gt;
 &lt;a href="#write-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Write performance is calculated as the geometric mean of the PutHit, PutMiss, and RemoveAndPutMiss benchmarks. This
weights miss benchmarks higher, as they are represented twice (PutMiss and RemoveAndPutMiss), an assumption which may
not be true in various real world workloads. Results for the three orderings (random, lowBits, highBits) follow:&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-5-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-5"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.writeGM";
 var ORDER = "random";
 var CHART_ID = "bmchart-5";
 var TITLE = "Map Write Geometric Mean — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (random)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.426&lt;/td&gt;
					&lt;td style="text-align: right"&gt;19.661&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.412*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;16.997&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.612&lt;/td&gt;
					&lt;td style="text-align: right"&gt;13.792&lt;/td&gt;
					&lt;td style="text-align: right"&gt;31.936&lt;/td&gt;
					&lt;td style="text-align: right"&gt;16.535&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.161&lt;/td&gt;
					&lt;td style="text-align: right"&gt;31.215&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We immediately see some patterns which look the same as from the read results, and note that AndroidX again hits a
performance cliff and times out at higher sizes. JRE is less competitive for writes than reads it would also appear,
and while Eclipse is again generally the fastest, it isn&amp;rsquo;t as dominant as it was for reads (again with the caveat
that Eclipse is operating at a different load factor than all the other libraries).&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-6-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-6"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.writeGM";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-6";
 var TITLE = "Map Write Geometric Mean — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (lowBits)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.397&lt;/td&gt;
					&lt;td style="text-align: right"&gt;19.466&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7.972*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.913&lt;/td&gt;
					&lt;td style="text-align: right"&gt;16.549&lt;/td&gt;
					&lt;td style="text-align: right"&gt;13.206&lt;/td&gt;
					&lt;td style="text-align: right"&gt;20.959&lt;/td&gt;
					&lt;td style="text-align: right"&gt;15.448&lt;/td&gt;
					&lt;td style="text-align: right"&gt;11.380&lt;/td&gt;
					&lt;td style="text-align: right"&gt;15.668&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Again, similar patterns for lowBits in writes from reads. Trove performs better with lowBits keys than random keys
(thanks to its identity finalizer), and the same for Eclipse (with its first choice identity finalizer).&lt;/p&gt;
&lt;p&gt;Once again, LibGDX also has excellent performance through the whole size range.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-7-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-7"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.writeGM";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-7";
 var TITLE = "Map Write Geometric Mean — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (highBits)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.470&lt;/td&gt;
					&lt;td style="text-align: right"&gt;220.477&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.939*&lt;/td&gt;
					&lt;td style="text-align: right"&gt;14.154&lt;/td&gt;
					&lt;td style="text-align: right"&gt;34.261&lt;/td&gt;
					&lt;td style="text-align: right"&gt;139.099&lt;/td&gt;
					&lt;td style="text-align: right"&gt;70.280&lt;/td&gt;
					&lt;td style="text-align: right"&gt;27.543&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.744&lt;/td&gt;
					&lt;td style="text-align: right"&gt;36.299&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And for highBits keys we see the same patterns for AndroidX and HPPC, awful performance, especially at lower sizes.
Fastutil shows the same degradation, just not as bad.&lt;/p&gt;
&lt;h4 class="heading-element" id="iteration-performance"&gt;&lt;span&gt;Iteration Performance&lt;/span&gt;
 &lt;a href="#iteration-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Since iteration is agnostic to actual key values, it is simply the geometric mean of Int and Long keyed map iterations.&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-8-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-8"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "Map.forEachGM";
 var ORDER = "";
 var CHART_ID = "bmchart-8";
 var TITLE = "Int → Int / forEach";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style="text-align: right"&gt;1180.189&lt;/td&gt;
					&lt;td style="text-align: right"&gt;394.426&lt;/td&gt;
					&lt;td style="text-align: right"&gt;1493.984&lt;/td&gt;
					&lt;td style="text-align: right"&gt;1136.482&lt;/td&gt;
					&lt;td style="text-align: right"&gt;996.386&lt;/td&gt;
					&lt;td style="text-align: right"&gt;1910.326&lt;/td&gt;
					&lt;td style="text-align: right"&gt;1566.672&lt;/td&gt;
					&lt;td style="text-align: right"&gt;917.517&lt;/td&gt;
					&lt;td style="text-align: right"&gt;1287.967&lt;/td&gt;
					&lt;td style="text-align: right"&gt;826.078&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;From roughly ~16K entries onwards, JRE iteration is slower than every other library - a direct consequence of it
using more memory. Accessing more memory in a linear fashion takes more time - no two ways about it. Without
investigation, it&amp;rsquo;s difficult to say why JRE iteration is competitive for fewer entries, but I would tend to assume
cache effects?&lt;/p&gt;
&lt;p&gt;On the opposite side, AndroidX has by far the fastest iteration up to ~16K entries - this is almost certainly due to
the fact that skipping empty slots only requires touching the much smaller metadata array for AndroidX. Since it&amp;rsquo;s
so much smaller, it&amp;rsquo;s much easier for it to stay in cache, even at larger sizes. Once a non-empty slot is
reached however, AndroidX will still need to load from the key/value arrays. I would suspect that up to ~16K
elements, metadata/keys/values are all able to stay in cache relatively well, but after keys/values drop out of
cache, iteration slows.&lt;/p&gt;
&lt;p&gt;HPPC however has unexpectedly slow iteration! This is a consequence of a design decision HPPC has made to prevent the
user from shooting themselves in the foot with naive copy operations. Most of these libraries under test iterate in the
same order as elements appear in the key or metadata array via their hashcode, which means iteration indirectly exposes
hash order. If iteration order is then used for insertion again (for example copying every element into
another map), this can be a worst case scenario and
&lt;a href="https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion" target="_blank" rel="external nofollow noopener noreferrer"&gt;lead to quadratic runtimes&lt;/a&gt;
(but the amelioration is also generally trivial, simply pre-size the map appropriately). In order to prevent
potentially quadratic runtimes from occurring, HPPC uses a random step length for iteration - iteration no longer
exposes hash ordering, but is also now not very cache friendly and thus is slower. Many of the other libraries
benchmarked here suffer the same problem, but generally ignore it and allow the user to shoot themselves in the foot
if they aren&amp;rsquo;t careful with proper pre-sizing so that iteration is not artificially slowed.&lt;/p&gt;
&lt;p&gt;Indeed, FastCollect and LibGDX suffers the same problem as well. FastCollect takes a slightly different approach to
solve the problem, however. Any linear probing approach means we can expect calculable upper bounds on maximum run
length for various map sizes (while there are indeed formulas to calculate this -
see &lt;a href="https://www.cs.tau.ac.il//~zwick/Adv-Alg-2015/Linear-Probing.pdf" target="_blank" rel="external nofollow noopener noreferrer"&gt;https://www.cs.tau.ac.il//~zwick/Adv-Alg-2015/Linear-Probing.pdf&lt;/a&gt; for more information - I skipped that and
simply used Monte Carlo simulations). I.e. given a map with a backing array of length 65536 which is ~83.3% full, we
would expect VERY roughly a maximum run length (consecutive non-empty slots) of ~500 at the 99.9th percentile. So
if we see a run length &amp;gt; 500 in a map of that size, it&amp;rsquo;s extraordinarily likely that something is going drastically
wrong. And further, if we&amp;rsquo;re in the accidentally quadratic re-insertion scenario discussed above, we&amp;rsquo;ll likely
encounter that warning after only ~500 elements - quite early in the re-insertion! When FastCollect detects these
pathologically long run lengths that should never be expected under any normal scenario, it will pre-emptively
resize the map larger, with the effect of ameliorating quadratic runtimes. This adds slightly more complex logic to
detect run lengths during insertion, but correct branch prediction minimizes the cost of this and still allows a fast
iteration implementation.&lt;/p&gt;
&lt;h4 class="heading-element" id="copy-performance"&gt;&lt;span&gt;Copy Performance&lt;/span&gt;
 &lt;a href="#copy-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Here we&amp;rsquo;ll only examine pre-allocated copy performance, as we really discussed all the interesting bits of naive
copy performance just now under the iteration section (though I encourage you to check out the naiveCopy results as
well, in particular to observe LibGDX&amp;rsquo;s performance under the &lt;a href="#all-results"&gt;All Results&lt;/a&gt; section).&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-9-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-9"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.preAllocatedCopy";
 var ORDER = "";
 var CHART_ID = "bmchart-9";
 var TITLE = "Int → Int / preAllocatedCopy";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;LibGDX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td style="text-align: right"&gt;774.117&lt;/td&gt;
					&lt;td style="text-align: right"&gt;576.580&lt;/td&gt;
					&lt;td style="text-align: right"&gt;490.935&lt;/td&gt;
					&lt;td style="text-align: right"&gt;18.716&lt;/td&gt;
					&lt;td style="text-align: right"&gt;138.692&lt;/td&gt;
					&lt;td style="text-align: right"&gt;620.683&lt;/td&gt;
					&lt;td style="text-align: right"&gt;259.533&lt;/td&gt;
					&lt;td style="text-align: right"&gt;121.097&lt;/td&gt;
					&lt;td style="text-align: right"&gt;134.853&lt;/td&gt;
					&lt;td style="text-align: right"&gt;288.608&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There&amp;rsquo;s perhaps not too much surprising here - copy performance is tied to write performance, except that
FastCollect is able to substantially outperform all other maps. FastCollect attempts to use direct memcpy whenever
possible in copying, and for the simple copy performed here (start with an empty map, copy all elements from another
map, which is already sized correctly) this yields much faster performance than manual iteration and insertion.&lt;/p&gt;
&lt;h3 class="heading-element" id="further-investigations"&gt;&lt;span&gt;Further Investigations&lt;/span&gt;
 &lt;a href="#further-investigations" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;h4 class="heading-element" id="load-factor--eclipse-performance"&gt;&lt;span&gt;Load Factor &amp;amp; Eclipse Performance&lt;/span&gt;
 &lt;a href="#load-factor--eclipse-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Eclipse generally out-performs all the other libraries we&amp;rsquo;ve benchmarked - but we&amp;rsquo;ve hypothesized that this is due
to the fact that it uses a load factor of 50%, where every other library uses a load factor of 75% or higher. Is
this actually true though? Or is the load factor not actually the defining factor in Eclipse&amp;rsquo;s performance? We couldn&amp;rsquo;t
perform full benchmarking (limited time), but here are the results for read geomean performance with all libraries
set to 50% load factor (AndroidX excluded since load factor is not adjustable, and FastCollect using a build with
load factor adjusted manually).&lt;/p&gt;







&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-10-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-10"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "MapLF50.readGM";
 var ORDER = "random";
 var CHART_ID = "bmchart-10";
 var TITLE = "50% LF Map Read Geometric Mean — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;p&gt;Geomeans across all sizes (lower is better):&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Agrona&lt;/th&gt;
					&lt;th style="text-align: right"&gt;AndroidX&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Eclipse&lt;/th&gt;
					&lt;th style="text-align: right"&gt;FastCollect&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Fastutil&lt;/th&gt;
					&lt;th style="text-align: right"&gt;HPPC&lt;/th&gt;
					&lt;th style="text-align: right"&gt;JRE&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Koloboke&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Trove&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Geomean (random)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;7.360&lt;/td&gt;
					&lt;td style="text-align: right"&gt;n/a&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.900&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.469&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.869&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.778&lt;/td&gt;
					&lt;td style="text-align: right"&gt;9.344&lt;/td&gt;
					&lt;td style="text-align: right"&gt;5.669&lt;/td&gt;
					&lt;td style="text-align: right"&gt;12.022&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With all libraries at 50% load factor, Eclipse is now solidly middle of the pack for read performance, and
FastCollect is now the fastest by a small margin. We can conclude that load factor was indeed the driving reason behind
Eclipse&amp;rsquo;s performance.&lt;/p&gt;
&lt;p&gt;At lower load factors like 50%, there&amp;rsquo;s a higher probability that any given entry will be residing in its home slot
(the slot it hashed to), meaning that this rewards libraries that have a fast home slot pathway. For a Robin Hood
hashtable, this also strongly increases the probability that if any entry is not at its home slot, it can be found
on the same cache line as the home slot, and thus reduces the probability of a cache miss.&lt;/p&gt;
&lt;h4 class="heading-element" id="androidx-oddities"&gt;&lt;span&gt;AndroidX Oddities&lt;/span&gt;
 &lt;a href="#androidx-oddities" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;AndroidX benchmarks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Appear to hit a performance cliff at &amp;gt; ~32 million entries&lt;/li&gt;
&lt;li&gt;Timeout at very high numbers of entries, so there is no data for &amp;gt; ~32 million entries&lt;/li&gt;
&lt;li&gt;Exhibit extremely poor performance with highBits keys.&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 class="heading-element" id="performance-cliff-at-32-million"&gt;&lt;span&gt;Performance Cliff At ~32 Million&lt;/span&gt;
 &lt;a href="#performance-cliff-at-32-million" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h5&gt;&lt;p&gt;With about 32 million entries, AndroidX&amp;rsquo;s metadata array (1 byte per entry) jumps from ~32MB to ~64MB, and thus no
longer fits in the L3 cache of the benchmarking machine (32MB). Almost every probe is hitting cold memory
(perhaps further exacerbated by the geometric probing sequence?). It&amp;rsquo;s worth remembering that AndroidX is primarily
intended for use on Android devices, which are likely to have a much smaller L3 cache than the desktop device used
for this benchmarking, but also are unlikely to be dealing with such large maps.&lt;/p&gt;
&lt;h5 class="heading-element" id="benchmark-timeout"&gt;&lt;span&gt;Benchmark Timeout&lt;/span&gt;
 &lt;a href="#benchmark-timeout" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h5&gt;&lt;p&gt;The timeout which leads to no benchmark data is not occurring within the benchmark, but within the setup method for
the benchmark, while the hashtable is being loaded. AndroidX does not offer any ensureCapacity() functionality,
meaning we cannot pre-size the table appropriately to reduce the cost of loading it - loading must go through a full
set of rehashes, which is much more expensive. There are a couple other tables that also do not offer ensureCapacity()
either however and do not time out, why is AndroidX still more expensive than these tables?&lt;/p&gt;
&lt;p&gt;This comes down to a couple features of AndroidX&amp;rsquo;s design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Three separate tables (metadata, keys, values) - more cache misses than 1-2 tables.&lt;/li&gt;
&lt;li&gt;Metadata table must be initialized with a non-zero value - 2x the initialization cost on this table.&lt;/li&gt;
&lt;li&gt;Higher load factor (87.5%) means more elements to rehash every rehash.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I suspect that if AndroidX offered an &lt;code&gt;ensureCapacity()&lt;/code&gt; API it would ameliorate these issues sufficiently to allow for
benchmarking at higher sizes and further investigate the performance cliff. Note that AndroidX does allow pre-sizing
via the constructor - and we technically could have made use of this in the benchmark. However, that would have
necessitated a different benchmarking structure for AndroidX than for every other library, and wasn&amp;rsquo;t worth the
trouble.&lt;/p&gt;
&lt;h5 class="heading-element" id="performance-drop-for-highbits-keys"&gt;&lt;span&gt;Performance Drop For HighBits Keys&lt;/span&gt;
 &lt;a href="#performance-drop-for-highbits-keys" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h5&gt;&lt;p&gt;In order to understand why AndroidX is so much more affected by highBits keys we&amp;rsquo;ll need to dig into how exactly the
library handles storage in the first place. AndroidX primarily uses a metadata array for lookups, with an equivalent
key and value array. We&amp;rsquo;ll focus only on the metadata array for now. When a key is hashed, the resulting 32-bit hash
is separated into two parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H1: the hash&amp;rsquo;s 25 most significant bits - used to determine the initial slot via masking by table size&lt;/li&gt;
&lt;li&gt;H2: the hash&amp;rsquo;s 7 least significant bits - stored as a fingerprint within the metadata array&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s examine what happens with a highBits example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int hash(int key) {
 var h = key * 0xCC9E2D51; // MurmurHashC1
 return h ^ (h &amp;lt;&amp;lt; 16);
}

key = 11011000000000000000000000000000
hash = 01011000000000000000000000000000
slot = 0 // 0101100000000000000000000 &amp;amp; 1111 (table size is 16 in this example)
fingerprint = 0000000

key = 01010110000000000000000000000000
hash = 00110110000000000000000000000000
slot = 0 // 0011011000000000000000000 &amp;amp; 1111 (table size is 16 in this example)
fingerprint = 0000000&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that the hash function has barely changed the key at all - the multiplication is not terribly effective as
expected, but where other libraries use a xor-right-shift to introduce a little more entropy to lower bits, AndroidX&amp;rsquo;s
xor-left-shift accomplishes nothing. The xor-left-shift is much more useful when more of the entropy is in the lower
bits, as is the case for lowBits keys for example. The resulting slot and fingerprint are exactly the same, meaning
we&amp;rsquo;ve achieved the absolute worst case - many keys all mapping to the same slot (and then distributed via geometric
probing), and each fingerprint matching every probe (so every group of metadata has to be explicitly compared with
the key which lives in a different array).&lt;/p&gt;
&lt;h4 class="heading-element" id="hppc-highbits-performance"&gt;&lt;span&gt;HPPC HighBits Performance&lt;/span&gt;
 &lt;a href="#hppc-highbits-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;HPPC is in many ways identical to Fastutil - same basic structure, same hash finalizer, same algorithms. So why does
its performance differ so drastically from Fastutil (and the other similar libraries) specifically for highBits
keys? The answer lies in the details - we are looking at the geomean between both Int → Int and Long → Int maps. For
Int → Int maps HPPC does indeed use the same hash finalizer as Fastutil and other maps, and sees identical
performance. However for Long keys, HPPC uses a different finalizer:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0x9e3779b97f4a7c15L; // PHI
return (int) (h ^ (h &amp;gt;&amp;gt;&amp;gt; 32));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compare this to the Long key finalizer used by Fastutil and other libraries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var h = key * 0x9e3779b97f4a7c15L; // PHI
h = h ^ (h &amp;gt;&amp;gt;&amp;gt; 32);
return (int) (h ^ (h &amp;gt;&amp;gt;&amp;gt; 16)); // second fold shifts entropy lower&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;HPPC&amp;rsquo;s lack of an additional fold to spread entropy downwards leads to substantially worse performance for Long maps
on highBits keys, and this is visible in the final geometric mean results.&lt;/p&gt;
&lt;h4 class="heading-element" id="libgdx-naivecopy-performance"&gt;&lt;span&gt;LibGDX NaiveCopy Performance&lt;/span&gt;
 &lt;a href="#libgdx-naivecopy-performance" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;LibGDX has excellent all-around performance except in one area, the
&lt;a href="#int--int--naivecopy--preallocatedcopy"&gt;naive copy benchmark&lt;/a&gt;. The root cause is LibGDX&amp;rsquo;s hash finalizer (reproduced
here):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return (int)(key * 0x9E3779B97F4A7C15L &amp;gt;&amp;gt;&amp;gt; Integer.countLeadingZeros(size - 1));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To start, this is an especially bad case of
&lt;a href="https://accidentallyquadratic.tumblr.com/post/153545455987/rust-hash-iteration-reinsertion" target="_blank" rel="external nofollow noopener noreferrer"&gt;accidentally quadratic re-insertion&lt;/a&gt;.
While all the libraries (except JRE and HPPC) under test are vulnerable to this to some degree, it&amp;rsquo;s much, much worse
for LibGDX due to the hash finalizer it uses. This hash finalizer ends up taking the top N bits of the Fibonacci
multiply (even after masking). In contrast, other libraries are taking the bottom N bits (because they use a
constant shift, not one based on the current table size).&lt;/p&gt;
&lt;p&gt;This matters in the naive copy scenario where we are filling in a very small map from a much larger map, in hash
order (because iteration order is the same as hash order). If we&amp;rsquo;re using the bottom N bits, then for the first half
of re-insertion things proceed relatively normally, and it&amp;rsquo;s only when we get to the second half of re-insertion
that insertions begin to &amp;ldquo;stack up&amp;rdquo; and we begin to get quadratic behavior (since growing a table exposes 1 &amp;rsquo;new&amp;rsquo;
bit from the hash). In contrast, if we&amp;rsquo;re using top N bits, then we begin to encounter quadratic behavior almost
immediately on re-insertion, and it affects the entire process. This is perhaps best illustrated with an example.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider a key with the hash 10100000101101 (16-bits for brevity&amp;rsquo;s sake). What slot does this key live at for
various table sizes?&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Table Size&lt;/th&gt;
					&lt;th&gt;Slot From High-Bits&lt;/th&gt;
					&lt;th&gt;Slot From Low-Bits&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;16&lt;/td&gt;
					&lt;td&gt;1010 = 10&lt;/td&gt;
					&lt;td&gt;1101 = 13&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;32&lt;/td&gt;
					&lt;td&gt;10100 = 20&lt;/td&gt;
					&lt;td&gt;01101 = 13&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;64&lt;/td&gt;
					&lt;td&gt;101000 = 40&lt;/td&gt;
					&lt;td&gt;101101 = 45&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;128&lt;/td&gt;
					&lt;td&gt;1010000 = 80&lt;/td&gt;
					&lt;td&gt;0101101 = 45&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now let&amp;rsquo;s observe the pattern - for high-bits, slot(n+1) = slot(n)*2 + newbit - each grow refines the same coarse
bucket into two finer ones. For low-bits, growing the table only adds a new high bit and the previous slot
assignment is either unchanged or shifts by 2^(n-1).&lt;/p&gt;
&lt;p&gt;With that in mind, let&amp;rsquo;s look at what happens if we iterate a large table in hash order - we could get multiple keys
that all map to the same slot because of hash collisions. What happens when we try to insert them in the smaller
table, what slot do they map to in the smaller table? We take 8 keys that all maps to slot 40 (ie a prefix of 101000)
of a 64-slot table and check what slot they map to in a 16 slot table:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;key&lt;/th&gt;
					&lt;th&gt;Destination Slot From High-Bits&lt;/th&gt;
					&lt;th&gt;Destination Slot From Low-Bits&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;35&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;124&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;179&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;15&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;268&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;357&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;412&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;501&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;9&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;556&lt;/td&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;12&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Those collisions on re-insertion is what leads to awful quadratic runtimes. Interestingly, I ran into the exact same
problem while writing the FastCollect library, and this is what inspired thinking about hash bit-reversal as a
technique. With bit reversal, you can put the maximum entropy bits into the low positions without needing a size
dependent shift, and thus avoiding this problem. As a general purpose library, it didn&amp;rsquo;t feel acceptable for
FastCollect to allow such a massive foot-gun for the client, even if it&amp;rsquo;s a rarer code path. That meant accepting
slightly worse get/put performance in order to ameliorate the foot-gun. Note that LibGDX is not intended to be a
general purpose library, and thus its calculus may be different. One more thing to note however, while this shows up
in naive copy scenarios, it&amp;rsquo;s also pretty easy for a malicious client to exploit intentionally&amp;hellip; As always, there
is a trade-off between performance and security.&lt;/p&gt;
&lt;h3 class="heading-element" id="final-takeaways"&gt;&lt;span&gt;Final Takeaways&lt;/span&gt;
 &lt;a href="#final-takeaways" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;We&amp;rsquo;ve confirmed the basics - compared to the JRE HashMap, hashtables specialized for primitives may deliver up to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;3-6.5x memory savings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2-3x CPU savings&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This isn&amp;rsquo;t always guaranteed however - there are scenarios where the JRE HashMap is competitive with the more
specialized hashtables for CPU usage. In the several different libraries we&amp;rsquo;ve explored, what stands out?&lt;/p&gt;
&lt;h4 class="heading-element" id="jre-1"&gt;&lt;span&gt;JRE&lt;/span&gt;
 &lt;a href="#jre-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;JRE HashMap is generally extremely competitive with primitive hashtables when it comes to GetMiss scenarios
(performing better than many of the libraries benchmarked), but is far slower than all other libraries in GetHit
scenarios. I&amp;rsquo;d hypothesize this is due to the two tier memory structure employed by separate chaining tables -
they are often able to skip entering the second tier of memory (the linked list) entirely in GetMiss scenarios.
If it&amp;rsquo;s necessary to enter the second tier (GetHit and Put scenarios) however, JRE HashMaps cannot compete on speed
(far more pointer dereferencing required, and likely non-linear memory access patterns).&lt;/p&gt;
&lt;p&gt;In the 2014 benchmarks, HashMap was ~9x (very roughly eyeballed, don&amp;rsquo;t take this too seriously) slower than
Koloboke in GetHit scenarios. In today&amp;rsquo;s benchmark, HashMap is only roughly ~2-3x slower! An impressive
improvement over the years. We might expect efforts like
&lt;a href="https://openjdk.org/projects/lilliput/" target="_blank" rel="external nofollow noopener noreferrer"&gt;Compressed Object Headers&lt;/a&gt; (available since Java 24-25, but not in 21
on which benchmarking was performed) to further reduce this distance (primarily for Double/Long keys however, since
for 4 byte values, padding will offset any header compression gains).&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="fastcollect-1"&gt;&lt;span&gt;FastCollect&lt;/span&gt;
 &lt;a href="#fastcollect-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;FastCollect performed quite well, beaten only by AndroidX at larger sizes in read geomean performance, and held
close to the fastest libraries in write geomean performance. In a separate build at 50% max load factor, FastCollect
was the fastest of all libraries in read scenarios. I originally chose to implement Robin Hood hashing for the fun of
it - there&amp;rsquo;s a certain elegance about the idea, and I was unfamiliar with it. While it&amp;rsquo;s worked out quite well, I am
unsure that Robin Hood hashing is worth it in the long run. There are certainly scenarios it shines in, but I think
for maximum performance simple linear probing without any hoopla may get closer to allowing the hardware to operate
the way it wants to with maximum performance. Linear scanning through memory is hard to beat. The only way to be
sure will be to write a linear probing implementation&amp;hellip;&lt;/p&gt;
&lt;p&gt;Simple linear probing results in tables that have more entries sitting at their home slots than a Robin Hood
table (since entries sitting at their home slots are considered &amp;ldquo;richest&amp;rdquo;, and thus &amp;ldquo;stolen from&amp;rdquo; more often). This
means that in GetHit scenarios, a non-RH table has (1) more entries that require only a single memory load since
they are sitting at their home slot (2) entries that are not sitting at their home slot tend to require more
probes to reach. However, since probe length increases logarithmically with table size (and since prefetching
can make linear accesses cheaper), this may not be as large a disadvantage as it first appears&amp;hellip; My overall
takeaway is that Robin Hood tables may in the long run not be worth the trouble, but I don&amp;rsquo;t have perfect clarity on
this. It&amp;rsquo;s very unclear how much of a performance difference is coming from the RH invariant vs the different
hash finalizer being used, and more experimentation is necessary.&lt;/p&gt;
&lt;p&gt;The idea of using bit/byte reversal in the hash finalizer appears to have been validated, as this approach
out-performed more standard finalizers for FastCollect specifically. As far as I can determine, this approach appears
to be novel (I couldn&amp;rsquo;t find any information on any other implementations using this technique). It&amp;rsquo;s still possible
someone with a deeper background in hashing than me could point out some fatal flaw. I am curious about benchmarking
this family of bit-reversing hash finalizers in a simple linear probing implementation - would that be
even faster? Worth experimenting with.&lt;/p&gt;
&lt;p&gt;Overall FastCollect&amp;rsquo;s performance was among the faster libraries, but there are avenues for further exploration.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="androidx-1"&gt;&lt;span&gt;AndroidX&lt;/span&gt;
 &lt;a href="#androidx-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;The AndroidX Swiss table design is clearly excellent for GetMiss scenarios at larger sizes, but average or poor at
most others. GetMiss demonstrates AndroidX&amp;rsquo;s strengths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AndroidX is very fast at iterating through groups of 8 entries at a time, and it can check if those 8 entries
may contain the lookup key in effectively a single operation. If a group of 8 entries may contain the key
however, AndroidX has to use more expensive bit-twiddling techniques to confirm the match and then extract
the correct entry.&lt;/li&gt;
&lt;li&gt;Even so, GetMiss performance is only advantageous outside the L2 cache regime (L3+). If the hashtable fits in L2
cache, AndroidX tends to be slower than other tables for GetMiss. I would hypothesize that below this size,
a combination of shorter run lengths (fewer entries to iterate through) and cheap cache misses (loading the
next entry hits L1/L2) means that AndroidX&amp;rsquo;s geometric probing is at a disadvantage. Other libraries which use
linear probing may also be benefiting more from prefetching?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C++ is largely dominated by Swiss tables (and similar designs that use SIMD APIs) these days - Java isn&amp;rsquo;t.
Unfortunately today, 8 years after Swiss tables were first released and 12 years after
&lt;a href="https://openjdk.org/projects/panama/" target="_blank" rel="external nofollow noopener noreferrer"&gt;Project Panama&lt;/a&gt; was introduced, Vector APIs are still an incubating
feature in Java. In addition, while I haven&amp;rsquo;t experimented with them myself, there are
&lt;a href="https://bluuewhale.github.io/posts/further-optimizing-my-java-swiss-table/" target="_blank" rel="external nofollow noopener noreferrer"&gt;some indications&lt;/a&gt; that even
the current incubating design is still slower than using bit-twiddling SWAR (SIMD In A Register) techniques
when it comes to hashtable usage.&lt;/p&gt;
&lt;p&gt;Interestingly, for GetHit performance AndroidX is only competitive in the L3 cache regime. When the table
either fits in L1/L2 or is larger than L3, AndroidX is substantially slower than other libraries. I do not
have a good hypothesis to explain this behavior.&lt;/p&gt;
&lt;p&gt;Finally, AndroidX has some interesting failure cases (highBits keys, and the performance cliff at ~32M entries) -
but it&amp;rsquo;s perhaps unlikely these would cause real world issues for most usages (who&amp;rsquo;s loading 32M entries into a
map on Android?).&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="eclipse-1"&gt;&lt;span&gt;Eclipse&lt;/span&gt;
 &lt;a href="#eclipse-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;Eclipse&amp;rsquo;s performance appeared exceptional, but as we investigated it became clear that this was entirely
due to using a much lower maximum load factor than any other library. Once this was accounted for, Eclipse
was reasonably in the middle of the pack.&lt;/p&gt;
&lt;p&gt;I was very interested to see how Eclipse&amp;rsquo;s multiple probing strategies (3 different hash functions) would work out.
The idea of first attempting the identity hash for half a cache line before switching to a more rigorous
finalizer seems like it could pay dividends, but at least for Eclipse, it doesn&amp;rsquo;t appear to have been a game
changer.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="fastutilhppckolobokeagrona"&gt;&lt;span&gt;Fastutil/HPPC/Koloboke/Agrona&lt;/span&gt;
 &lt;a href="#fastutilhppckolobokeagrona" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;All of these libraries implement pretty much the same hashtable design (linear probing, same hash finalizer, same
memory layouts), with only minor variations that lead to small differences in performance.&lt;/p&gt;
&lt;p&gt;Within this family of tables, HPPC tends to generally be the fastest and Agrona the slowest. This appears to be
more due to small differences in implementation rather than anything algorithmic.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="libgdx-1"&gt;&lt;span&gt;LibGDX&lt;/span&gt;
 &lt;a href="#libgdx-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;LibGDX uses classical Fibonacci hashing to achieve some of the fastest results in most categories, but collapses to
pathologically quadratic runtimes on naiveCopy scenarios. Whether this is a blocker depends a bit on your point of
view - naive copy scenarios are generally easy to avoid if you appropriately pre-size maps. However, the library
cannot enforce this, it&amp;rsquo;s up to the client, so the question becomes whether it is acceptable for a client to be able
to shoot itself in the foot if they don&amp;rsquo;t pre-size appropriately when required. It could also be considered a
security flaw, in that a malicious attacker can easily trigger a CPU DoS attack if it has control of the hashtable.
As previously mentioned however, none of these libraries other than the JRE HashMap are secure in that fashion -
it&amp;rsquo;s just that LibGDX&amp;rsquo;s implementation allows much worse DoSes.&lt;/p&gt;
&lt;p&gt;On the other hand, LibGDX may be a good example of proving out my hypothesis about simple linear probing out-performing
Robin Hood hashing in the end. It&amp;rsquo;s just a question of whether a workable hash function can be found.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 class="heading-element" id="trove-1"&gt;&lt;span&gt;Trove&lt;/span&gt;
 &lt;a href="#trove-1" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h4&gt;&lt;p&gt;The only prime-sized table here - it&amp;rsquo;s good to see and evaluate a different approach, but overall it couldn&amp;rsquo;t
really compete with the other libraries, and was frequently even outperformed by the JRE HashMap. Trove did perform
exceptionally well with lowBits keys due to using the identity hash finalizer, but if we&amp;rsquo;re specializing for
lowBits keys I suspect other table designs would out-perform Trove by using the identity hash as well. Trove&amp;rsquo;s&lt;br&gt;
general lack of performance appears to have been noted as early as 2017&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; as well.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In the end, performance across all these libraries is respectable (except perhaps Trove, which often performs worse
than the standard JRE HashMap). The larger benefit is memory savings - the secondary benefit is CPU savings. And
while it&amp;rsquo;s quite entertaining to squeeze every bit of performance possible out of these tables, the vast majority of
code written in the JVM ecosystem (outside of scientific and high-performance computing) is unlikely to care.&lt;/p&gt;
&lt;h2 class="heading-element" id="all-results"&gt;&lt;span&gt;All Results&lt;/span&gt;
 &lt;a href="#all-results" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;At this point, the only thing left is all the raw benchmarking results - feel free to continue scrolling if those
are of interest, otherwise there&amp;rsquo;s nothing more to read.&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--gethit"&gt;&lt;span&gt;Int → Int / getHit&lt;/span&gt;
 &lt;a href="#int--int--gethit" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-11-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-11"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getHit";
 var ORDER = "random";
 var CHART_ID = "bmchart-11";
 var TITLE = "Int → Int / getHit — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-12-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-12"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getHit";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-12";
 var TITLE = "Int → Int / getHit — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-13-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-13"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getHit";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-13";
 var TITLE = "Int → Int / getHit — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--getmiss"&gt;&lt;span&gt;Int → Int / getMiss&lt;/span&gt;
 &lt;a href="#int--int--getmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-14-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-14"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-14";
 var TITLE = "Int → Int / getMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-15-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-15"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-15";
 var TITLE = "Int → Int / getMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-16-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-16"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.getMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-16";
 var TITLE = "Int → Int / getMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--puthit"&gt;&lt;span&gt;Int → Int / putHit&lt;/span&gt;
 &lt;a href="#int--int--puthit" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-17-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-17"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putHit";
 var ORDER = "random";
 var CHART_ID = "bmchart-17";
 var TITLE = "Int → Int / putHit — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-18-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-18"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putHit";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-18";
 var TITLE = "Int → Int / putHit — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-19-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-19"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putHit";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-19";
 var TITLE = "Int → Int / putHit — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--putmiss"&gt;&lt;span&gt;Int → Int / putMiss&lt;/span&gt;
 &lt;a href="#int--int--putmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-20-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-20"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-20";
 var TITLE = "Int → Int / putMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-21-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-21"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-21";
 var TITLE = "Int → Int / putMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-22-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-22"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.putMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-22";
 var TITLE = "Int → Int / putMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--removeandputmiss"&gt;&lt;span&gt;Int → Int / removeAndPutMiss&lt;/span&gt;
 &lt;a href="#int--int--removeandputmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-23-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-23"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.removeAndPutMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-23";
 var TITLE = "Int → Int / removeAndPutMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-24-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-24"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.removeAndPutMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-24";
 var TITLE = "Int → Int / removeAndPutMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-25-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-25"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.removeAndPutMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-25";
 var TITLE = "Int → Int / removeAndPutMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="int--int--foreach"&gt;&lt;span&gt;Int → Int / forEach&lt;/span&gt;
 &lt;a href="#int--int--foreach" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-26-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-26"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.forEach";
 var ORDER = "";
 var CHART_ID = "bmchart-26";
 var TITLE = "Int → Int / forEach";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;h3 class="heading-element" id="int--int--naivecopy--preallocatedcopy"&gt;&lt;span&gt;Int → Int / naiveCopy + preAllocatedCopy&lt;/span&gt;
 &lt;a href="#int--int--naivecopy--preallocatedcopy" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-27-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-27"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.naiveCopy";
 var ORDER = "";
 var CHART_ID = "bmchart-27";
 var TITLE = "Int → Int / naiveCopy";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-28-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-28"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "IntMap.preAllocatedCopy";
 var ORDER = "";
 var CHART_ID = "bmchart-28";
 var TITLE = "Int → Int / preAllocatedCopy";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--gethit"&gt;&lt;span&gt;Long → Int / getHit&lt;/span&gt;
 &lt;a href="#long--int--gethit" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-29-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-29"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getHit";
 var ORDER = "random";
 var CHART_ID = "bmchart-29";
 var TITLE = "Long → Int / getHit — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-30-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-30"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getHit";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-30";
 var TITLE = "Long → Int / getHit — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-31-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-31"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getHit";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-31";
 var TITLE = "Long → Int / getHit — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--getmiss"&gt;&lt;span&gt;Long → Int / getMiss&lt;/span&gt;
 &lt;a href="#long--int--getmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-32-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-32"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-32";
 var TITLE = "Long → Int / getMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-33-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-33"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-33";
 var TITLE = "Long → Int / getMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-34-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-34"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.getMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-34";
 var TITLE = "Long → Int / getMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--puthit"&gt;&lt;span&gt;Long → Int / putHit&lt;/span&gt;
 &lt;a href="#long--int--puthit" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-35-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-35"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putHit";
 var ORDER = "random";
 var CHART_ID = "bmchart-35";
 var TITLE = "Long → Int / putHit — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-36-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-36"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putHit";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-36";
 var TITLE = "Long → Int / putHit — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-37-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-37"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putHit";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-37";
 var TITLE = "Long → Int / putHit — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--putmiss"&gt;&lt;span&gt;Long → Int / putMiss&lt;/span&gt;
 &lt;a href="#long--int--putmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-38-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-38"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-38";
 var TITLE = "Long → Int / putMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-39-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-39"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-39";
 var TITLE = "Long → Int / putMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-40-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-40"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.putMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-40";
 var TITLE = "Long → Int / putMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--removeandputmiss"&gt;&lt;span&gt;Long → Int / removeAndPutMiss&lt;/span&gt;
 &lt;a href="#long--int--removeandputmiss" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-41-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-41"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.removeAndPutMiss";
 var ORDER = "random";
 var CHART_ID = "bmchart-41";
 var TITLE = "Long → Int / removeAndPutMiss — random keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-42-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-42"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.removeAndPutMiss";
 var ORDER = "lowBits";
 var CHART_ID = "bmchart-42";
 var TITLE = "Long → Int / removeAndPutMiss — lowBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-43-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-43"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.removeAndPutMiss";
 var ORDER = "highBits";
 var CHART_ID = "bmchart-43";
 var TITLE = "Long → Int / removeAndPutMiss — highBits keys";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;h3 class="heading-element" id="long--int--foreach"&gt;&lt;span&gt;Long → Int / forEach&lt;/span&gt;
 &lt;a href="#long--int--foreach" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-44-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-44"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.forEach";
 var ORDER = "";
 var CHART_ID = "bmchart-44";
 var TITLE = "Long → Int / forEach";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;

&lt;h3 class="heading-element" id="long--int--naivecopy--preallocatedcopy"&gt;&lt;span&gt;Long → Int / naiveCopy + preAllocatedCopy&lt;/span&gt;
 &lt;a href="#long--int--naivecopy--preallocatedcopy" class="heading-mark"&gt;
 &lt;svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"&gt;&lt;path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"&gt;&lt;/path&gt;&lt;/svg&gt;
 &lt;/a&gt;
&lt;/h3&gt;&lt;p&gt;






&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-45-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-45"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.naiveCopy";
 var ORDER = "";
 var CHART_ID = "bmchart-45";
 var TITLE = "Long → Int / naiveCopy";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;








&lt;div class="benchmark-chart-wrap"&gt;
 &lt;div class="benchmark-chart-toolbar"&gt;
 &lt;button id="bmchart-46-yscale" class="benchmark-chart-btn active" title="Toggle y-axis scale"&gt;Linear Y-Axis&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="benchmark-chart-canvas"&gt;
 &lt;canvas id="bmchart-46"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;p class="benchmark-chart-hint"&gt;Scroll to zoom &amp;middot; drag to pan &amp;middot; double-click to reset&lt;/p&gt;
&lt;/div&gt;

&lt;script&gt;
(function () {
 var BENCHMARK = "LongMap.preAllocatedCopy";
 var ORDER = "";
 var CHART_ID = "bmchart-46";
 var TITLE = "Long → Int / preAllocatedCopy";
 var Y_SCALE = "log" === 'linear' ? 'linear' : 'logarithmic';

 var COLORS = [
 '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231',
 '#911eb4', '#42d4f4', '#f032e6', '#bfef45', '#fabed4'
 ];

 function fmtSize(n) {
 n = Math.round(n);
 if (n &gt;= 1 &lt;&lt; 20) return (n &gt;&gt;&gt; 20) + 'M';
 if (n &gt;= 1 &lt;&lt; 10) return (n &gt;&gt;&gt; 10) + 'K';
 return String(n);
 }

 window.__chartJsReady = window.__chartJsReady || new Promise(function (resolve) {
 function done() {
 if (window.ChartZoom) { Chart.register(ChartZoom); }
 resolve();
 }
 if (document.readyState === 'complete') {
 done();
 } else {
 document.addEventListener('DOMContentLoaded', done);
 }
 });

 window.__benchmarkCsvPromise = window.__benchmarkCsvPromise ||
 fetch('/libraries.csv', { cache: 'no-cache' }).then(function (r) { return r.text(); });

 window.__benchmarkDataPromise = window.__benchmarkDataPromise || window.__benchmarkCsvPromise.then(function (csv) {
 var lines = csv.split(/\r?\n/);
 var h = lines[0].split(',');
 var iB = h.indexOf('benchmark');
 var iL = h.indexOf('library');
 var iO = h.indexOf('order');
 var iS = h.indexOf('size');
 var iM = h.indexOf('median');
 var iU = h.indexOf('unit');

 var byKey = {};
 for (var i = 1; i &lt; lines.length; i++) {
 if (!lines[i]) continue;
 var c = lines[i].split(',');
 var key = c[iB] + '|' + c[iO];
 var entry = byKey[key];
 if (!entry) entry = byKey[key] = { unit: '', byLib: {} };
 if (!entry.unit &amp;&amp; iU &gt;= 0) entry.unit = c[iU];
 var lib = c[iL];
 var size = +c[iS];
 var median = +c[iM];
 if (!entry.byLib[lib]) entry.byLib[lib] = [];
 entry.byLib[lib].push({ x: Math.log2(size), y: median, _size: size });
 }
 return byKey;
 });

 function buildChart(data) {
 var entry = data[BENCHMARK + '|' + ORDER] || { unit: '', byLib: {} };
 var unit = entry.unit;
 var byLib = entry.byLib;

 var libs = Object.keys(byLib).sort();
 var datasets = libs.map(function (lib, i) {
 return {
 label: lib,
 data: byLib[lib].sort(function (a, b) { return a.x - b.x; }),
 borderColor: COLORS[i % COLORS.length],
 backgroundColor: 'transparent',
 borderWidth: 2,
 pointRadius: 2,
 pointHoverRadius: 5,
 tension: 0
 };
 });

 var canvas = document.getElementById(CHART_ID);
 var chart = new Chart(canvas, {
 type: 'line',
 data: { datasets: datasets },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 parsing: false,
 plugins: {
 title: { display: true, text: TITLE, font: { size: 14 } },
 legend: { labels: { usePointStyle: true, pointStyle: 'line', pointStyleWidth: 20 } },
 tooltip: {
 callbacks: {
 title: function (items) {
 return items.length ? 'Size: ' + items[0].raw._size : '';
 },
 label: function (ctx) {
 return ctx.dataset.label + ': ' + ctx.parsed.y.toFixed(2) + ' ' + unit;
 }
 }
 },
 zoom: {
 zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: 'xy' },
 pan: { enabled: true, mode: 'xy' }
 }
 },
 scales: {
 x: {
 type: 'linear',
 title: { display: true, text: 'Map Size (entries)' },
 ticks: {
 stepSize: 1,
 callback: function (v) { return fmtSize(Math.pow(2, v)); }
 }
 },
 y: { type: Y_SCALE, title: { display: true, text: 'Median (' + unit + ')' } }
 }
 }
 });

 canvas.addEventListener('dblclick', function () { chart.resetZoom(); });

 var btn = document.getElementById(CHART_ID + '-yscale');
 function updateButton() {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 btn.textContent = isLog ? 'Linear Y-Axis' : 'Logarithmic Y-Axis';
 btn.classList.toggle('active', isLog);
 }
 updateButton();
 btn.addEventListener('click', function () {
 var isLog = chart.options.scales.y.type === 'logarithmic';
 chart.options.scales.y.type = isLog ? 'linear' : 'logarithmic';
 updateButton();
 chart.resetZoom();
 chart.update();
 });
 }

 function loadAndBuild() {
 Promise.all([window.__benchmarkDataPromise, window.__chartJsReady]).then(function (results) {
 buildChart(results[0]);
 });
 }

 var canvasEl = document.getElementById(CHART_ID);
 if ('IntersectionObserver' in window) {
 var observer = new IntersectionObserver(function (entries) {
 entries.forEach(function (entry) {
 if (entry.isIntersecting) {
 observer.unobserve(entry.target);
 loadAndBuild();
 }
 });
 }, { rootMargin: '200px 0px' });
 observer.observe(canvasEl);
 } else {
 loadAndBuild();
 }
}());
&lt;/script&gt;
&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Diego Costa, Artur Andrzejak, Janos Seboek, and David Lo. 2017. Empirical Study of Usage and Performance of
Java Collections. In Proceedings of the 8th ACM/SPEC on International Conference on Performance Engineering (ICPE &amp;lsquo;17).
&lt;a href="https://doi.org/10.1145/3030207.3030221" target="_blank" rel="external nofollow noopener noreferrer"&gt;https://doi.org/10.1145/3030207.3030221&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item></channel></rss>