mirror of
https://github.com/greg7mdp/parallel-hashmap.git
synced 2026-08-29 16:40:39 +08:00
update "Parallel Hashmap" writeup
This commit is contained in:
Vendored
+34
-38
@@ -107,7 +107,7 @@
|
||||
</div>
|
||||
<h1 id="the-parallel-hashmap">The Parallel Hashmap</h1>
|
||||
<p>or Abseiling from the shoulders of giants - © Gregory Popovitch - March 10, 2019</p>
|
||||
<p>[tl;dr] We present a novel hashmap design, the Parallel Hashmap. Built on top of Abseil's <em>flat_hash_map</em>, the Parallel Hashmap has lower space requirements, is nearly as fast as the underlying <em>flat_hash_map</em>, and can be used from multiple threads with high levels of concurrency.</p>
|
||||
<p>[tl;dr] We present a novel hashmap design, the Parallel Hashmap. Built on a modified version of Abseil's <em>flat_hash_map</em>, the Parallel Hashmap has lower space requirements, is nearly as fast as the underlying <em>flat_hash_map</em>, and can be used from multiple threads with high levels of concurrency. The <a href="https://github.com/greg7mdp/parallel-hashmap">parallel hashmap</a> repository provides header-only version of the flat and node hashmaps, and their parallel versions as well.</p>
|
||||
<h3 id="a-quick-look-at-the-current-state-of-the-art">A quick look at the current state of the art</h3>
|
||||
<p>If you haven't been living under a rock, you know that Google open sourced late last year their Abseil library, which includes a very efficient flat hash table implementation. The <em>absl::flat_hash_map</em> stores the values directly in a memory array, which avoids memory indirections (this is referred to as closed hashing).</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/closed_hashing.png?raw=true" alt="closed_hashing" /></p>
|
||||
@@ -137,16 +137,14 @@
|
||||
</blockquote>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/index_computation.png?raw=true" alt="index_computation" /></p>
|
||||
<p>The benefit of this approach would be that the internal tables would each resize on its own when they reach 87.5% capacity, and since each table contains approximately one sixteenth of the values, the memory usage peak would be only one sixteenth of the size we saw for the single <em>flat_hash_map</em>.</p>
|
||||
<p>The rest of this article describes my implementation of this concept that I have done inside the Abseil library (I have submitted a pull request in the hope it will be merged into the main Abseil codebase). The current name for it is <em>parallel_flat_hash_map</em> or <em>parallel_flat_hash_set</em>. It does provide the same external API as Abseil's other hash tables, and internally it uses a std::array of N <em>flat_hash_maps</em>.</p>
|
||||
<p>I was delighted to find out that not only the <em>parallel_flat_hash_map</em> has significant memory usage benefits compared to the <em>flat_hash_map</em>, but it also has significant advantages for concurrent programming as I will show later.</p>
|
||||
<blockquote>
|
||||
<p>I will use the names <em>parallel_hash_map</em> and <em>parallel_flat_hash_map</em> interchangably. They refer to the same data structure. The name used in my Abseil fork is <em>absl::parallel_flat_hash_map</em>, as it may be desirable to also provide a <em>absl::parallel_node_hash_map</em>.</p>
|
||||
</blockquote>
|
||||
<p>The rest of this article describes my implementation of this concept that I have done in my <a href="https://github.com/greg7mdp/parallel-hashmap">parallel hashmap</a> repository. This is a header only library, which provides the following eight hashmaps: - phmap::flat_hash_set - phmap::flat_hash_map - phmap::node_hash_set - phmap::node_hash_map - phmap::parallel_flat_hash_set - phmap::parallel_flat_hash_map - phmap::parallel_node_hash_set - phmap::parallel_node_hash_map</p>
|
||||
<p>This implementation requires a C++11 compatible compiler, and provides full compatibility with the std::unordered_map (with the exception of <em>pointer stability</em> for the <code>flat</code> versions. C++14 and C++17 methods, like <code>try-emplace</code>, are provided as well. The names for it are <em>parallel_flat_hash_map</em> or <em>parallel_flat_hash_set</em>, and the <em>node</em> equivalents. These hashmaps provide the same external API as the <em>flat_hash_map</em>, and internally use a std::array of 2**N <em>flat_hash_maps</em>.</p>
|
||||
<p>I was delighted to find out that not only the <em>parallel_flat_hash_map</em> has significant memory usage benefits compared to the <em>flat_hash_map</em>, but it also has significant advantages for concurrent programming as I will show later. In the rest of this article, we will focus on the <em>parallel_flat_hash_map</em>, but similar results are seen for the <em>parallel_node_hash_map</em>, and the <em>set</em> versions of course.</p>
|
||||
<h3 id="the-parallel-hashmap-memory-usage">The Parallel Hashmap: memory usage</h3>
|
||||
<p>So, without further ado, let's see the same graphs graphs as above, with the addition of the <em>parallel_flat_hash_map</em>. Let us first look at memory usage (the second graph provides a "zoomed-in" view of the location where resizing occurs):</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/stl_flat_par_mem.PNG?raw=true" alt="stl_flat_par comparison" /></p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/stl_flat_par_mem_zoomed.PNG?raw=true" alt="stl_flat_par_zoomed comparison" /></p>
|
||||
<p>We see that the <em>parallel_hash_map</em> behaves as expected. The memory usage matches exactly the memory usage of its base <em>flat_hash_map</em>, except that the peaks of memory usage which occur when the table resizes are drastically reduced, to the point that they are not objectionable anymore. In the "zoomed-in" view, we can see the sixteen dots corresponding to each of the individual submaps resizing. The fact that those resizes are occuring at roughly the same x location in the graph shows that we have a good hash function distribution, distributing the values evenly between the sixteen individual submaps.</p>
|
||||
<p>We see that the <em>parallel_flat_hash_map</em> behaves as expected. The memory usage matches exactly the memory usage of its base <em>flat_hash_map</em>, except that the peaks of memory usage which occur when the table resizes are drastically reduced, to the point that they are not objectionable anymore. In the "zoomed-in" view, we can see the sixteen dots corresponding to each of the individual submaps resizing. The fact that those resizes are occuring at roughly the same x location in the graph shows that we have a good hash function distribution, distributing the values evenly between the sixteen individual submaps.</p>
|
||||
<h3 id="the-parallel-hashmap-speed">The Parallel Hashmap: speed</h3>
|
||||
<p>But what about the speed? After all, for each value inserted into the parallel hashmap, we have to do some extra work (steps 1 and 2 below):</p>
|
||||
<ol>
|
||||
@@ -156,17 +154,17 @@
|
||||
</ol>
|
||||
<p>The first step (compute the hash) is the most problematic one, as it can potentially be costly. As we mentioned above, the second step (computing the index from the hash) is very simple and its cost in minimal (3 processor instruction as shown below in <em>Matt Godbolt</em>'s compiler explorer):</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/idx_computation_cost.PNG?raw=true" alt="index computation cost" /></p>
|
||||
<p>As for the hash value computation, fortunately we can eliminate this cost by providing the computed hash to the submap functions, so that it is computed only once. This is exactly what I have done in my implementation of the <em>parallel_hash_map</em> within the Abseil library, adding a few extra APIs to the Abseil internal raw_hash_map.h header, which allow the <em>parallel_hash_map</em> to pass the precomputed hash value to the underlying submaps.</p>
|
||||
<p>So we have all but eliminated the cost of the first step, and seen that the cost of the second step is very minimal. At this point we expect that the <em>parallel_hash_map</em> performance will be close to the one of its underlying <em>flat_hash_map</em>, and this is confirmed by the chart below:</p>
|
||||
<p>As for the hash value computation, fortunately we can eliminate this cost by providing the computed hash to the submap functions, so that it is computed only once. This is exactly what I have done in my implementation of the <em>parallel_flat_hash_map</em>, adding a few extra APIs to the internal raw_hash_map.h header, which allow the <em>parallel_flat_hash_map</em> to pass the precomputed hash value to the underlying submaps.</p>
|
||||
<p>So we have all but eliminated the cost of the first step, and seen that the cost of the second step is very minimal. At this point we expect that the <em>parallel_flat_hash_map</em> performance will be close to the one of its underlying <em>flat_hash_map</em>, and this is confirmed by the chart below:</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/stl_flat_par_speed.PNG?raw=true" alt="stl_flat_par comparison" /></p>
|
||||
<p>Indeed, because of the scale is somewhat compressed due to the longer times of the std::unordered_map, we can barely distinguish between the blue curve of the <em>flat_hash_map</em> and the red curve of the <em>parallel_hash_map</em>. So let's look at a graph without the std::unordered_map:</p>
|
||||
<p>Indeed, because of the scale is somewhat compressed due to the longer times of the std::unordered_map, we can barely distinguish between the blue curve of the <em>flat_hash_map</em> and the red curve of the <em>parallel_flat_hash_map</em>. So let's look at a graph without the std::unordered_map:</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/flat_par_speed.PNG?raw=true" alt="flat_par comparison" /></p>
|
||||
<p>This last graph shows that the <em>parallel_hash_map</em> is slightly slower especially for smaller table sizes. For a reason not obvious to me (maybe better memory locality), the speeds of the <em>parallel_hash_map</em> and <em>flat_hash_map</em> are essentially undistinguishable for larger map sizes (> 80 million values).</p>
|
||||
<p>This last graph shows that the <em>parallel_flat_hash_map</em> is slightly slower especially for smaller table sizes. For a reason not obvious to me (maybe better memory locality), the speeds of the <em>parallel_flat_hash_map</em> and <em>flat_hash_map</em> are essentially undistinguishable for larger map sizes (> 80 million values).</p>
|
||||
<h3 id="are-we-done-yet">Are we done yet?</h3>
|
||||
<p>This is already looking pretty good. For large hash_maps, the <em>parallel_flat_hash_map</em> is a very appealing solution, as it provides essentially the excellent performance of the <em>flat_hash_map</em>, while virtually eliminating the peaks of memory usage which occur when the hash table resizes.</p>
|
||||
<p>But there is another aspect of the inherent parallelism of the <em>parallel_hash_map</em> which is interesting to explore. As we know, typical hashmaps cannot be modified from multiple threads without explicit synchronization. And bracketing write accesses to a shared hash_map with synchronization primitives, such as mutexes, can reduce the concurrency of our program, and even cause deadlocks.</p>
|
||||
<p>Because the <em>parallel_hash_map</em> is made of sixteen separate submaps, it posesses some intrinsic parallelism. Indeed, suppose you can make sure that different threads will use different submaps, you would be able to insert into the same <em>parallel_hash_map</em> at the same time from the different threads without any locking.</p>
|
||||
<h3 id="using-the-intrinsic-parallelism-of-the-parallel_hash_map-to-insert-values-from-multiple-threads-lock-free">Using the intrinsic parallelism of the <em>parallel_hash_map</em> to insert values from multiple threads, lock free.</h3>
|
||||
<p>But there is another aspect of the inherent parallelism of the <em>parallel_flat_hash_map</em> which is interesting to explore. As we know, typical hashmaps cannot be modified from multiple threads without explicit synchronization. And bracketing write accesses to a shared hash_map with synchronization primitives, such as mutexes, can reduce the concurrency of our program, and even cause deadlocks.</p>
|
||||
<p>Because the <em>parallel_flat_hash_map</em> is made of sixteen separate submaps, it posesses some intrinsic parallelism. Indeed, suppose you can make sure that different threads will use different submaps, you would be able to insert into the same <em>parallel_flat_hash_map</em> at the same time from the different threads without any locking.</p>
|
||||
<h3 id="using-the-intrinsic-parallelism-of-the-parallel_flat_hash_map-to-insert-values-from-multiple-threads-lock-free">Using the intrinsic parallelism of the <em>parallel_flat_hash_map</em> to insert values from multiple threads, lock free.</h3>
|
||||
<p>So, if you can iterate over the values you want to insert into the hash table, the idea is that each thread will iterate over all values, and then for each value:</p>
|
||||
<ol>
|
||||
<li>compute the hash for that value</li>
|
||||
@@ -225,13 +223,13 @@
|
||||
<p>Using multiple threads, we are able to populate the <em>parallel_flat_hash_map</em> (inserting 100 million values) three times faster than the standard <em>flat_hash_map</em> (which we could not have populated from multiple threads without explicit locks, which would have prevented performance improvements).</p>
|
||||
<p>And the graphical visualization of the results:</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/mt_stl_flat_par_both_run2.PNG?raw=true" alt="mt_stl_flat_par comparison" /></p>
|
||||
<p>We notice in this last graph that the memory usage peaks, while still smaller than those of the <em>flat_hash_map</em>, are larger that those we saw when populating the <em>parallel_hash_map</em> using a single thread. The obvious reason is that, when using a single thread, only one of the submaps would resize at a time, ensuring that the peak would only be 1/16th of the one for the <em>flat_hash_map</em> (provided of course that the hash function distributes the values somewhat evenly between the submaps).</p>
|
||||
<p>When running in multi-threaded mode (in this case eight threads), potentially as many as eight submaps can resize simultaneaously, so for a <em>parallel_hash_map</em> with sixteen submaps the memory peak size can be half as large as the one for the <em>flat_hash_map</em>.</p>
|
||||
<p>Still, this is a pretty good result, we are now inserting values into our <em>parallel_hash_map</em> three times faster than we were able to do using the <em>flat_hash_map</em>, while using a lower memory ceiling.</p>
|
||||
<p>We notice in this last graph that the memory usage peaks, while still smaller than those of the <em>flat_hash_map</em>, are larger that those we saw when populating the <em>parallel_flat_hash_map</em> using a single thread. The obvious reason is that, when using a single thread, only one of the submaps would resize at a time, ensuring that the peak would only be 1/16th of the one for the <em>flat_hash_map</em> (provided of course that the hash function distributes the values somewhat evenly between the submaps).</p>
|
||||
<p>When running in multi-threaded mode (in this case eight threads), potentially as many as eight submaps can resize simultaneaously, so for a <em>parallel_flat_hash_map</em> with sixteen submaps the memory peak size can be half as large as the one for the <em>flat_hash_map</em>.</p>
|
||||
<p>Still, this is a pretty good result, we are now inserting values into our <em>parallel_flat_hash_map</em> three times faster than we were able to do using the <em>flat_hash_map</em>, while using a lower memory ceiling.</p>
|
||||
<p>This is significant, as the speed of insertion into a hash map is important in many algorithms, for example removing duplicates in a collection of values.</p>
|
||||
<h3 id="using-the-intrinsic-parallelism-of-the-parallel_hash_map-with-internal-mutexes">Using the intrinsic parallelism of the <em>parallel_hash_map</em> with internal mutexes</h3>
|
||||
<p>It may not be practical to add logic into your program to ensure you use different internal submaps from each thread. Still, locking the whole <em>parallel_hash_map</em> for each access would forego taking advantage of its intrinsic parallelism.</p>
|
||||
<p>For that reason, the <em>parallel_hash_map</em> can provide internal locking using the <code>absl::Mutex</code> (the default template parameter is <code>absl::NullMutex</code>, which does no locking and has no size cost). When selecting <code>absl::Mutex</code>, one mutex is created for each internal submap at a cost of 8 bytes per submap, and the <em>parallel_hash_map</em> internally protects each submap access with its associated mutex.</p>
|
||||
<h3 id="using-the-intrinsic-parallelism-of-the-parallel_flat_hash_map-with-internal-mutexes">Using the intrinsic parallelism of the <em>parallel_flat_hash_map</em> with internal mutexes</h3>
|
||||
<p>It may not be practical to add logic into your program to ensure you use different internal submaps from each thread. Still, locking the whole <em>parallel_flat_hash_map</em> for each access would forego taking advantage of its intrinsic parallelism.</p>
|
||||
<p>For that reason, the <em>parallel_flat_hash_map</em> can provide internal locking using the <code>std::mutex</code> (the default template parameter is <code>phmap::NullMutex</code>, which does no locking and has no size cost). When selecting <code>std::mutex</code>, one mutex is created for each internal submap at a cost of 8 bytes per submap, and the <em>parallel_flat_hash_map</em> internally protects each submap access with its associated mutex.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
@@ -247,17 +245,17 @@
|
||||
<td style="text-align: right;">64</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td style="text-align: left;">absl::flat_hash_map</td>
|
||||
<td style="text-align: left;">phmap::flat_hash_map</td>
|
||||
<td style="text-align: center;">-</td>
|
||||
<td style="text-align: right;">48</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td style="text-align: left;">absl::parallel_flat_hash_map, N=4, absl::NullMutex</td>
|
||||
<td style="text-align: left;">phmap::parallel_flat_hash_map, N=4, phmap::NullMutex</td>
|
||||
<td style="text-align: center;">16</td>
|
||||
<td style="text-align: right;">768</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td style="text-align: left;">absl::parallel_flat_hash_map, N=4, absl::Mutex</td>
|
||||
<td style="text-align: left;">phmap::parallel_flat_hash_map, N=4, phmap::Mutex</td>
|
||||
<td style="text-align: center;">16</td>
|
||||
<td style="text-align: right;">896</td>
|
||||
</tr>
|
||||
@@ -265,17 +263,17 @@
|
||||
</table>
|
||||
<p>It is about time we provide the complete parallel_flat_hash_map class declaration (the declaration for parallel_flat_hash_set is similar):</p>
|
||||
<pre><code>template <class K, class V,
|
||||
class Hash = absl::container_internal::hash_default_hash<K>,
|
||||
class Eq = absl::container_internal::hash_default_eq<K>,
|
||||
class Allocator = std::allocator<std::pair<const K, V>>,
|
||||
class Hash = phmap::container_internal::hash_default_hash<K>,
|
||||
class Eq = phmap::container_internal::hash_default_eq<K>,
|
||||
class Allocator = phmap::container_internal::Allocator<std::pair<const K, V>>, // alias for std::allocator
|
||||
size_t N = 4, // 2**N submaps
|
||||
class Mutex = absl::NullMutex> // use absl::Mutex to enable internal locks
|
||||
class Mutex = phmap::NullMutex> // use std::mutex to enable internal locks
|
||||
class parallel_flat_hash_map;
|
||||
</code></pre>
|
||||
<p>Let's see what result we get for the insertion of random values from multiple threads, however this time we create a <em>parallel_hash_map</em> with internal locking (by providing absl::Mutex as the last template argument), and modify the code so that each thread inserts values in any submap (no pre-selection).</p>
|
||||
<p>Let's see what result we get for the insertion of random values from multiple threads, however this time we create a <em>parallel_flat_hash_map</em> with internal locking (by providing std::mutex as the last template argument), and modify the code so that each thread inserts values in any submap (no pre-selection).</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/no_preselection.PNG?raw=true" alt="no_preselection" /></p>
|
||||
<p>If we were to do a intensive insertion test into a hash map from multiple threads, where we lock the whole hash table for each insertion, we would be likely to get even worse results than for a single threaded insert, because of heavy lock contention.</p>
|
||||
<p>In this case, our expectation is that the finer grained locking of the <em>parallel_hash_map</em> (separate locks for each internal submap) will provide a speed benefit when compared to the single threaded insertion, and this is indeed what the benchmarks show:</p>
|
||||
<p>In this case, our expectation is that the finer grained locking of the <em>parallel_flat_hash_map</em> (separate locks for each internal submap) will provide a speed benefit when compared to the single threaded insertion, and this is indeed what the benchmarks show:</p>
|
||||
<p><img src="https://github.com/greg7mdp/parallel-hashmap/blob/master/html/img/flat_par_mutex_4.PNG?raw=true" alt="flat_par_mutex_4" /></p>
|
||||
<p>Interestingly, we notice that the memory peaks (when resizing occur) are again very small, in the order of 1/16th of those for the <em>flat_hash_map</em>. This is likely because, as soon as one of the submaps resizes (which takes much longer than a regular insertion), the other threads very soon have to wait on the resizing submap's mutex for an insertion, before they reach their own resizing threashold.</p>
|
||||
<p>Since threads statistically will insert on a different submap for each value, it would be a surprising coincidence indeed if two submaps reached their resizing threshold without the resizing of the first submap blocking all the other threads first.</p>
|
||||
@@ -293,25 +291,25 @@ class parallel_flat_hash_map;
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td style="text-align: left;">absl::flat_hash_map</td>
|
||||
<td style="text-align: left;">phmap::flat_hash_map</td>
|
||||
<td style="text-align: center;">-</td>
|
||||
<td style="text-align: right;">48</td>
|
||||
<td style="text-align: right;">14.77s</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td style="text-align: left;">absl::parallel_flat_hash_map, N=4, absl::Mutex</td>
|
||||
<td style="text-align: left;">phmap::parallel_flat_hash_map, N=4, std::mutex</td>
|
||||
<td style="text-align: center;">16</td>
|
||||
<td style="text-align: right;">896</td>
|
||||
<td style="text-align: right;">8.36s</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td style="text-align: left;">absl::parallel_flat_hash_map, N=5, absl::Mutex</td>
|
||||
<td style="text-align: left;">phmap::parallel_flat_hash_map, N=5, std::mutex</td>
|
||||
<td style="text-align: center;">32</td>
|
||||
<td style="text-align: right;">1792</td>
|
||||
<td style="text-align: right;">7.14s</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td style="text-align: left;">absl::parallel_flat_hash_map, N=6, absl::Mutex</td>
|
||||
<td style="text-align: left;">phmap::parallel_flat_hash_map, N=6, std::mutex</td>
|
||||
<td style="text-align: center;">64</td>
|
||||
<td style="text-align: right;">3584</td>
|
||||
<td style="text-align: right;">6.61s</td>
|
||||
@@ -323,15 +321,13 @@ class parallel_flat_hash_map;
|
||||
<p>We have seen that the novel parallel hashmap approach, used within a single thread, provides significant space advantages, with a very minimal time penalty. When used in a multi-thread context, the parallel hashmap still provides a significant space benefit, in addition to a consequential time benefit by reducing (or even eliminating) lock contention when accessing the parallel hashmap.</p>
|
||||
<h3 id="future-work">Future work</h3>
|
||||
<ol>
|
||||
<li><p>It would be beneficial to provide additional APIs for the <em>parallel_flat_hash_map</em> and <em>parallel_flat_hash_set</em> taking a precomputed hash value. This would enable the lock-free usage of the <em>parallel_flat_hash_map</em>, described above for multi-threaded environments, without requiring a double hash computation.</p></li>
|
||||
<li><p>We may consider providing <em>parallel_node_hash_map</em> and <em>parallel_node_hash_set</em> in Abseil, for the cases when pointer stability is required for keys and/or values. This would be a simple addition.</p></li>
|
||||
<li>It would be beneficial to provide additional APIs for the <em>parallel_flat_hash_map</em> and <em>parallel_flat_hash_set</em> taking a precomputed hash value. This would enable the lock-free usage of the <em>parallel_flat_hash_map</em>, described above for multi-threaded environments, without requiring a double hash computation.</li>
|
||||
</ol>
|
||||
<h3 id="thanks">Thanks</h3>
|
||||
<p>I would like to thank Google's <em>Matt Kulukundis</em> for his eye-opening presentation of the <em>flat_hash_map</em> design at CPPCON 2017 - my frustration with not being able to use it helped trigger my insight into the <em>parallel_hash_map</em>. Also many thanks to the Abseil container developers - I believe the main contributors are <em>Alkis Evlogimenos</em> and <em>Roman Perepelitsa</em> - who created an excellent codebase into which the graft of this new hashmap took easily, and finally to Google for open-sourcing Abseil. Thanks also to my son <em>Andre</em> for reviewing this paper, and for his patience when I was rambling about the <em>parallel_hash_map</em> and its benefits.</p>
|
||||
<p>I would like to thank Google's <em>Matt Kulukundis</em> for his eye-opening presentation of the <em>flat_hash_map</em> design at CPPCON 2017 - my frustration with not being able to use it helped trigger my insight into the <em>parallel_flat_hash_map</em>. Also many thanks to the Abseil container developers - I believe the main contributors are <em>Alkis Evlogimenos</em> and <em>Roman Perepelitsa</em> - who created an excellent codebase into which the graft of this new hashmap took easily, and finally to Google for open-sourcing Abseil. Thanks also to my son <em>Andre</em> for reviewing this paper, and for his patience when I was rambling about the <em>parallel_flat_hash_map</em> and its benefits.</p>
|
||||
<h3 id="links">Links</h3>
|
||||
<p><a href="https://github.com/greg7mdp/parallel-hashmap">Github repository for the benchmark code used in this paper</a></p>
|
||||
<p><a href="https://github.com/greg7mdp/parallel-hashmap">Repository for the Parallel Hashmap, including the benchmark code used in this paper</a></p>
|
||||
<p><a href="https://abseil.io/blog/20180927-swisstables">Swiss Tables doc</a></p>
|
||||
<p><a href="https://github.com/greg7mdp/abseil-cpp">My fork of Google Abseil repository, with the parallel_flat_hash_map implementation</a></p>
|
||||
<p><a href="https://github.com/abseil/abseil-cpp">Google Abseil repository</a></p>
|
||||
<p><a href="https://www.youtube.com/watch?v=ncHmEUmJZf4">Matt Kulukindis: Designing a Fast, Efficient, Cache-friendly Hash Table, Step by Step</a></p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user