GithubHelp home page GithubHelp logo

greg7mdp / parallel-hashmap Goto Github PK

View Code? Open in Web Editor NEW
2.3K 60.0 219.0 3.07 MB

A family of header-only, very fast and memory-friendly hashmap and btree containers.

Home Page: https://greg7mdp.github.io/parallel-hashmap/

License: Apache License 2.0

C++ 100.00%
hash tables unordered-map unordered-set hash-container parallel multi-thread memory-footprint parallel-programming concurrency

parallel-hashmap's Introduction

The Parallel Hashmap

License: Apache-2.0 Linux MacOS Windows

Overview

This repository aims to provide a set of excellent hash map implementations, as well as a btree alternative to std::map and std::set, with the following characteristics:

  • Header only: nothing to build, just copy the parallel_hashmap directory to your project and you are good to go.

  • drop-in replacement for std::unordered_map, std::unordered_set, std::map and std::set

  • Compiler with C++11 support required, C++14 and C++17 APIs are provided (such as try_emplace)

  • Very efficient, significantly faster than your compiler's unordered map/set or Boost's, or than sparsepp

  • Memory friendly: low memory usage, although a little higher than sparsepp

  • Supports heterogeneous lookup

  • Easy to forward declare: just include phmap_fwd_decl.h in your header files to forward declare Parallel Hashmap containers [note: this does not work currently for hash maps with pointer keys]

  • Dump/load feature: when a flat hash map stores data that is std::trivially_copyable, the table can be dumped to disk and restored as a single array, very efficiently, and without requiring any hash computation. This is typically about 10 times faster than doing element-wise serialization to disk, but it will use 10% to 60% extra disk space. See examples/serialize.cc. (flat hash map/set only)

  • Tested on Windows (vs2015 & vs2017, vs2019, vs2022, Intel compiler 18 and 19), linux (g++ 4.8, 5, 6, 7, 8, 9, 10, 11, 12, clang++ 3.9 to 16) and MacOS (g++ and clang++) - click on travis and appveyor icons above for detailed test status.

  • Automatic support for boost's hash_value() method for providing the hash function (see examples/hash_value.h). Also default hash support for std::pair and std::tuple.

  • natvis visualization support in Visual Studio (hash map/set only)

@byronhe kindly provided this Chinese translation of the README.md.

Fast and memory friendly

Click here For a full writeup explaining the design and benefits of the Parallel Hashmap.

The hashmaps and btree provided here are built upon those open sourced by Google in the Abseil library. The hashmaps use closed hashing, where values are stored directly into a memory array, avoiding memory indirections. By using parallel SSE2 instructions, these hashmaps are able to look up items by checking 16 slots in parallel, allowing the implementation to remain fast even when the table is filled up to 87.5% capacity.

IMPORTANT: This repository borrows code from the abseil-cpp repository, with modifications, and may behave differently from the original. This repository is an independent work, with no guarantees implied or provided by the authors. Please visit abseil-cpp for the official Abseil libraries.

Installation

Copy the parallel_hashmap directory to your project. Update your include path. That's all.

If you are using Visual Studio, you probably want to add phmap.natvis to your projects. This will allow for a clear display of the hash table contents in the debugger.

A cmake configuration files (CMakeLists.txt) is provided for building the tests and examples. Command for building and running the tests is: mkdir build && cd build && cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON .. && cmake --build . && make test

Example

#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",  "[email protected]"},
        { "jeff", "[email protected]"},
        { "jim",  "[email protected]"}
    };
 
    // 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"] = "[email protected]";
 
    // and print it
    std::cout << "bill's email is: " << email["bill"] << "\n";
 
    return 0;
}

Various hash maps and their pros and cons

The header parallel_hashmap/phmap.h provides the implementation for the following eight hash tables:

  • 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

The header parallel_hashmap/btree.h provides the implementation for the following btree-based ordered containers:

  • phmap::btree_set
  • phmap::btree_map
  • phmap::btree_multiset
  • phmap::btree_multimap

The btree containers are direct ports from Abseil, and should behave exactly the same as the Abseil ones, modulo small differences (such as supporting std::string_view instead of absl::string_view, and being forward declarable).

When btrees are mutated, values stored within can be moved in memory. This means that pointers or iterators to values stored in btree containers can be invalidated when that btree is modified. This is a significant difference with std::map and std::set, as the std containers do offer a guarantee of pointer stability. The same is true for the 'flat' hash maps and sets.

The full types with template parameters can be found in the parallel_hashmap/phmap_fwd_decl.h header, which is useful for forward declaring the Parallel Hashmaps when necessary.

Key decision points for hash containers:

  • The flat hash maps will move the keys and values in memory. So if you keep a pointer to something inside a flat hash map, this pointer may become invalid when the map is mutated. The node hash maps don't, and should be used instead if this is a problem.

  • The flat hash maps will use less memory, and usually be faster than the node hash maps, so use them if you can. the exception is when the values inserted in the hash map are large (say more than 100 bytes [needs testing]) and costly to move.

  • The parallel hash maps are preferred when you have a few hash maps that will store a very large number of values. The non-parallel hash maps are preferred if you have a large number of hash maps, each storing a relatively small number of values.

  • The benefits of the parallel hash maps are:
    a. reduced peak memory usage (when resizing), and
    b. multithreading support (and inherent internal parallelism)

Key decision points for btree containers:

Btree containers are ordered containers, which can be used as alternatives to std::map and std::set. They store multiple values in each tree node, and are therefore more cache friendly and use significantly less memory.

Btree containers will usually be preferable to the default red-black trees of the STL, except when:

  • pointer stability or iterator stability is required
  • the value_type is large and expensive to move

When an ordering is not needed, a hash container is typically a better choice than a btree one.

Changes to Abseil's hashmaps

  • The default hash framework is std::hash, not absl::Hash. However, if you prefer the default to be the Abseil hash framework, include the Abseil headers before phmap.h and define the preprocessor macro PHMAP_USE_ABSL_HASH.

  • The erase(iterator) and erase(const_iterator) both return an iterator to the element following the removed element, as does the std::unordered_map. A non-standard void _erase(iterator) is provided in case the return value is not needed.

  • No new types, such as absl::string_view, are provided. All types with a std::hash<> implementation are supported by phmap tables (including std::string_view of course if your compiler provides it).

  • The Abseil hash tables internally randomize a hash seed, so that the table iteration order is non-deterministic. This can be useful to prevent Denial Of Service attacks when a hash table is used for a customer facing web service, but it can make debugging more difficult. The phmap hashmaps by default do not implement this randomization, but it can be enabled by adding #define PHMAP_NON_DETERMINISTIC 1 before including the header phmap.h (as is done in raw_hash_set_test.cc).

  • Unlike the Abseil hash maps, we do an internal mixing of the hash value provided. This prevents serious degradation of the hash table performance when the hash function provided by the user has poor entropy distribution. The cost in performance is very minimal, and this helps provide reliable performance even with imperfect hash functions.

Memory usage

type memory usage additional peak memory usage when resizing
flat tables flat_mem_usage flat_peak_usage
node tables node_mem_usage node_peak_usage
parallel flat tables flat_mem_usage parallel_flat_peak
parallel node tables node_mem_usage parallel_node_peak
  • size() is the number of values in the container, as returned by the size() method
  • load_factor() is the ratio: size() / bucket_count(). It varies between 0.4375 (just after the resize) to 0.875 (just before the resize). The size of the bucket array doubles at each resize.
  • the value 9 comes from sizeof(void *) + 1, as the node hash maps store one pointer plus one byte of metadata for each entry in the bucket array.
  • flat tables store the values, plus one byte of metadata per value), directly into the bucket array, hence the sizeof(C::value_type) + 1.
  • the additional peak memory usage (when resizing) corresponds the the old bucket array (half the size of the new one, hence the 0.5), which contains the values to be copied to the new bucket array, and which is freed when the values have been copied.
  • the parallel hashmaps, when created with a template parameter N=4, create 16 submaps. When the hash values are well distributed, and in single threaded mode, only one of these 16 submaps resizes at any given time, hence the factor 0.03 roughly equal to 0.5 / 16

Iterator invalidation for hash containers

The rules are the same as for std::unordered_map, and are valid for all the phmap hash containers:

Operations Invalidated
All read only operations, swap, std::swap Never
clear, rehash, reserve, operator= Always
insert, emplace, emplace_hint, operator[] Only if rehash triggered
erase Only to the element erased

Iterator invalidation for btree containers

Unlike for std::map and std::set, any mutating operation may invalidate existing iterators to btree containers.

Operations Invalidated
All read only operations, swap, std::swap Never
clear, operator= Always
insert, emplace, emplace_hint, operator[] Yes
erase Yes

Example 2 - providing a hash function for a user-defined class

In order to use a flat_hash_set or flat_hash_map, a hash function should be provided. This can be done with one of the following methods:

  • Provide a hash functor via the HashFcn template parameter

  • As with boost, you may add a hash_value() friend function in your class.

For example:

#include <parallel_hashmap/phmap_utils.h> // minimal header providing phmap::HashState()
#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;
};
  • Inject a specialization of std::hash for the class into the "std" namespace. We provide a convenient and small header phmap_utils.h which allows to easily add such specializations.

For example:

file "Person.h"

#include <parallel_hashmap/phmap_utils.h> // minimal header providing phmap::HashState()
#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
{
    // inject specialization of std::hash for Person into 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);
        }
    };
}

The std::hash specialization for Person combines the hash values for both first and last name and age, using the convenient phmap::HashState() function, and returns the combined hash value.

file "main.cpp"

#include "Person.h"   // defines Person  with std::hash specialization

#include <iostream>
#include <parallel_hashmap/phmap.h>

int main()
{
    // As we have defined a specialization of std::hash() for Person,
    // we can now create sparse_hash_set or sparse_hash_map of Persons
    // ----------------------------------------------------------------
    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';

}

Thread safety

Parallel Hashmap containers follow the thread safety rules of the Standard C++ library. In Particular:

  • A single phmap hash table is thread safe for reading from multiple threads. For example, given a hash table A, it is safe to read A from thread 1 and from thread 2 simultaneously.

  • If a single hash table is being written to by one thread, then all reads and writes to that hash table on the same or other threads must be protected. For example, given a hash table A, if thread 1 is writing to A, then thread 2 must be prevented from reading from or writing to A.

  • It is safe to read and write to one instance of a type even if another thread is reading or writing to a different instance of the same type. For example, given hash tables A and B of the same type, it is safe if A is being written in thread 1 and B is being read in thread 2.

  • The parallel tables can be made internally thread-safe for concurrent read and write access, by providing a synchronization type (for example std::mutex) as the last template argument. Because locking is performed at the submap level, a high level of concurrency can still be achieved. Read access can be done safely using if_contains(), which passes a reference value to the callback while holding the submap lock. Similarly, write access can be done safely using modify_if, try_emplace_l or lazy_emplace_l. However, please be aware that iterators or references returned by standard APIs are not protected by the mutex, so they cannot be used reliably on a hash map which can be changed by another thread.

  • Examples on how to use various mutex types, including boost::mutex, boost::shared_mutex and absl::Mutex can be found in examples/bench.cc

Using the Parallel Hashmap from languages other than C++

While C++ is the native language of the Parallel Hashmap, we welcome bindings making it available for other languages. One such implementation has been created for Python and is described below:

Acknowledgements

Many thanks to the Abseil developers for implementing the swiss table and btree data structures (see abseil-cpp) upon which this work is based, and to Google for releasing it as open-source.

parallel-hashmap's People

Contributors

barracuda156 avatar bpmckinnon avatar byronhe avatar cartoonist avatar cgaudreau-ubisoft avatar colingaudreau avatar decster avatar dirtysalt avatar dmadisetti avatar ecatmur avatar fcharlie avatar greg7mdp avatar gtarcoder avatar ilobilo avatar jendrikseipp avatar kc9jud avatar kkiningh avatar maikelvdh avatar myozka avatar squadrick avatar stdpain avatar x-neon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parallel-hashmap's Issues

Do you support c++11 stateful allocators?

Does parallel-hashmap ever make its own clean instance of my custom Allocator and allocates from that instead of a copy of the allocator I passed in the constructor?

phmap btree_map strange behavior

Hi @greg7mdp , thanks for this amazing chain of powerful projects.

I'd like to ask about the behavior of the phmap::btree_map and I have several inquiries.

Sorry if this seemed to be unprofessional documenting of the issue but I could not find a better way to describe, so I recorded a short video.

ezgif-6-17801bba0096

Or view it from the following URL.

As you will see, there's inconsistency in the output of compiling and running the same code.

The code used:

  1. main.cpp
#include <iostream>
#include <map>
#include <cstdint>
#include <parallel_hashmap/btree.h>

int main(){
    std::cout << "phmap btree Inserting uint64_t ..." << std::endl; 
    phmap::btree_map<uint64_t, uint64_t> btree_1;
    for(uint64_t i; i < 10000; i++) btree_1[i] = i;
    std::cout << "btree_1 size: " << btree_1.size() << std::endl << std::endl;

    std::cout << "phmap btree Inserting int ..." << std::endl; 
    phmap::btree_map<int, int> btree_2;
    for(int j; j < 10000; j++) btree_2[j] = j;
    std::cout << "btree_2 size: " << btree_2.size() << std::endl << std::endl;
}
  1. CmakeLists.txt
cmake_minimum_required(VERSION 3.4)
project(btree_map_issue)


set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -fPIC -fopenmp -W -Wall -pedantic -O3 -oFast")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall")


file(GLOB SOURCES 
"main.cpp"
)

include_directories(parallel-hashmap)
add_executable(run_issue ${SOURCES})

Thank you.

The default hash does not like using pointers as keys.

Happy code: phmap::flat_hash_map<void *, bool, std::hash<void *>> Foo; Foo[(void *)0x00] = true;
Sad code: phmap::flat_hash_map<void *, bool> Foo; Foo[(void *)0x00] = true;

Compiler log:

phmap.h(102): error C2440: 'static_cast': cannot convert from 'T *' to 'uintptr_t'
phmap.h(101): note: while compiling class template member function 'size_t phmap::Hash<const T *>::operator ()(T *) noexcept const'
phmap.h(4203): note: see reference to class template instantiation 'phmap::Hash<const T *>' being compiled
phmap.h(2418): note: see reference to function template instantiation 'std::pair<phmap::container_internal::raw_hash_set<Policy,Hash,Eq,Alloc>::iterator,bool> phmap::container_internal::raw_hash_map<Policy,Hash,Eq,Alloc>::try_emplace<_Ty,,0,0x0>(void *&&)' being compiled
          with
          [
              Policy=phmap::container_internal::FlatHashMapPolicy<void *,bool>,
              Hash=phmap::container_internal::HashEq<void *,void>::Hash,
              Eq=phmap::container_internal::HashEq<void *,void>::Eq,
              Alloc=std::allocator<std::pair<void *const ,bool>>,
              _Ty=void *
          ]

About default parameters

If I wanted to create a parallel_flat_hash_map with the last parameter being std::mutex, what should i put for the Hash, Eq, Alloc and N, what are the defaults.

template <class K, class V, class Hash, class Eq, class Alloc, size_t N, class Mtx_>
class parallel_flat_hash_map ......;

I tried to look into the codes to figure it out myself, but seems it's beyond my level :P

node hash map and parallel node hash map Load and Dump function error

I've wrote a test case and find that node_hash_map when load from a dump, the first key is 0, and all other key is incorrect, If I has missed some thing? the test code is bellow

TEST(PHMAP, TEST) {
  {
    phmap::node_hash_map<uint64_t, uint32_t> mp1 = {{100, 99}, {300, 299}};
    for (const auto &n : mp1)
      std::cout << n.first << "'s value is: " << n.second << "\n";

    {
      phmap::BinaryOutputArchive ar_out("./dump.data");
      mp1.dump(ar_out);
    }
  }

  phmap::node_hash_map<uint64_t, uint32_t> mp2;
  {
    phmap::BinaryInputArchive ar_in("./dump.data");
    mp2.load(ar_in);
  }

  for (const auto& n : mp2)
    std::cout << n.first << "'s value is: " << n.second << "\n";
}

the output is as follows:

300's value is: 299
100's value is: 99
0's value is: 299
93934918124288's value is: 99

address sanitizer messages

Hi,

I'm using the parallel-hashmap for text tokenisation at https://github.com/bnosac/tokenizers.bpe
It is an R package. When compiling the package with Address Sanitizer (Debian Linux, R-devel, GCC ASAN/UBSAN)
I'm getting the following address sanitizer messages from parallel-hashmap. Is it possible to fix these? And how? Thanks!

./parallel_hashmap/phmap.h: In function โ€˜__m128i phmap::container_internal::_mm_cmpgt_epi8_fixed(__m128i, __m128i)โ€™:
./parallel_hashmap/phmap.h:294:44: warning: overflow in implicit constant conversion [-Woverflow]
     const __m128i mask = _mm_set1_epi8(0x80);
                                            ^
./parallel_hashmap/phmap_bits.h: In function โ€˜uint64_t umul128(uint64_t, uint64_t, uint64_t*)โ€™:
./parallel_hashmap/phmap_bits.h:342:44: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                            ^~~~~~~~
./parallel_hashmap/phmap_bits.h:342:80: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                                                                ^~~~~~~~

Full trace log below

Trace log

* installing to library โ€˜/home/docker/Rโ€™
* installing *source* package โ€˜tokenizers.bpeโ€™ ...
** libs
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG -pthread -DSTRICT_R_HEADERS -I./youtokentome/cpp -I. -I"/home/docker/R/Rcpp/include" -I/usr/local/include   -fpic  -g -O2 -Wall -pedantic -mtune=native -c youtokentome/cpp/utils.cpp -o youtokentome/cpp/utils.o
In file included from ./parallel_hashmap/phmap_utils.h:26:0,
                 from ./parallel_hashmap/phmap.h:51,
                 from youtokentome/cpp/utils.h:6,
                 from youtokentome/cpp/utils.cpp:2:
./parallel_hashmap/phmap_bits.h: In function โ€˜uint64_t umul128(uint64_t, uint64_t, uint64_t*)โ€™:
./parallel_hashmap/phmap_bits.h:342:44: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                            ^~~~~~~~
./parallel_hashmap/phmap_bits.h:342:80: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                                                                ^~~~~~~~
In file included from youtokentome/cpp/utils.h:6:0,
                 from youtokentome/cpp/utils.cpp:2:
./parallel_hashmap/phmap.h: In function โ€˜__m128i phmap::container_internal::_mm_cmpgt_epi8_fixed(__m128i, __m128i)โ€™:
./parallel_hashmap/phmap.h:294:44: warning: overflow in implicit constant conversion [-Woverflow]
     const __m128i mask = _mm_set1_epi8(0x80);
                                            ^
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG -pthread -DSTRICT_R_HEADERS -I./youtokentome/cpp -I. -I"/home/docker/R/Rcpp/include" -I/usr/local/include   -fpic  -g -O2 -Wall -pedantic -mtune=native -c youtokentome/cpp/bpe.cpp -o youtokentome/cpp/bpe.o
In file included from ./parallel_hashmap/phmap_utils.h:26:0,
                 from ./parallel_hashmap/phmap.h:51,
                 from youtokentome/cpp/bpe.h:6,
                 from youtokentome/cpp/bpe.cpp:4:
./parallel_hashmap/phmap_bits.h: In function โ€˜uint64_t umul128(uint64_t, uint64_t, uint64_t*)โ€™:
./parallel_hashmap/phmap_bits.h:342:44: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                            ^~~~~~~~
./parallel_hashmap/phmap_bits.h:342:80: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                                                                ^~~~~~~~
In file included from youtokentome/cpp/bpe.h:6:0,
                 from youtokentome/cpp/bpe.cpp:4:
./parallel_hashmap/phmap.h: In function โ€˜__m128i phmap::container_internal::_mm_cmpgt_epi8_fixed(__m128i, __m128i)โ€™:
./parallel_hashmap/phmap.h:294:44: warning: overflow in implicit constant conversion [-Woverflow]
     const __m128i mask = _mm_set1_epi8(0x80);
                                            ^
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG -pthread -DSTRICT_R_HEADERS -I./youtokentome/cpp -I. -I"/home/docker/R/Rcpp/include" -I/usr/local/include   -fpic  -g -O2 -Wall -pedantic -mtune=native -c youtokentome/cpp/utf8.cpp -o youtokentome/cpp/utf8.o
In file included from ./parallel_hashmap/phmap_utils.h:26:0,
                 from ./parallel_hashmap/phmap.h:51,
                 from youtokentome/cpp/utils.h:6,
                 from youtokentome/cpp/utf8.h:3,
                 from youtokentome/cpp/utf8.cpp:2:
./parallel_hashmap/phmap_bits.h: In function โ€˜uint64_t umul128(uint64_t, uint64_t, uint64_t*)โ€™:
./parallel_hashmap/phmap_bits.h:342:44: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                            ^~~~~~~~
./parallel_hashmap/phmap_bits.h:342:80: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                                                                ^~~~~~~~
In file included from youtokentome/cpp/utils.h:6:0,
                 from youtokentome/cpp/utf8.h:3,
                 from youtokentome/cpp/utf8.cpp:2:
./parallel_hashmap/phmap.h: In function โ€˜__m128i phmap::container_internal::_mm_cmpgt_epi8_fixed(__m128i, __m128i)โ€™:
./parallel_hashmap/phmap.h:294:44: warning: overflow in implicit constant conversion [-Woverflow]
     const __m128i mask = _mm_set1_epi8(0x80);
                                            ^
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG -pthread -DSTRICT_R_HEADERS -I./youtokentome/cpp -I. -I"/home/docker/R/Rcpp/include" -I/usr/local/include   -fpic  -g -O2 -Wall -pedantic -mtune=native -c rcpp_youtokentome.cpp -o rcpp_youtokentome.o
In file included from ./parallel_hashmap/phmap_utils.h:26:0,
                 from ./parallel_hashmap/phmap.h:51,
                 from ./youtokentome/cpp/bpe.h:6,
                 from rcpp_youtokentome.cpp:3:
./parallel_hashmap/phmap_bits.h: In function โ€˜uint64_t umul128(uint64_t, uint64_t, uint64_t*)โ€™:
./parallel_hashmap/phmap_bits.h:342:44: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                            ^~~~~~~~
./parallel_hashmap/phmap_bits.h:342:80: warning: ISO C++ does not support โ€˜__int128โ€™ for โ€˜type nameโ€™ [-Wpedantic]
         auto result = static_cast<unsigned __int128>(a) * static_cast<unsigned __int128>(b);
                                                                                ^~~~~~~~
In file included from ./youtokentome/cpp/bpe.h:6:0,
                 from rcpp_youtokentome.cpp:3:
./parallel_hashmap/phmap.h: In function โ€˜__m128i phmap::container_internal::_mm_cmpgt_epi8_fixed(__m128i, __m128i)โ€™:
./parallel_hashmap/phmap.h:294:44: warning: overflow in implicit constant conversion [-Woverflow]
     const __m128i mask = _mm_set1_epi8(0x80);
                                            ^
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG -pthread -DSTRICT_R_HEADERS -I./youtokentome/cpp -I. -I"/home/docker/R/Rcpp/include" -I/usr/local/include   -fpic  -g -O2 -Wall -pedantic -mtune=native -c RcppExports.cpp -o RcppExports.o
g++ -fsanitize=undefined,bounds-strict -fno-omit-frame-pointer -std=gnu++11 -shared -L/usr/local/lib/R/lib -L/usr/local/lib -o tokenizers.bpe.so youtokentome/cpp/utils.o youtokentome/cpp/bpe.o youtokentome/cpp/utf8.o rcpp_youtokentome.o RcppExports.o -pthread -L/usr/local/lib/R/lib -lR
rm -f youtokentome/cpp/utils.o youtokentome/cpp/bpe.o youtokentome/cpp/utf8.o rcpp_youtokentome.o RcppExports.o
installing to /home/docker/R/tokenizers.bpe/libs
** R
** data
*** moving datasets to lazyload DB
** inst
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded
* DONE (tokenizers.bpe)

SSSE3 checks

There are two places protected with a check of PHMAP_HAVE_SSSE3, both in phmap.h. The protected intrinsics, _mm_movemask_epi8 and _mm_or_si128, are both SSE2 intrinsics.

It seems you could just remove those checks, the corresponding the #else case code, and simplify phmap_config.h to only test for (and require) SSE2.

Migration from Abseil

I'm not exactly sure how this is meant to be used with Abseil. I set -DPHMAP_USE_ABSL_HASH, -DPHMAP_USE_ABSL_HASHEQ, include parallel_hashmap/phmap_fwd_decl.h, and then am I to include absl/hash/hash.h, finally phmap?

Playing around with it, but I'm getting cryptic name collisions, and thought I would ask about intended usage oppose to fighting with it more. I'm migrating from vanilla abseil to phmap for stronger concurrency guarantees in parallel_*. I want to retain my hashing from hashing with abseil. My classes have the CRTP AbslHashValue hook defined, and I was hoping for plug and play.

A provided example would be ๐ŸŽ‰

Ability to manipulate a hash_map value behind the write lock

I've encounter a case where I would like to modify the value inside the hash map in a thread-safe way. I would like to propose a function similar to if_contains that I have so far named if_modify. The difference between the two functions is if_modify is not const, and it locks the unique_lock rather than the shared_lock. Otherwise they use the same function body.

slow than unordered_map

I run the examples/emplace.cc and i found the phmap::flat_hash_map slower than unordered_map, is that something wrong?

here is the output

bench: iterations: 100000 / container_size: 1
std::unordered_map: 178 ms
std::map: 120 ms
std::vectorstd::pair: 118 ms
phmap::flat_hash_map: 541 ms
bench: iterations: 100000 / container_size: 10
std::unordered_map: 1300 ms
std::map: 1461 ms
std::vectorstd::pair: 802 ms
phmap::flat_hash_map: 3574 ms
bench: iterations: 100000 / container_size: 50
std::unordered_map: 8307 ms
std::map: 9969 ms
std::vectorstd::pair: 5532 ms
phmap::flat_hash_map: 19734 ms

my g++ version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609

Implementing heterogeneous lookup for custom types

I have the following two types:

class StringView final {
   public:
	template <size_t sz>
	constexpr StringView(const char (&_text)[sz]) noexcept
		: _data(_text), _length(sz) {
		static_assert(sz > 0, "String must not be empty");
	}
	constexpr StringView(const char* __restrict _text, size_t len) noexcept
		: _data(_text), _length(len) {}
	StringView(const StringView v) noexcept
		: _data(v._data), _length(v._length) {}
	StringView(const String& str) noexcept;
	StringView() = delete;

	constexpr const char* data() const noexcept { return _data; }
	constexpr size_t length() const noexcept { return _length; }

	bool operator==(const StringView v) const noexcept;

   private:
	const char* _data;
	const size_t _length;
};
struct String final {
	String() noexcept;
	String(sds str) noexcept;
	String(size_t len) noexcept;
	String(const StringView v) noexcept;
	String(const char* __restrict str) noexcept;
	String(const char* __restrict str, size_t len) noexcept;

	~String() noexcept;
	String(const String& other) noexcept;
	String(String&& other) noexcept;
	void operator=(const String& str) noexcept;
	void operator=(String&& str) noexcept;

	operator sds() const noexcept { return s; }

	bool operator==(const String& str) const noexcept;
	bool operator==(const char* __restrict str) const noexcept;
	bool operator==(const StringView v) const noexcept;

	size_t length() const noexcept;

	sds s;
};

I would like to implement heterogeneous lookup between them.

I've done the following:

struct StringHash {
	using is_transparent = void;

	size_t operator()(const StringView v) const;
};

struct StringEq {
	using is_transparent = void;

	bool operator()(const StringView lhs, const StringView rhs) const;
};

And I've then created the following map:

phmap::node_hash_map<String, String, StringHash, StringEq>

However, it appears emplacing values into it triggers an assert.

where[String(key.data(), key.length())] =
			String(value.as_cstring(), value.get_string_length())

assert(PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) ==
iterator_at(i) &&
"constructed value does not match the lookup key");

How should I go about doing this? Are there any examples available anywhere?

Document more clearly a simple example of how to use it safely in a multi-threaded scenario

When using parallel-hashmap and telling it a mutex type to use internally for thread-safety .... you say it's not safe to use iterators while another thread may be writing ....

Obviously iterating with those iterators I would expect to not be thread-safe.

But presumably it is thread-safe to, without any extra external locking, do a .find() - which returns precisely such an iterator - and then retrieve the value from that iterator ....?

Otherwise it's not at all clear how the class could be at all used thread-safely, e.g. how does simply one retrieve a value from the map (in such a case, generally using the [] operator is less desirable that using find(), as we don't want to potentially be writing a new entry to the map as a side-effect of our get, and creating extra contention in the process etc).

Note - I did look at the examples and tests, but trying to wade through all the macros is a ton harder and less informative than if one could just read some simple example code along with an explanation of exactly what is and isn't safe to do.

std::shared_mutex support

Hello!

Is std::shared_mutex support for C++14 and higher planned? I know I can provide LockableImpl specialization for it, but what about native support?

absl::EqualTo does not exist

Not sure if it existed back when this was first added, but currently there is no absl::EqualTo, so compilation breaks when PHMAP_USE_ABSL_HASHEQ is defined. Workaround is to add this before including phmap.h:

namespace absl {
  template <typename T>
  struct EqualTo {
    bool operator()(const T& a, const T& b) const {
      return a == b;
    }
  };
}

At a glance, couldn't phmap just always use its own EqualTo? At least for me, I'm solely using PHMAP_USE_ABSL_HASHEQ for the hashing.

exported as a C lib?

Since it's a header only lib, can it be easily exported as a lib and used by C code?
thanks!

Does not compile on MSVSC 2017 - Intrinsics Error

Compiler Error:

c:\users\jerem\documents\umich\research\iros\polylidarv2\polylidar\parallel_hashmap\phmap_bits.h(265): error C3861: '_BitScanForward64': identifier not found
c:\users\jerem\documents\umich\research\iros\polylidarv2\polylidar\parallel_hashmap\phmap_bits.h(298): error C3861: '_BitScanForward': identififier not found

These errors occured on Line 265 of phmap_bits.h. Googling the error informed me that we need to have a #include <intrin.h> before this line number. However this #include does not come until Line 327:

To fix this issue I deleted this include statement and put it below Line 162, here:

#define PHMAP_BASE_INTERNAL_FORCEINLINE __forceinline

After this change it worked just fine for me. I think the only issue is that there should be a guard to check for X64 architecture. I tried the #include other places but additional errors kept appearing.

Hope this helps.

Is it feasible to make iterators bidirectional

In some applications it would be desirable to have bi-directional iteration over the hash table (even though the standards specifies that iterators are only forward iterators). Would it be possible to add such functionality to node_ and flat_ hash tables?

What's mean "move the keys and values in memory" ?

The flat hash maps may move the keys and values in memory. So if you keep a pointer to something inside a flat hash map, this pointer may become invalid when the map is mutated. The node hash maps don't, and should be used instead if this is a problem.

What's mean "move the keys and values in memory" ?
Why "pointer may become invalid"๏ผŸI can't understand.

Because used memcpy() and didn't use the copy constructor?

coredump

benchHashMap n = 4086946, keyType = int64_t, valueType = RankItem(128 Byet)

#0 0x00007f171cfdf428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0 0x00007f171cfdf428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f171cfe102a in __GI_abort () at abort.c:89
#2 0x00007f171d92284d in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007f171d9206b6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007f171d920701 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007f171d920919 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007f171d920ebc in operator new(unsigned long) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7 0x000000000040de84 in __gnu_cxx::new_allocator<void* phmap::container_internal::Allocate<8ul, std::allocator<std::pair<long const, RankItem> > >(std::allocator<std::pair<long const, RankItem> >, unsigned long)::M>::allocate(unsigned long, void const) (this=, __n=) at /usr/include/c++/5/ext/new_allocator.h:104
#8 phmap::allocator_traits<std::allocator<void* phmap::container_internal::Allocate<8ul, std::allocator<std::pair<long const, RankItem> > >(std::allocator<std::pair<long const, RankItem> >, unsigned long)::M> >::allocate(std::allocator<void phmap::container_internal::Allocate<8ul, std::allocator<std::pair<long const, RankItem> > >(std::allocator<std::pair<long const, RankItem> >*, unsigned long)::M>&, unsigned long) (a=, n=) at ./phmap/phmap_base.h:1579
#9 phmap::container_internal::Allocate<8ul, std::allocator<std::pair<long const, RankItem> > > (alloc=0x7ffd8ac798e8, n=) at ./phmap/phmap.h:696
#10 phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::initialize_slots (this=this@entry=0x7ffd8ac798c0)
at ./phmap/phmap.h:1810
#11 0x00000000004274da in phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::resize (this=0x7ffd8ac798c0,
new_capacity=) at ./phmap/phmap.h:1842
#12 0x000000000042f5ec in phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::rehash_and_grow_if_necessary (
this=0x7ffd8ac798c0) at ./phmap/phmap.h:1934
#13 phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::prepare_insert (this=0x7ffd8ac798c0,
hash=11022116168902158974) at ./phmap/phmap.h:2025
#14 0x000000000042f6cd in phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::find_or_prepare_insert (
this=this@entry=0x7ffd8ac798c0, key=@0x7ffd8ac798b8: -187985434075425886, hash=) at ./phmap/phmap.h:2013
#15 0x0000000000430477 in phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::find_or_prepare_insert (
key=@0x7ffd8ac798b8: -187985434075425886, this=0x7ffd8ac798c0) at ./phmap/phmap.h:2018
#16 phmap::container_internal::raw_hash_map<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::try_emplace_impl<long const&> (
k=@0x7ffd8ac798b8: -187985434075425886, this=0x7ffd8ac798c0) at ./phmap/phmap.h:2293
#17 phmap::container_internal::raw_hash_map<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::try_emplace<long, , 0>(long const&) (
k=@0x7ffd8ac798b8: -187985434075425886, this=0x7ffd8ac798c0) at ./phmap/phmap.h:2241
#18 phmap::container_internal::raw_hash_map<phmap::container_internal::FlatHashMapPolicy<long, RankItem>, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > >::operator[]<long, phmap::container_internal::FlatHashMapPolicy<long, RankItem> > (key=@0x7ffd8ac798b8: -187985434075425886, this=0x7ffd8ac798c0) at ./phmap/phmap.h:2277
#19 insert_high_load<phmap::flat_hash_map<long, RankItem, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > > > (ahash=..., hash_name="phmap", vList=std::vector of length 4086946, capacity 4086946 = {...})
at ebench.cpp:535
#20 0x0000000000430b3b in benOneHash<phmap::flat_hash_map<long, RankItem, std::hash, phmap::EqualTo, std::allocator<std::pair<long const, RankItem> > > > (tmp=..., hash_name="phmap",
oList=std::vector of length 4086946, capacity 4086946 = {...}) at ebench.cpp:933
#21 0x00000000004064e4 in benchHashMap (n=4086946) at ebench.cpp:1041
#22 0x000000000040230a in main (argc=, argv=) at ebench.cpp:1312

Issue with constexpr

When compiling latest clone with g++-8 (Ubuntu/Linaro 8.3.0-26ubuntu1~18.04) 8.3.0 on Linux minit 4.9.140-tegra #1 SMP PREEMPT Wed Mar 13 00:32:22 PDT 2019 aarch64 aarch64 aarch64 GNU/Linux with the following switch -O3 -std=c++1z

In file included from XXX
../include/parallel_hashmap/phmap.h: In function โ€˜size_t phmap::container_internal::CapacityToGrowth(size_t)โ€™:
/home/minit/nano-tools/include/parallel_hashmap/phmap_config.h:634:58: error: โ€˜capacityโ€™ is not a constant expression
     #define PHMAP_IF_CONSTEXPR(expr) if constexpr ((expr))
                                                          
../include/parallel_hashmap/phmap.h:499:5: note: in expansion of macro โ€˜PHMAP_IF_CONSTEXPRโ€™
     PHMAP_IF_CONSTEXPR (Group::kWidth == 8 && capacity == 7) {
     ^~~~~~~~~~~~~~~~~~
../include/parallel_hashmap/phmap.h: In function โ€˜size_t phmap::container_internal::GrowthToLowerboundCapacity(size_t)โ€™:
/home/minit/nano-tools/include/parallel_hashmap/phmap_config.h:634:58: error: โ€˜growthโ€™ is not a constant expression
     #define PHMAP_IF_CONSTEXPR(expr) if constexpr ((expr))
                                                          ^
../include/parallel_hashmap/phmap.h:513:5: note: in expansion of macro โ€˜PHMAP_IF_CONSTEXPRโ€™
     PHMAP_IF_CONSTEXPR (Group::kWidth == 8 && growth == 7) {

reserve and capacity not matching

Hello,
I encountered an issue with reserve and capacity.
I am using parallel flat hash map.

When calling reserve(X), The following call to capacity() does not seem to return X or even anything close, I could see why it would be different because of the way the hashmap is implemented, but the difference seems pretty big and as a result a call to capacity() seem useless and dangerous.

My use case was iterating a bunch of images to collect statistics and color info.
For each image I was doing a map.reserve(std::max(map.capacity(), image.size()) to make sure the map's capacity was at least the current image's number of pixels.
The program ended up trying to allocate several GBs of memory and ended up crashing pretty fast. Without the reserve line, memory usage peaks at like 350 MB.

Is this supposed to be this way ?

`has_hash_value` does not work as expected

The Visual Studio 2017/2019 STL exports the template function

template <class _Kty>
inline size_t hash_value(const _Kty& _Keyval) { // hash _Keyval to size_t value one-to-one
  return (size_t) _Keyval ^ (size_t) 0xdeadbeef;
}

into the std namespace. This causes has_hash_value to always return true, seeing that the function is well-defined for any type as long as we don't have to actually run it.

This causes an unfortunate interaction with the StringHash class, which is instantiated unconditionally as soon as you include phmap.h and C++17 is being used:

template <class T>
struct Hash
{
    template <class U, typename std::enable_if<has_hash_value<U>::value, int>::type = 0>
    size_t _hash(const T& val) const
    {
        return hash_value(val);
    }
 
    template <class U, typename std::enable_if<!has_hash_value<U>::value, int>::type = 0>
    size_t _hash(const T& val) const
    {
        return std::hash<T>()(val);
    }
 
    inline size_t operator()(const T& val) const
    {
        return _hash<T>(val);
    }
};
// ...
struct StringHash 
{
    using is_transparent = void;

    size_t operator()(std::string_view v) const {
        return phmap::Hash<std::string_view>{}(v);
    }
};

This will try to instantiate the above std::hash_value on std::string_view and fail miserably. One workaround is to define hash_value(std::string_view) somewhere.

intel compiler warning

Intel C++ compiler issues warning:

warning #2196: routine is both "inline" and "noinline"

for methods marked as PHMAP_ATTRIBUTE_NOINLINE. I think this is known issue with icpc, that could be explicitly addressed by adding intel specific pragma.

Python Bindings for Parallel Hashmap

Hi all,

I have created a python package that binds parallel-hashmap with numpy integration. The package is not finished, but it has complete, vectorized POD object support such as ints, floats, and bools. There is limited support for strings. I plan to precompile some simple tuples and arrays in the future. Extending new datatypes should be fairly straight forward.

I was able to achieve approximately comparable performance to the c++ implementation without any optimization of the bindings yet. (~30 seconds and 2100MB memory for 100M 8B:8B insertions).

The link to the repo is here: https://github.com/atom-moyer/sparsepy.git

Would we be interested in advertising that package on this page?

This is my first open source contribution, so I am quite excited to help as many people as I can with their hash map needs. My engineering research relies heavily on prototyping protocols which require high performance hash maps. Therefore, I have a personal interest in developing the fastest, smallest, and easiest hash maps possible.

Dumping and Loading Nested Maps/Sets

Hey Greg,

I would like to make an unordered unique multimap phmap::flat_hash_map<Key, phmap::flat_hash_set<Value>> where Key and Value are trivially copyable. As one would expect when I try to dump the outer map using phmap::BinaryOutputArchive ar_out( filename.c_str() ); dict.dump( ar_out ); I get an error that the inner set is not trivially copyable.

What would you suggest for serializing?

Also, side question: Is this how you would make a multimap?

Thanks,
Adam

btree_set fails to insert what should be a unique object

#include "parallel_hashmap/btree.h"

struct Test
	{
	int a;
	int b;
	};

class Comparer {
public:
	bool operator()(const Test& l, const Test& r) const noexcept
		{
		return l.a < r.a;
		}
	};

using HashSet = phmap::btree_set<Test, Comparer>;

int main(int argc, char** argv)
	{
	Test a { 1, 1 };
	Test b { 2, 1 };
	Test c { 2, 2 };

	HashSet h;
	auto it = h.insert(a);
	if ( ! it.second )
		printf("failed to insert a\n");

	auto it2 = h.insert(b);
	if ( ! it2.second )
		printf("failed to insert b\n");

	auto it3 = h.insert(c);
	if ( ! it3.second )
		printf("failed to insert c\n");
	}

I would expect that to insert all three elements, but for some reason c fails to insert what should be a unique object.

Recommendation for deserializing a large hashmap?

Thank you Greg for this awesome contribution, I am finding it quite useful for my application.

I plan to generate a very large hashmap with bitsets as the key and integers as the value. The size of the hashmap will be 10GB when serialized to binary. It might also be important to note that the hashmap will not be modified after generation; purely used for look-up purposes.

I went ahead and serialized a 1GB flat hashmap using cereal and that took about 10 minutes, while it took about 20 minutes to deserialize.

From what I could gather, hashmaps are slow to deserialize due to the fact they need to repopulate the entire map again from scratch (or so I've been told).

To mitigate the slow deserialization process I was thinking to split the hashmap up and by some means parallelize the process of deserialization. Or maybe pre-allocating the memory necessary for the hashmap will speed things up. I am not really sure.

I look forward to receiving your response.

"Parallel" suggests of parallel computations

'Tis but a nitpick.

When I see the name "Parallel Hash Map" I immediately think of a hash map for parallel (concurrent) computations.

So the name choice is pretty confusing in this regard.

AddressSanitizer issue on multihreaded insert

Hello @greg7mdp !

I'm using a parallel_flat_hash_map<uint64_t, uint32_t> to store information about some groups, much like a SQL GROUP BY. Map key is a XXH3 cumulative hash of the grouped columns and value is the position of that group.

I submit to 16 worker threads a list of group keys to insert, following example code from your documentation:

using map_type = phmap::parallel_flat_hash_map<uint64_t, uint32_t>;

map_type map;

tv::parallel::for_each(
    map.subcnt(),
    [&](auto& ctx) {
        using hasher_type = typename map_type::hasher;

        hasher_type hasher;

        // gkeys is a std::vector<uint64_t> containing group keys
        for (auto gkey : gkeys) {
            auto map_key = hasher(gkey);
            auto idx = map.subidx(map_key);

            // ctx.local_index is the worker index: from 0 to map.subcnt() - 1
            if (idx == ctx.local_index) {
                auto gpos = current_group_count;

                // This line triggers AddressSanitizer or segfault
                auto res = map.insert(map_entry(gkey, gpos));

                if (res.second) {
                    // New element inserted
                    ++current_group_count;
                } else {
                    // Grab existing group position
                    gpos = res.first->second;
                }

                // Update size of group
                ++group_sizes[gpos];
            }
        }
    }
);

When compiled with AddressSanitizer under GCC 8.3.0, I get the attached trace. When compiled without AddressSanitizer, it segfaults at the same location. I've banged my head around (though it starts to hurt a bit now) checking and re-checking to no-avail.

Could you please help me ?

log.txt

assertion reached

The following code:

    template <typename Container, typename T>
    inline void update_hash_table__(Container& S, const T& t) {
        auto [it, res] = S.insert(t);

        if (res == false) {
            auto x = S.extract(it);
            x.value().merge(t);
            S.insert(std::move(x));
        }
    } // update_hash_table__

with Container=phmap::node_hash_set fails with the following assertion:

parallel_hashmap/phmap.h:2010: void phmap::container_internal::raw_hash_set<Policy, Hash, Eq, Alloc>::emplace_at(size_t, Args&& ...) [with Args = {const qap_task&}; Policy = phmap::container_internal::NodeHashSetPolicy<qap_task>; Hash = phmap::Hash<qap_task>; Eq = phmap::EqualTo<qap_task>; Alloc = std::allocator<qap_task>; size_t = long unsigned int]: Assertion `PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) == iterator_at(i) && "constructed value does not match the lookup key"' failed.

T is a custom type which supplies std::hash<T> and provides custom operator==. The method merge does not change how the updated object hashes or tests for equality. Considering this, why the assertion would be triggered?

Edit: it seems that the failing test case triggers assertion on the first insert in the code snippet.

gdb pretty printer for phmap

Can anyone of kindness has implemented a gdb pretty printer for phmap's types? Please offer one here, thanks a lot!!

std::result_of is deprecated in C++17

phmap_base.h mentions std::result_of here:

template <typename T>
using result_of_t = typename std::result_of<T>::type;

In VS 2019 16.5, due to compiler improvements for the [[deprecated]] attribute, this line will emit "warning C4996: 'std::result_of<_Fty>': warning STL4014: std::result_of and std::result_of_t are deprecated in C++17. They are superseded by std::invoke_result and std::invoke_result_t. You can define _SILENCE_CXX17_RESULT_OF_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning."

It appears that nothing in parallel-hashmap uses result_of_t. Therefore, I believe that the best fix would be to delete this line entirely.

Unreferenced parameter warning

This one in minor. In phmap_base.h, when PHMAP_HAVE_EXCEPTIONS is 0 (false), the implementation of Throw does not reference the parameter 'error' and may generate a warning.

hash function

Hello,

There are 2 ways to give an object a hash function.

However, I am working with memory addresses and I wish I could keep them the same. I tried, specializating the template<> Hash for size_t to return the identity (which I saw that it is already being done like in phmap_utils.h), however printing the hash value in the insert or find is totally different. That might be because of the control bits, if I am not mistaken.

Is there a way I could just pass the hash function as the identity function, so that when I try to store a memory address as the key, it will no longer be hashed ?

All the best!

Bug with allocator?

Hey. I try implement parallel hashmap to this solution IrrlichtBaw
I change

std::unordered_map

to your

phmap::flat_hash_map

Code where you can look at this

and i have error with allocator

Severity Code Description Project File Line Suppression State
Error C2338 You've instantiated std::aligned_storage<Len, Align> with an extended alignment (in other words, Align > alignof(max_align_t)). Before VS 2017 15.8, the member "type" would non-conformingly have an alignment of only alignof(max_align_t). VS 2017 15.8 was fixed to handle this correctly, but the fix inherently changes layout and breaks binary compatibility (only for uses of aligned_storage with extended alignments). Please define either (1) _ENABLE_EXTENDED_ALIGNED_STORAGE to acknowledge that you understand this message and that you actually want a type with an extended alignment, or (2) _DISABLE_EXTENDED_ALIGNED_STORAGE to silence this message and get the old non-conforming behavior. IrrlichtServer C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.24.28314\include\type_traits 1061

Its a bug ?

My Output

max_load_factor() does not work properly.

When I run following code:

phmap::parallel_flat_hash_map<std::string, int> my_map;
my_map.max_load_factor(0.5);
my_map.reserve(15);
std::cout << my_map.max_load_factor() << std::endl;
std::cout << my_map.bucket_count() << std::endl;

I got the result:

1
16

But it is supposed to be:

0.5
32

[Question] Can I assume time-complexity of O(1) for read / write?

Hello,

I couldn't find any info about the theoretical time complexity.

I'd like to check with an author that this package has an O(1) time complexity for accessing an element / inserting an element.

This is what most people assume when you use the word 'hashmap', but maybe it's not implemented this way, but as long as it remains approx. O(1) in small n, it's "O(1) enough" for me.

Thank you.

Explore using highbits from the hash for subtable selection

Hey, just wanted to say that I really like the work you are doing here. We have found that SwissTables performance can suffer dramatically when there is not enough entropy in the hash function. By using a hash of (h ^ (h >> 4)) & 0x0F to select your subtables, you are creating just such a reduction of entropy. Given that tables rarely grow large enough to use all their high bits, you can probably get better performance by doing something like h >> 56. At the very least it is worth exploring.

Building with Intel Compiler

C++ isn't my strong suit so I haven't quite figured out what this issue is yet.
But, when I try to build the example in MSVS 2017 and 2019 with Windows SDK 10.0.17763.0, using ICC 2019 update 3 (all are debug builds using default settings), I get these errors:

Severity Code Description Project File Line Suppression State
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4144
Error (active) E1389 redeclaration cannot add dllexport/dllimport to "ldexpf" (declared at line 706 of "C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt\corecrt_math.h") hash C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2019\windows\compiler\include\math.h 704
Error (active) E1389 redeclaration cannot add dllexport/dllimport to "powf" (declared at line 743 of "C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt\corecrt_math.h") hash C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2019\windows\compiler\include\math.h 799
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 557
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 573
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 604
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 2166
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 3304
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4203
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4262
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4321
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4372
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4419
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4470
Error #3466 inheriting constructors must be inherited from a direct base class hash d:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap.h 4520

Works fine with Clang and MSVC using c++14 standard though.

Under c++17 with Clang I get:

Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 557
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 573
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 604
Error cannot convert 'const std::basic_string_view<char, std::char_traits >' to 'size_t' (aka 'unsigned int') without a conversion operator hash C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include\xhash 31

And with MSVC

Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 557
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 573
Error (active) E2512 the argument to a feature-test macro must be a simple identifier hash D:\Downloads\parallel-hashmap-master\parallel_hashmap\phmap_config.h 604

Though you might want to know.

Cheers

Intermittent segfault in parallel_flat_hash_map::find

I observe intermittent segmentation fault when I use parallel_flat_hash_map in a multithreaded application. The fault happens in raw_hash_set::EqualElement::operator(). I guess references that EqualElement stores became invalid.

Sanitizer output:

==2547==ERROR: AddressSanitizer: heap-use-after-free on address 0x7fffea323880 at pc 0x000000487e8d bp 0x7fffd4c4e930 sp 0x7fffd4c4e928
READ of size 8 at 0x7fffea323880 thread T7
    #0 0x487e8c in std::__1::equal_to<unsigned long>::operator()(unsigned long const&, unsigned long const&) const /somewhere/bin/../include/c++/v1/functional:688:17
    #1 0x487e8c in phmap::EqualTo<unsigned long>::operator()(unsigned long const&, unsigned long const&) const /somewhere/include/parallel_hashmap/phmap_base.h:79:16
    #2 0x487e8c in bool phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>::operator()<unsigned long, std::__1::piecewise_construct_t const&, std::__1::tuple<unsigned long const&>, std::__1::tuple<short const&> >(unsigned long const&, std::__1::piecewise_construct_t const&, std::__1::tuple<unsigned long const&>&&, std::__1::tuple<short const&>&&) const /somewhere/include/parallel_hashmap/phmap.h:1674:20
    #3 0x487e8c in decltype(std::declval<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long> >()(std::declval<unsigned long const& const&>(), std::__1::piecewise_construct, std::declval<std::__1::tuple<unsigned long const&> >(), std::declval<std::__1::tuple<short const&> >())) phmap::container_internal::memory_internal::DecomposePairImpl<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>, unsigned long const&, std::__1::tuple<short const&> >(phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>&&, std::__1::pair<std::__1::tuple<unsigned long const&>, std::__1::tuple<short const&> >) /somewhere/include/parallel_hashmap/phmap.h:673:12
    #4 0x487e8c in decltype(memory_internal::DecomposePairImpl(std::forward<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long> >(fp), PairArgs(std::forward<std::__1::pair<unsigned long const, short>&>(fp0)))) phmap::container_internal::DecomposePair<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>, std::__1::pair<unsigned long const, short>&>(phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>&&, std::__1::pair<unsigned long const, short>&) /somewhere/include/parallel_hashmap/phmap.h:3499:12
    #5 0x487e8c in decltype(phmap::container_internal::DecomposePair(std::declval<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long> >(), std::declval<std::__1::pair<unsigned long const, short>&>())) phmap::container_internal::FlatHashMapPolicy<unsigned long, short>::apply<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>, std::__1::pair<unsigned long const, short>&>(phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>&&, std::__1::pair<unsigned long const, short>&) /somewhere/include/parallel_hashmap/phmap.h:3602:16
    #6 0x487e8c in decltype(phmap::container_internal::FlatHashMapPolicy<unsigned long, short>::apply(std::forward<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long> >(fp), std::forward<std::__1::pair<unsigned long const, short>&>(fp0))) phmap::container_internal::hash_policy_traits<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, void>::apply<phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>, std::__1::pair<unsigned long const, short>&, phmap::container_internal::FlatHashMapPolicy<unsigned long, short> >(phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::EqualElement<unsigned long>&&, std::__1::pair<unsigned long const, short>&) /somewhere/include/parallel_hashmap/phmap_base.h:544:16
    #7 0x487e8c in phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::iterator phmap::container_internal::raw_hash_set<phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::find<unsigned long>(unsigned long const&, unsigned long) /somewhere/include/parallel_hashmap/phmap.h:1571:21
    #8 0x487e8c in phmap::container_internal::parallel_hash_set<4ul, phmap::container_internal::raw_hash_set, phmap::NullMutex, phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::iterator phmap::container_internal::parallel_hash_set<4ul, phmap::container_internal::raw_hash_set, phmap::NullMutex, phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::find<unsigned long>(unsigned long const&, unsigned long) /somewhere/include/parallel_hashmap/phmap.h:3038:24
    #9 0x487e8c in phmap::container_internal::parallel_hash_set<4ul, phmap::container_internal::raw_hash_set, phmap::NullMutex, phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::iterator phmap::container_internal::parallel_hash_set<4ul, phmap::container_internal::raw_hash_set, phmap::NullMutex, phmap::container_internal::FlatHashMapPolicy<unsigned long, short>, phmap::Hash<unsigned long>, phmap::EqualTo<unsigned long>, std::__1::allocator<std::__1::pair<unsigned long const, short> > >::find<unsigned long>(unsigned long const&) /somewhere/include/parallel_hashmap/phmap.h:3044:16

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.