Skip to content
/ emhash Public
forked from ktprime/emhash

a very fast and memory efficient c++ flat hash map/set

License

Notifications You must be signed in to change notification settings

yhcpp/emhash

 
 

Repository files navigation

A quite fast and memory efficient open address c++ flat hash map

some feature is not enabled by default and it also can be used by set the compile marco but may loss tiny performance, some featue is conflicted each other or difficlut to be merged into only one head file and so it's distributed in different hash table file. Not all feature can be open in only one file(one hash map).
  • default load factor is 0.95 and can be set to 0.999 by set marco EMHASH_HIGH_LOAD == somevalue (in hash_table[5/6/7/8].hpp)

  • head only support by c++11/14/17 without any depency, interface is highly compatible with std::unordered_map,some new function is added for performance issiue.

    • _erase : without return next iterator after erasion
    • shrink_to_fit : shrink memory to fit for saving memory
    • insert_unqiue : insert unique key into hash without find and compare
    • try_find : check or get key/value without return iterator
    • set_get : only once find/insert combind
  • more efficient than other's hash map implemention if key&value is not aligned (ex sizeof(key) % 8 != sizeof(value) % 8), hash_map<uint64_t, uint32_t> can save 1/3 memoery than hash_map<uint64_t, uint64_t>.

  • lru can be used if compile marco EMHASH_LRU_SET set for some special case. for exmaple some key is "frequceny accessed", if the key accessed is not in main bucket position, it'll be swaped with main bucket from current position, and it will be founded/probed only once during next access.

  • no tombstones in this hash map. performance will not deteriorate even high frequceny insertion and erasion.

  • more than 3 different implementation to choose, each of them is some tiny different can be used in some case for example some case pay attention on finding hot, some focus on finding cold(miss), and others only care about insert or erase and so on.

  • the fastest hash map for find performance(100% hit) at present, and fast inserting performacne if no rehash (reserve before insertsion) and effficient erasion. At present from 6 different benchmark(4 of them in my bench dir) by my bench

  • fully tested on OS(Win, Linux, Mac) with compiler(msvs, clang, gcc) and cpu(AMD, Intel, ARM64).

  • many optimization with integer key, some new and interesting feature is underdeveloping if it's stable to release.

emhash design

  • only one array allocted, each node/bucket contains a struct/entry (Key key, uint32_t bucket, Value value), bucket is not awalys in the middle between key and value, depend on struct align pack and compiler marco, index data(bucket) is not separated from the other hash implemention.

  • a smart collision algorithm used for hash collision, collision node is linked after the main bucket with a auxiliary integer index(bucket). main bucket can not be occupyed and all opertion based it.

  • three different ways of probing is used to seach empty slot. it's not suffered heavily performance loss by primary and secondary clustering.

    • linear probing search 2-3 cpu cachelines
    • quadratic probing works after limited linear probing
    • linear search both begin&end with last founded empty slot
  • a new linear probing is used (in hash_table5.hpp). normaly linear probing is inefficient with high load factor, it use a new 3-way linear probing strategy to search empty slot. from benchmark even the load factor > 0.9, it's more 2-3 timer fast than traditional seach strategy.

  • use the second/backup hashing function if the input hash is bad with a very high collision if the compile marco EMHASH_SAFE_HASH is set to defend hash attack(but 10% performance descrease)

  • dump hash collision statics to analyze cache performance, number of probes for look up of successful/unsuccessful can be showed from dump info.

  • A new cache friendly algorithm of finding multi empty bucket base on cpu bit scanf(ctz) instruction. it filters 64 bucket at once than other's implemention.

  • choose different hash algorithm by set compile marco EMHASH_FIBONACCI_HASH or EMHASH_IDENTITY_HASH depend on use case.

  • A thirdy party string hash algorithm is used for string key wyhash, which is faster than std::hash implementation

benchmark

some of benchmark result is uploaded, I use other hash map (martinus, ska, phmap, dense_hash_map ...) source to compile and benchmark. [Bench All] and [Bench High Load]

another html result with impressive curve chartsAll.html (download all js file in tls_bench dir) generated by Tessil benchmark code

txt file result martin_bench.txt generated by code from martin

the benchmark code is some tiny changed for injecting new hash map, the result is not final beacuse it depends on os, cpu, compiler and dataset input.

my result is benched on 3 linux server(amd, intel, arm64), win10 pc/Laptop and apple m1): low is best

some bad

  • it's not a node-based hash map and can't keep the reference stable if insert/erase/rehash happens, use value pointer or choose the other node base hash map.
    emhash7:HashMap<int,int> myhash(10);
    myhash[1] = 1;
    auto& myref = myhash[1];//**wrong used here**,  can not keep reference stable
     ....
    auto old = myref ;  // myref maybe be changed and not invalid.

    emhash7:HashMap<int,int> myhash2;
    for (int i = 0; i < 10000; i ++)
        myhash2[rand()] = myhash2[rand()]; // it will be crashed because of rehash, call reserve before or use insert.
  • for very large key-value, use pointer instead of value if you care about memory usage with high frequency of insertion or erasion
  emhash7:HashMap<keyT,valueT> myhash; //value is very big, ex sizeof(value) 100 byte

  emhash7:HashMap<keyT,*valueT> myhash2; //new valueT, or use std::shared_ptr<valueT>.

  • the only known bug as follow example, if erase key/iterator during iteration. one key will be iteraored twice or missed. and fix it can desearse performance 20% or even much more and no good way to fix.
    emhash7:HashMap<int,int> myhash;
    //dome some init ...
    for (const auto& it : myhash)
    {
        if (some_key == it.first) {
            myhash.erase(key);  //no any break
       }
       ...
       do_some_more();
    }

About

a very fast and memory efficient c++ flat hash map/set

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 85.8%
  • HTML 10.2%
  • JavaScript 3.7%
  • Python 0.1%
  • Makefile 0.1%
  • CMake 0.1%