• Stars
    star
    152
  • Rank 244,685 (Top 5 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created over 10 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A java.util.HashMap compatible map that won't stall puts or gets when resizing
/*
 * Written by Gil Tene, based on Apache Harmony version of java.util.HashMap.
 */

PauselessHashMap: A java.util.HashMap compatible Map implementation that
performs background resizing for inserts, avoiding the common "resize/rehash"
outlier experienced by normal HashMap.

get() and put() operations are "pauseless" in the sense that they do not
block during resizing of the map. Other operations, like remove(), putAll(),
clear(), and the derivation of keysets and such *will* block for pending
resize operations.

Like HashMap, PauselessHashMap provides no synchronization or thread-safe
behaviors on it's own, and MUST be externally synchronized if used by multiple
threads. The background resizing mechanism relies on the calling program
enforcing serialized access to all methods, and behavior is undefined if
concurrent access (for modification or otherwise) is allowed.

And like HashMap, PauselessHashMap is an implementation of Map. All optional
operations (adding and removing) are supported. Keys and values can be any objects.


-----------------------------

Here is some more background and commentary I included in my posting
on the mechanical sympathy group on the subject:
https://groups.google.com/forum/#!topic/mechanical-sympathy/DY8vysxdmj4

Pauseless HashMap

Some background: As those of you who have read my various rants may have
noticed, I spend a lot of my time thinking about the behavior of
latency/response-time/reaction-time. In addition to trying to understand
and teach about the behavior better (with monitoring and measurement tools
like HdrHistogram, LatencyUtils, and jHiccup), I actually work on things
that try to improve bad behavior (for some definitions of "improve" and
"bad"). Eliminating pausing behavior in GC was the lowest hanging fruit,
but more recent work has focused on eliminating pauses due to things like
at-market-open deoptimzations, or lock deflation, or de-basing, or all
sorts of TTSP (time to safepoint) issues. I've also learned a lot about
how to bring down Linux's contribution to latency spikes.

But the JVM and the OS are not the only things that cause latency spikes.
Sometimes your code is just "spiky". In my day job, I keep running into in
actual, real-world low latency system code that is typically super-fast,
but occasionally spikes in actual work latency due to some rare but huge
piece of work that needs to be done, most often due to some state
accumulation. After we eliminate GC pauses (which tend to dominate latency
spikes, and which simply disappear immediately when Zing is applied), we
often see this nice pattern of growing latency spikes at growing intervals,
with a near-perfect doubling in both magnitude and interval between the
spikes. This happens so often that we've studied the common causes, and
(by far) the most common culprits are HashMaps used to accumulate something
during the trading day. And it's all about resizing work.

I've had "build a Pauseless HashMap" on my weekend project list for over a
year now, but finally got around to actually building it (at the request of
someone on this list). There are probably at least 17 ways to skin a HashMap
so it won't stall puts and gets when it resizes, but this is my simple take
on it:

https://github.com/giltene/PauselessHashMap

Keep in mind that this is a "probably-working draft" that's gone through
some bench testing, but is not yet battle hardened (scrutiny is welcome).

I intentionally based this version on Apache Harmony's version of HashMap,
and not on OpenJDK's, in order to make it available without GPLv2 license
restrictions (for those who may want to include it in non-GPL products).
The background resizing concept itself is simple, and can be applied just
as easily to the OpenJDK version (e.g. if some future Java SE version
wants to use it). You can use 
(https://svn.apache.org/repos/asf/harmony/enhanced/java/trunk/classlib/modules/luni/src/main/java/java/util/HashMap.java)
as a baseline comparison for the code I started with.

This is also a classic example of how GC makes this sort of concurrent
programming thing both fast and simple. This is a classic case of an
asymmetric speed need between two concurrent actors that share mutable
state. I worked hard to make the fast path get() and put() cheap, and
managed (I think) to not even use volatiles in the fast path. In doing
this, I shifted all the cost I could think of to the background work,
where latency doesn't matter nearly as much. This sort of trick would
be much harder (or slower) to do if GC wasn't there to safely pick up
the junk behind us, as it would (invariably, I think) add a need for
additional synchronizing work in the fast path.