Parallel Hashmap

repository·master·Indexed 25 days ago

https://github.com/greg7mdp/parallel-hashmap

A high-performance, header-only C++11 library providing efficient hash map and btree implementations. Designed as a drop-in replacement for STL containers like std::unordered_map and std::map, it offers various types including flat, node, and parallel versions (e.g., phmap::flat_hash_map, phmap::parallel_flat_hash_map) to balance speed, memory efficiency, and pointer stability. It supports fine-grained locking for concurrent access and provides cache-friendly btree alternatives to standard red-black trees.

Tokens
3.9K
Snippets
8
Records
17
Agent score
85%

What's inside parallel-hashmap

  1. Available Hashmap Types in Parallel Hashmap

    master

    The parallel-hashmap library is a header-only C++11 library that provides several hashmap implementations. It includes both flat (closed hashing) and node (open addressing/pointer stability) versions, as well as their parallel counterparts designed for high concurrency and reduced memory peaks during resizing.

    Standard (Single-threaded) Maps:

    • phmap::flat_hash_set
    • phmap::flat_hash_map
    • phmap::node_hash_set
    • phmap::node_hash_map

    Parallel (Multi-threaded) Maps:

    • phmap::parallel_flat_hash_set
    • phmap::parallel_flat_hash_map
    • phmap::parallel_node_hash_set
    • phmap::parallel_node_hash_map
  2. Build tests and examples with CMake

    master

    A CMakeLists.txt is provided to build the library's tests and examples. Use the following commands to build and run them:

    cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON -B build
    cmake --build build
    ctest --test-dir build
    cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON -B build
    
    cmake --build build
    
    ctest --test-dir build
  3. Install Parallel Hashmap

    master

    Parallel Hashmap is a header-only library. To install it, copy the parallel_hashmap directory into your project and update your include path.

    If you are using Visual Studio, you can add phmap.natvis to your project to enable clear visualization of hash table contents in the debugger.

  4. Run benchmarks on Windows using Cygwin

    master

    To run the benchmarks on Windows, use a Cygwin window with the VC++ 2017 compiler environment variables loaded. You can automate this by adding the following command to your Cygwin.bat file:

    CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
    CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
  5. Provide a hash function for user-defined classes

    master

    To use flat_hash_set or flat_hash_map with custom types, you must provide a hash function. You can do this in three ways:

    1. Hash Functor: Provide a hash functor via the HashFcn template parameter.
    2. hash_value() Friend Function: Add a friend size_t hash_value(const T &obj) function to your class. Use phmap::HashState().combine(...) to combine member variables.
    3. std::hash Specialization: Inject a specialization of std::hash for your class into the std namespace. This is often done using the phmap_utils.h header to simplify combining hash values with phmap::HashState().
    #include <parallel_hashmap/phmap_utils.h>
    #include <string>
    using std::string;
    
    struct Person
    {
        bool operator==(const Person &o) const
        {
            return _first == o._first && _last == o._last && _age == o._age;
        }
    
        friend size_t hash_value(const Person &p)
        {
            return phmap::HashState().combine(0, p._first, p._last, p._age);
        }
    
        string _first;
        string _last;
        int    _age;
    };
  6. Use `parallel_flat_hash_map` with internal fine-grained locking

    master
    If you cannot manually partition work among threads, you can use std::mutex as the Mutex template parameter. This enables internal fine-grained locking where each submap has its own mutex. This reduces lock contention compared to a single global mutex, as threads accessing different submaps can proceed in parallel.
  7. Configure Parallel Hashmap behavior via macros

    master

    You can modify the default behavior of the library by defining preprocessor macros before including phmap.h:

    • PHMAP_USE_ABSL_HASH: Use the Abseil hash framework instead of std::hash (requires including Abseil headers first).
    • PHMAP_NON_DETERMINISTIC 1: Enables hash seed randomization to prevent Denial of Service attacks (makes iteration order non-deterministic).
    • PHMAP_DISABLE_MIX 1: Disables internal hash value mixing. This is not recommended as it can lead to performance degradation with poor hash functions.
  8. Use flat_hash_map in C++

    master

    Include parallel_hashmap/phmap.h to use the hash map implementations. phmap::flat_hash_map is a high-performance, memory-efficient replacement for std::unordered_map that stores keys and values directly in a memory array.

    #include <iostream>
    #include <string>
    #include <parallel_hashmap/phmap.h>
    
    using phmap::flat_hash_map;
    
    int main()
    {
        // Create an unordered_map of three strings (that map to strings)
        flat_hash_map<std::string, std::string> email =
        {
            { "tom",  "tom@gmail.com"},
            { "jeff", "jk@gmail.com"},
            { "jim", "jimg@microsoft.com"}
        };
    
        // Iterate and print keys and values
        for (const auto& n : email)
            std::cout << n.first << "'s email is: " << n.second << "\n";
    
        // Add a new entry
        email["bill"] = "bg@whatever.com";
    
        // and print it
        std::cout << "bill's email is: " << email["bill"] << "\n";
    
        return 0;
    }
  9. Use `parallel_flat_hash_map` with lock-free multi-threaded insertion

    master

    You can achieve high-performance, lock-free concurrent insertion by ensuring each thread only operates on a specific subset of the internal submaps. This is done by computing the hash of the key, determining its target submap index, and having the thread only proceed if that index belongs to its assigned range.

    To implement this, use the subcnt() method to get the number of submaps and subidx(hashval) to determine the submap index for a given hash.

    template <class HT>
    void _fill_random_inner_mt(int64_t cnt, HT &hash, RSU &rsu)
    {
        constexpr int64_t num_threads = 8;   // has to be a power of two
        std::unique_ptr<std::thread> threads[num_threads];
    
        auto thread_fn = [&hash, cnt, num_threads](int64_t thread_idx, RSU rsu) {
            size_t modulo = hash.subcnt() / num_threads;        // subcnt() returns the number of submaps
    
            for (int64_t i=0; i<cnt; ++i)
            {
                unsigned int key = rsu.next();                  // get next key to insert
                size_t hashval = hash.hash(key);                // compute its hash
                size_t idx  = hash.subidx(hashval);             // compute the submap index for this hash
                if (idx / modulo == thread_idx)                 // if the submap is suitable for this thread
                {
                    hash.insert(typename HT::value_type(key, 0)); // insert the value
                    ++(num_keys[thread_idx]);                     // increment count of inserted values
                }
            }
        };
    
        for (int64_t i=0; i<num_threads; ++i)
            threads[i].reset(new std::thread(thread_fn, i, rsu));
    
        for (int64_t i=0; i<cnt; ++i)
            rsu.next();
        
        for (int64_t i=0; i<cnt; ++i)
            threads[i]->join();
    }
  10. Inject `std::hash` specialization for custom types

    master

    You can specialize std::hash for a custom class to make it compatible with phmap containers. Use phmap::HashState().combine() within the specialization to aggregate the hashes of the class members.

    // file "Person.h"
    #include <parallel_hashmap/phmap_utils.h>
    #include <string>
    using std::string;
    
    struct Person
    {
        bool operator==(const Person &o) const
        {
            return _first == o._first && _last == o._last && _age == o._age;
        }
    
        string _first;
        string _last;
        int    _age;
    };
    
    namespace std
    {
        template<> struct hash<Person>
        {
            std::size_t operator()(Person const &p) const
            {
                return phmap::HashState().combine(0, p._first, p._last, p._age);
            }
        };
    }
    
    // file "main.cpp"
    #include "Person.h"
    #include <iostream>
    #include <parallel_hashmap/phmap.h>
    
    int main()
    {
        phmap::flat_hash_set<Person> persons = 
            { { "John", "Mitchell", 35 },
              { "Jane", "Smith",    32 },
              { "Jane", "Smith",    30 },
            };
    
        for (auto& p: persons)
            std::cout << p._first << ' ' << p._last << " (" << p._age << ")" << '\n';
    }