blueloveTH 3 年之前
父节点
当前提交
57fd320ae2
共有 11 个文件被更改,包括 3877 次插入14 次删除
  1. 2 2
      src/codeobject.h
  2. 5 0
      src/common.h
  3. 1 1
      src/compiler.h
  4. 2034 0
      src/hash_table5.hpp
  5. 1817 0
      src/hash_table6.hpp
  6. 1 1
      src/hash_table8.hpp
  7. 2 2
      src/parser.h
  8. 6 3
      src/pocketpy.h
  9. 1 1
      src/safestl.h
  10. 5 1
      src/str.h
  11. 3 3
      src/vm.h

+ 2 - 2
src/codeobject.h

@@ -61,9 +61,9 @@ struct CodeObject {
     std::vector<Bytecode> codes;
     std::vector<Bytecode> codes;
     pkpy::List consts;
     pkpy::List consts;
     std::vector<std::pair<StrName, NameScope>> names;
     std::vector<std::pair<StrName, NameScope>> names;
-    emhash8::HashMap<StrName, int> global_names;
+    pkpy::HashMap<StrName, int> global_names;
     std::vector<CodeBlock> blocks = { CodeBlock{NO_BLOCK, -1} };
     std::vector<CodeBlock> blocks = { CodeBlock{NO_BLOCK, -1} };
-    emhash8::HashMap<StrName, int> labels;
+    pkpy::HashMap<StrName, int> labels;
 
 
     void optimize(VM* vm);
     void optimize(VM* vm);
 
 

+ 5 - 0
src/common.h

@@ -27,7 +27,12 @@
 // #include <filesystem>
 // #include <filesystem>
 // namespace fs = std::filesystem;
 // namespace fs = std::filesystem;
 
 
+#define EMH_EXT 1
 #include "hash_table8.hpp"
 #include "hash_table8.hpp"
+namespace pkpy {
+	template<typename... Args>
+	using HashMap = emhash8::HashMap<Args...>;
+}
 
 
 #ifdef POCKETPY_H
 #ifdef POCKETPY_H
 #define UNREACHABLE() throw std::runtime_error( "L" + std::to_string(__LINE__) + " UNREACHABLE()!");
 #define UNREACHABLE() throw std::runtime_error( "L" + std::to_string(__LINE__) + " UNREACHABLE()!");

+ 1 - 1
src/compiler.h

@@ -23,7 +23,7 @@ class Compiler {
     int lexing_count = 0;
     int lexing_count = 0;
     bool used = false;
     bool used = false;
     VM* vm;
     VM* vm;
-    emhash8::HashMap<TokenIndex, GrammarRule> rules;
+    pkpy::HashMap<TokenIndex, GrammarRule> rules;
 
 
     CodeObject_ co() const{ return codes.top(); }
     CodeObject_ co() const{ return codes.top(); }
     CompileMode mode() const{ return parser->src->mode; }
     CompileMode mode() const{ return parser->src->mode; }

+ 2034 - 0
src/hash_table5.hpp

@@ -0,0 +1,2034 @@
+// emhash5::HashMap for C++11/14/17
+// version 2.1.2
+// https://github.com/ktprime/ktprime/blob/master/hash_table5.hpp
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2019-2023 Huang Yuanbing & bailuzhou AT 163.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE
+
+#pragma once
+
+#include <cstring>
+#include <string>
+#include <cmath>
+#include <cstdlib>
+#include <type_traits>
+#include <cassert>
+#include <utility>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <algorithm>
+
+#if EMH_WY_HASH
+    #include "wyhash.h"
+#endif
+
+#ifdef EMH_KEY
+    #undef  EMH_KEY
+    #undef  EMH_VAL
+    #undef  EMH_PKV
+    #undef  EMH_BUCKET
+    #undef  EMH_NEW
+    #undef  EMH_EMPTY
+    #undef  EMH_PREVET
+#endif
+
+// likely/unlikely
+#if (__GNUC__ >= 4 || __clang__)
+#    define EMH_LIKELY(condition)   __builtin_expect(condition, 1)
+#    define EMH_UNLIKELY(condition) __builtin_expect(condition, 0)
+#else
+#    define EMH_LIKELY(condition)   condition
+#    define EMH_UNLIKELY(condition) condition
+#endif
+
+#ifndef EMH_BUCKET_INDEX
+    #define EMH_BUCKET_INDEX 1
+#endif
+
+#if EMH_BUCKET_INDEX == 0
+    #define EMH_KEY(p,n)     p[n].second.first
+    #define EMH_VAL(p,n)     p[n].second.second
+    #define EMH_BUCKET(p,n)  p[n].first
+    #define EMH_PKV(p,n)     p[n].second
+    #define EMH_NEW(key, val, bucket) new(_pairs + bucket) PairT(bucket, value_type(key, val)); _num_filled ++
+#elif EMH_BUCKET_INDEX == 2
+    #define EMH_KEY(p,n)     p[n].first.first
+    #define EMH_VAL(p,n)     p[n].first.second
+    #define EMH_BUCKET(p,n)  p[n].second
+    #define EMH_PREVET(p,n)  *(size_type*)(&p[n].first.first)
+    #define EMH_PKV(p,n)     p[n].first
+    #define EMH_NEW(key, val, bucket) new(_pairs + bucket) PairT(value_type(key, val), bucket); _num_filled ++
+#else
+    #define EMH_KEY(p,n)     p[n].first
+    #define EMH_VAL(p,n)     p[n].second
+    #define EMH_BUCKET(p,n)  p[n].bucket
+    #define EMH_PREVET(p,n)  *(size_type*)(&p[n].first)
+    #define EMH_PKV(p,n)     p[n]
+    #define EMH_NEW(key, val, bucket) new(_pairs + bucket) PairT(key, val, bucket); _num_filled ++
+#endif
+
+#define EMH_EMPTY(p, b) (0 > (int)EMH_BUCKET(p, b))
+
+namespace emhash5 {
+
+#if EMH_SIZE_TYPE_64BIT
+    typedef uint64_t size_type;
+    static constexpr size_type INACTIVE = 0 - 0x1ull;
+#else
+    typedef uint32_t size_type;
+    const constexpr size_type INACTIVE = 0xFFFFFFFF;
+#endif
+
+#ifndef EMH_MALIGN
+    static constexpr uint32_t EMH_MALIGN = 16;
+#endif
+static_assert(EMH_MALIGN >= 16 && (EMH_MALIGN & (EMH_MALIGN - 1)) == 0);
+
+template <typename First, typename Second>
+struct entry {
+    using first_type =  First;
+    using second_type = Second;
+    entry(const First& key, const Second& val, size_type ibucket)
+        :second(val), first(key)
+    {
+        bucket = ibucket;
+    }
+
+    entry(First&& key, Second&& val, size_type ibucket)
+        :second(std::move(val)), first(std::move(key))
+    {
+        bucket = ibucket;
+    }
+
+    template<typename K, typename V>
+    entry(K&& key, V&& val, size_type ibucket)
+        :second(std::forward<V>(val)), first(std::forward<K>(key))
+    {
+        bucket = ibucket;
+    }
+
+    template<typename K, typename V>
+    entry(K&& key, std::tuple<V> val, size_type ibucket)
+        :second(std::get<1>(val)),
+        first(std::forward<K>(key))
+    {
+        bucket = ibucket;
+    }
+
+    entry(const std::pair<First, Second>& pair)
+        :second(pair.second), first(pair.first)
+    {
+        bucket = INACTIVE;
+    }
+
+    entry(std::pair<First, Second>&& pair)
+        :second(std::move(pair.second)), first(std::move(pair.first))
+    {
+        bucket = INACTIVE;
+    }
+
+    entry(std::tuple<First, Second>&& tup)
+        :second(std::move(std::get<2>(tup))), first(std::move(std::get<1>(tup)))
+    {
+        bucket = INACTIVE;
+    }
+
+    entry(const entry& rhs)
+        :second(rhs.second), first(rhs.first)
+    {
+        bucket = rhs.bucket;
+    }
+
+    entry(entry&& rhs) noexcept
+        :second(std::move(rhs.second)), first(std::move(rhs.first))
+    {
+        bucket = rhs.bucket;
+    }
+
+    entry& operator = (entry&& rhs) noexcept
+    {
+        second = std::move(rhs.second);
+        bucket = rhs.bucket;
+        first  = std::move(rhs.first);
+        return *this;
+    }
+
+    entry& operator = (const entry& rhs)
+    {
+        second = rhs.second;
+        bucket = rhs.bucket;
+        first  = rhs.first;
+        return *this;
+    }
+
+    bool operator == (const entry<First, Second>& p) const
+    {
+        return first == p.first && second == p.second;
+    }
+
+    bool operator == (const std::pair<First, Second>& p) const
+    {
+        return first == p.first && second == p.second;
+    }
+
+    void swap(entry<First, Second>& o)
+    {
+        std::swap(second, o.second);
+        std::swap(first, o.first);
+    }
+
+#if EMH_ORDER_KV || EMH_SIZE_TYPE_64BIT
+    First first; //long
+    size_type bucket;
+    Second second;//int
+#else
+    Second second;
+    size_type bucket;
+    First first;
+#endif
+};
+
+/// A cache-friendly hash table with open addressing, linear/qua probing and power-of-two capacity
+template <typename KeyT, typename ValueT, typename HashT = std::hash<KeyT>, typename EqT = std::equal_to<KeyT>>
+class HashMap
+{
+#ifndef EMH_DEFAULT_LOAD_FACTOR
+    constexpr static float EMH_DEFAULT_LOAD_FACTOR = 0.80f;
+    constexpr static float EMH_MIN_LOAD_FACTOR     = 0.25f; //< 0.5
+#endif
+#if EMH_CACHE_LINE_SIZE < 32
+    constexpr static uint32_t EMH_CACHE_LINE_SIZE  = 64;
+#endif
+
+public:
+    typedef HashMap<KeyT, ValueT, HashT, EqT> htype;
+    typedef std::pair<KeyT, ValueT>           value_type;
+
+#if EMH_BUCKET_INDEX == 0
+    typedef value_type                        value_pair;
+    typedef std::pair<size_type, value_type>  PairT;
+#elif EMH_BUCKET_INDEX == 2
+    typedef value_type                        value_pair;
+    typedef std::pair<value_type, size_type>  PairT;
+#else
+    typedef entry<KeyT, ValueT>               value_pair;
+    typedef entry<KeyT, ValueT>               PairT;
+#endif
+
+    typedef KeyT   key_type;
+    typedef ValueT val_type;
+    typedef ValueT mapped_type;
+    typedef HashT  hasher;
+    typedef EqT    key_equal;
+    typedef PairT&       reference;
+    typedef const PairT& const_reference;
+
+    class const_iterator;
+    class iterator
+    {
+    public:
+        typedef std::forward_iterator_tag iterator_category;
+        typedef std::ptrdiff_t            difference_type;
+        typedef value_pair                value_type;
+
+        typedef value_pair*               pointer;
+        typedef value_pair&               reference;
+
+        iterator() { _map = nullptr; _bucket = -1; }
+        iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { }
+
+        iterator& operator++()
+        {
+            goto_next_element();
+            return *this;
+        }
+
+        iterator operator++(int)
+        {
+            auto old_index = _bucket;
+            goto_next_element();
+            return {_map, old_index};
+        }
+
+        reference operator*() const { return _map->EMH_PKV(_pairs, _bucket); }
+        pointer operator->() const { return &(_map->EMH_PKV(_pairs, _bucket)); }
+
+        bool operator==(const iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const iterator& rhs) const { return _bucket != rhs._bucket; }
+
+        bool operator==(const const_iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const const_iterator& rhs) const { return _bucket != rhs._bucket; }
+
+        size_type bucket() const { return _bucket; }
+
+    private:
+        void goto_next_element()
+        {
+            while ((int)_map->EMH_BUCKET(_pairs, ++_bucket) < 0);
+        }
+
+    public:
+        const htype* _map;
+        size_type _bucket;
+    };
+
+    class const_iterator
+    {
+    public:
+        typedef std::forward_iterator_tag iterator_category;
+        typedef std::ptrdiff_t            difference_type;
+        typedef value_pair                value_type;
+
+        typedef const value_pair*         pointer;
+        typedef const value_pair&         reference;
+
+        //const_iterator() { }
+        const_iterator(const iterator& proto) : _map(proto._map), _bucket(proto._bucket) { }
+        const_iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { }
+
+        const_iterator& operator++()
+        {
+            goto_next_element();
+            return *this;
+        }
+
+        const_iterator operator++(int)
+        {
+            auto old_index = _bucket;
+            goto_next_element();
+            return {_map, old_index};
+        }
+
+        reference operator*() const { return _map->EMH_PKV(_pairs, _bucket); }
+        pointer operator->() const { return &(_map->EMH_PKV(_pairs, _bucket)); }
+
+        bool operator==(const const_iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const const_iterator& rhs) const { return _bucket != rhs._bucket; }
+
+        size_type bucket() const { return _bucket; }
+
+    private:
+        void goto_next_element()
+        {
+            while ((int)_map->EMH_BUCKET(_pairs, ++_bucket) < 0);
+        }
+
+    public:
+        const htype* _map;
+        size_type _bucket;
+    };
+
+    void init(size_type bucket, float mlf = EMH_DEFAULT_LOAD_FACTOR) noexcept
+    {
+        _pairs = nullptr;
+        _mask  = _num_buckets = 0;
+        _num_filled = 0;
+#if EMH_HIGH_LOAD
+        _ehead = 0;
+#endif
+        max_load_factor(mlf);
+        rehash(bucket);
+    }
+
+    HashMap(size_type bucket = 2, float mlf = EMH_DEFAULT_LOAD_FACTOR) noexcept
+    {
+        init(bucket, mlf);
+    }
+
+    HashMap(const HashMap& rhs) noexcept
+    {
+        if (rhs.load_factor() > EMH_MIN_LOAD_FACTOR) {
+            _pairs = alloc_bucket(rhs._num_buckets);
+            clone(rhs);
+        } else {
+            init(rhs._num_filled + 2, EMH_DEFAULT_LOAD_FACTOR);
+            for (auto it = rhs.begin(); it != rhs.end(); ++it)
+                insert_unique(it->first, it->second);
+        }
+    }
+
+    HashMap(HashMap&& rhs) noexcept
+    {
+        init(0);
+        *this = std::move(rhs);
+    }
+
+    HashMap(std::initializer_list<value_type> ilist) noexcept
+    {
+        init((size_type)ilist.size());
+        for (auto it = ilist.begin(); it != ilist.end(); ++it)
+            do_insert(*it);
+    }
+
+    template<class InputIt>
+    HashMap(InputIt first, InputIt last, size_type bucket_count=4) noexcept
+    {
+        init(std::distance(first, last) + bucket_count);
+        for (; first != last; ++first)
+            emplace(*first);
+    }
+
+    HashMap& operator=(const HashMap& rhs) noexcept
+    {
+        if (this == &rhs)
+            return *this;
+
+        if (rhs.load_factor() < EMH_MIN_LOAD_FACTOR) {
+            clear();
+#if EMH_SMALL_SIZE
+            if (_pairs != (PairT*)_small)
+#endif
+            free(_pairs);
+            _pairs = nullptr;
+
+            rehash(rhs._num_filled + 2);
+            for (auto it = rhs.begin(); it != rhs.end(); ++it)
+                insert_unique(it->first, it->second);
+            return *this;
+        }
+
+        clearkv();
+        if (_num_buckets < rhs._num_buckets || _num_buckets > 2 * rhs._num_buckets) {
+#if EMH_SMALL_SIZE
+            if (_pairs != (PairT*)_small)
+#endif
+            free(_pairs);
+            _pairs = alloc_bucket(rhs._num_buckets);
+        }
+
+        clone(rhs);
+        return *this;
+    }
+
+    HashMap& operator=(HashMap&& rhs) noexcept
+    {
+        if (this == &rhs)
+            return *this;
+
+#if EMH_SMALL_SIZE
+        if (_pairs == (PairT*)_small || rhs._pairs == (PairT*)rhs._small) {
+            clear();
+            if (rhs.empty())
+                return *this;
+            if (_pairs != (PairT*)_small)
+                free(_pairs);
+            if (rhs._num_buckets > EMH_SMALL_SIZE)
+                _pairs = alloc_bucket(rhs._num_buckets);
+            clone(rhs);
+            rhs.clear();
+            return *this;
+        }
+#endif
+        swap(rhs);
+        rhs.clear();
+        return *this;
+    }
+
+    template<typename Con>
+    bool operator == (const Con& rhs) const noexcept
+    {
+        if (size() != rhs.size())
+            return false;
+
+        for (auto it = begin(), last = end(); it != last; ++it) {
+            auto oi = rhs.find(it->first);
+            if (oi == rhs.end() || it->second != oi->second)
+                return false;
+        }
+        return true;
+    }
+
+    template<typename Con>
+    bool operator != (const Con& rhs) const { return !(*this == rhs); }
+
+    ~HashMap() noexcept
+    {
+        clearkv();
+#if EMH_SMALL_SIZE
+        if (_pairs != (PairT*)_small)
+#endif
+        free(_pairs);
+    }
+
+    void clone(const HashMap& rhs) noexcept
+    {
+        _hasher      = rhs._hasher;
+//        _eq          = rhs._eq;
+        _num_buckets = rhs._num_buckets;
+        _num_filled  = rhs._num_filled;
+        _mlf         = rhs._mlf;
+        _last        = rhs._last;
+        _mask        = rhs._mask;
+#if EMH_HIGH_LOAD
+        _ehead       = rhs._ehead;
+#endif
+
+        auto opairs  = rhs._pairs;
+
+        if (is_copy_trivially())
+            memcpy(_pairs, opairs, (_num_buckets + 2) * sizeof(PairT));
+        else {
+            for (size_type bucket = 0; bucket < _num_buckets; bucket++) {
+                auto next_bucket = EMH_BUCKET(_pairs, bucket) = EMH_BUCKET(opairs, bucket);
+                if ((int)next_bucket >= 0)
+                    new(_pairs + bucket) PairT(opairs[bucket]);
+#if EMH_HIGH_LOAD
+                else if (next_bucket != INACTIVE)
+                    EMH_PREVET(_pairs, bucket) = EMH_PREVET(opairs, bucket);
+#endif
+            }
+            memcpy(_pairs + _num_buckets, opairs + _num_buckets, sizeof(PairT) * 2);
+        }
+    }
+
+    void swap(HashMap& rhs) noexcept
+    {
+#if EMH_SMALL_SIZE
+        if (_pairs == (PairT*)_small || rhs._pairs == (PairT*)rhs._small) {
+            if (is_copy_trivially()) {
+                char tmp[(EMH_SMALL_SIZE + 2) * sizeof(PairT)];
+                memcpy(tmp,  _small, sizeof(tmp));
+                memcpy(_small, rhs._small, sizeof(tmp));
+                memcpy(rhs._small, tmp,  sizeof(tmp)); //copy once if only one small map
+            } else {
+                HashMap tmp(*this);
+                *this = rhs;
+                rhs = tmp;
+                return;
+            }
+        }
+#endif
+        //      std::swap(_eq, rhs._eq);
+        std::swap(_hasher, rhs._hasher);
+        std::swap(_pairs, rhs._pairs);
+        std::swap(_num_buckets, rhs._num_buckets);
+        std::swap(_num_filled, rhs._num_filled);
+        std::swap(_mask, rhs._mask);
+        std::swap(_mlf, rhs._mlf);
+        std::swap(_last, rhs._last);
+#if EMH_HIGH_LOAD
+        std::swap(_ehead, rhs._ehead);
+#endif
+    }
+
+    // -------------------------------------------------------------
+    iterator begin() noexcept
+    {
+        if (_num_filled == 0)
+            return end();
+
+        size_type bucket = 0;
+        while (EMH_EMPTY(_pairs, bucket)) {
+            ++bucket;
+        }
+        return {this, bucket};
+    }
+
+#if 0
+    iterator last() noexcept
+    {
+        if (_num_filled == 0)
+            return end();
+
+        size_type bucket = _num_buckets - 1;
+        while (EMH_EMPTY(_pairs, bucket)) bucket--;
+        return {this, bucket};
+    }
+#endif
+
+    const_iterator cbegin() const noexcept
+    {
+        if (_num_filled == 0)
+            return end();
+
+        size_type bucket = 0;
+        while (EMH_EMPTY(_pairs, bucket)) {
+            ++bucket;
+        }
+        return {this, bucket};
+    }
+    inline const_iterator begin() const noexcept { return cbegin(); }
+
+    inline iterator end() noexcept { return {this, _num_buckets}; }
+    inline const_iterator end()  const noexcept { return cend(); }
+    inline const_iterator cend() const noexcept { return {this, _num_buckets}; }
+
+    inline size_type size() const noexcept { return _num_filled; }
+    inline bool empty() const noexcept { return _num_filled == 0; }
+    inline size_type bucket_count() const noexcept { return _num_buckets; }
+
+    inline HashT hash_function() const noexcept { return static_cast<const HashT&>(_hasher); }
+    inline EqT key_eq() const noexcept { return static_cast<const EqT&>(_eq); }
+
+    inline float load_factor() const noexcept { return static_cast<float>(_num_filled) / _num_buckets; }
+    inline float max_load_factor() const noexcept { return (1 << 27) / (float)_mlf; }
+    void max_load_factor(float ml) noexcept
+    {
+        if (ml < 0.991f && ml > EMH_MIN_LOAD_FACTOR)
+            _mlf = (uint32_t)((1 << 27) / ml);
+    }
+
+    inline constexpr size_type max_size() const { return 1ull << (sizeof(size_type) * 8 - 1); }
+    inline constexpr size_type max_bucket_count() const { return max_size(); }
+
+#if EMH_STATIS
+    //Returns the bucket number where the element with key k is located.
+    size_type bucket_slot(const KeyT& key) const
+    {
+        const auto bucket = key_to_bucket(key);
+        const auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return 0;
+        else if (bucket == next_bucket)
+            return bucket + 1;
+
+        return hash_main(bucket) + 1;
+    }
+
+    //Returns the number of elements in bucket n.
+    size_type bucket_size(const size_type bucket) const
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return 0;
+
+        next_bucket = hash_main(bucket);
+        size_type ibucket_size = 1;
+
+        //iterator each item in current main bucket
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket) {
+                break;
+            }
+            ibucket_size ++;
+            next_bucket = nbucket;
+        }
+        return ibucket_size;
+    }
+
+    size_type get_main_bucket(const uint32_t bucket) const
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return INACTIVE;
+
+        return hash_main(bucket);
+    }
+
+    size_type get_diss(uint32_t bucket, uint32_t next_bucket, const uint32_t slots) const
+    {
+        auto pbucket = reinterpret_cast<uint64_t>(&_pairs[bucket]);
+        auto pnext   = reinterpret_cast<uint64_t>(&_pairs[next_bucket]);
+        if (pbucket / EMH_CACHE_LINE_SIZE == pnext / EMH_CACHE_LINE_SIZE)
+            return 0;
+        uint32_t diff = pbucket > pnext ? (pbucket - pnext) : (pnext - pbucket);
+        if (diff / EMH_CACHE_LINE_SIZE < slots - 1)
+            return diff / EMH_CACHE_LINE_SIZE + 1;
+        return slots - 1;
+    }
+
+    int get_bucket_info(const uint32_t bucket, uint32_t steps[], const uint32_t slots) const
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return -1;
+
+        const auto main_bucket = hash_main(bucket);
+        if (next_bucket == main_bucket)
+            return 1;
+        else if (main_bucket != bucket)
+            return 0;
+
+        steps[get_diss(bucket, next_bucket, slots)] ++;
+        uint32_t ibucket_size = 2;
+        //find a new empty and linked it to tail
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                break;
+
+            steps[get_diss(nbucket, next_bucket, slots)] ++;
+            ibucket_size ++;
+            next_bucket = nbucket;
+        }
+        return ibucket_size;
+    }
+
+    void dump_statics() const
+    {
+        const int slots = 128;
+        uint32_t buckets[slots + 1] = {0};
+        uint32_t steps[slots + 1]   = {0};
+        for (uint32_t bucket = 0; bucket < _num_buckets; ++bucket) {
+            auto bsize = get_bucket_info(bucket, steps, slots);
+            if (bsize > 0)
+                buckets[bsize] ++;
+        }
+
+        uint32_t sumb = 0, collision = 0, sumc = 0, finds = 0, sumn = 0;
+        puts("============== buckets size ration =========");
+        for (uint32_t i = 0; i < sizeof(buckets) / sizeof(buckets[0]); i++) {
+            const auto bucketsi = buckets[i];
+            if (bucketsi == 0)
+                continue;
+            sumb += bucketsi;
+            sumn += bucketsi * i;
+            collision += bucketsi * (i - 1);
+            finds += bucketsi * i * (i + 1) / 2;
+            printf("  %2u  %8u  %2.2lf|  %.2lf\n", i, bucketsi, bucketsi * 100.0 * i / _num_filled, sumn * 100.0 / _num_filled);
+        }
+
+        puts("========== collision miss ration ===========");
+        for (uint32_t i = 0; i < sizeof(steps) / sizeof(steps[0]); i++) {
+            sumc += steps[i];
+            if (steps[i] <= 2)
+                continue;
+            printf("  %2u  %8u  %.2lf  %.2lf\n", i, steps[i], steps[i] * 100.0 / collision, sumc * 100.0 / collision);
+        }
+
+        if (sumb == 0)  return;
+        printf("    _num_filled/bucket_size/packed collision/cache_miss/hit_find = %u/%.2lf/%d/ %.2lf%%/%.2lf%%/%.2lf\n",
+                _num_filled, _num_filled * 1.0 / sumb, int(sizeof(PairT)), (collision * 100.0 / _num_filled), (collision - steps[0]) * 100.0 / _num_filled, finds * 1.0 / _num_filled);
+        assert(sumc == collision);
+        assert(sumn == _num_filled);
+        puts("============== buckets size end =============");
+    }
+#endif
+
+    // ------------------------------------------------------------
+    template<typename K=KeyT>
+    inline iterator find(const K& key) noexcept
+    {
+        return {this, find_filled_bucket(key)};
+    }
+
+    template<typename K=KeyT>
+    inline const_iterator find(const K& key) const noexcept
+    {
+        return {this, find_filled_bucket(key)};
+    }
+
+    template<typename K=KeyT>
+    inline iterator find(const K& key, size_type key_hash) noexcept
+    {
+        return {this, find_hash_bucket(key, key_hash)};
+    }
+
+    template<typename K=KeyT>
+    inline const_iterator find(const K& key, size_type key_hash) const noexcept
+    {
+        return {this, find_hash_bucket(key, key_hash)};
+    }
+
+    template<typename K=KeyT>
+    inline ValueT& at(const K& key)
+    {
+        const auto bucket = find_filled_bucket(key);
+        //throw
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename K=KeyT>
+    inline const ValueT& at(const K& key) const
+    {
+        const auto bucket = find_filled_bucket(key);
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename K=KeyT>
+    ValueT& at(const K& key, size_type key_hash)
+    {
+        const auto bucket = find_hash_bucket(key, key_hash);
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename K=KeyT>
+    const ValueT& at(const K& key, size_type key_hash) const
+    {
+        const auto bucket = find_hash_bucket(key, key_hash);
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename K=KeyT>
+    inline bool contains(const K& key) const noexcept
+    {
+        return find_filled_bucket(key) != _num_buckets;
+    }
+
+    template<typename K=KeyT>
+    inline bool contains(const K& key, size_type key_hash) const noexcept
+    {
+        return find_hash_bucket(key, key_hash) != _num_buckets;
+    }
+
+    template<typename K=KeyT>
+    inline size_type count(const K& key) const noexcept
+    {
+        return find_filled_bucket(key) == _num_buckets ? 0 : 1;
+    }
+
+    template<typename K=KeyT>
+    inline size_type count(const K& key, size_type key_hash) const noexcept
+    {
+        return find_hash_bucket(key, key_hash) == _num_buckets ? 0 : 1;
+    }
+
+    template<typename K=KeyT>
+    std::pair<iterator, iterator> equal_range(const K& key) noexcept
+    {
+        const auto found = find(key);
+        if (found.bucket() == _num_buckets)
+            return { found, found };
+        else
+            return { found, std::next(found) };
+    }
+
+    template<typename K=KeyT>
+    std::pair<const_iterator, const_iterator> equal_range(const K& key) const
+    {
+        const auto found = find(key);
+        if (found.bucket() == _num_buckets)
+            return { found, found };
+        else
+            return { found, std::next(found) };
+    }
+
+    void merge(HashMap& rhs)
+    {
+        if (_num_filled == 0) {
+            *this = std::move(rhs);
+            return;
+        }
+
+        for (auto rit = rhs.begin(); rit != rhs.end(); ) {
+            auto fit = find(rit->first);
+            if (fit.bucket() == _num_buckets) {
+                insert_unique(rit->first, std::move(rit->second));
+                rit = rhs.erase(rit);
+            } else {
+                ++rit;
+            }
+        }
+    }
+
+#ifdef EMH_EXT
+    /// Return the old value or ValueT() if it didn't exist.
+    ValueT set_get(const KeyT& key, const ValueT& val)
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+
+        if (EMH_EMPTY(_pairs, bucket)) {
+            EMH_NEW(key, val, bucket);
+            return ValueT();
+        } else {
+            ValueT old_value(val);
+            std::swap(EMH_VAL(_pairs, bucket), old_value);
+            return old_value;
+        }
+    }
+
+    /// Returns the matching ValueT or nullptr if k isn't found.
+    bool try_get(const KeyT& key, ValueT& val) const
+    {
+        const auto bucket = find_filled_bucket(key);
+        const auto found = bucket != _num_buckets;
+        if (found) {
+            val = EMH_VAL(_pairs, bucket);
+        }
+        return found;
+    }
+
+    /// Returns the matching ValueT or nullptr if k isn't found.
+    inline ValueT* try_get(const KeyT& key)
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket != _num_buckets ? &EMH_VAL(_pairs, bucket) : nullptr;
+    }
+
+    /// Const version of the above
+    inline ValueT* try_get(const KeyT& key) const
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket != _num_buckets ? &EMH_VAL(_pairs, bucket) : nullptr;
+    }
+
+    /// set value if key exist
+    bool try_set(const KeyT& key, const ValueT& val)
+    {
+        const auto bucket = find_filled_bucket(key);
+        if (bucket == _num_buckets)
+            return false;
+
+        EMH_VAL(_pairs, bucket) = val;
+        return true;
+    }
+
+    /// set value if key exist
+    bool try_set(const KeyT& key, ValueT&& val)
+    {
+        const auto bucket = find_filled_bucket(key);
+        if (bucket == _num_buckets)
+            return false;
+
+        EMH_VAL(_pairs, bucket) = std::move(val);
+        return true;
+    }
+
+    /// Convenience function.
+    ValueT get_or_return_default(const KeyT& key) const
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket == _num_buckets ? ValueT() : EMH_VAL(_pairs, bucket);
+    }
+#endif
+
+    // -----------------------------------------------------
+#if EMH_BUCKET_INDEX == 1
+    std::pair<iterator, bool> do_insert(const value_pair& value)
+    {
+        const auto bucket = find_or_allocate(value.first);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+            EMH_NEW(value.first, value.second, bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+#endif
+
+    std::pair<iterator, bool> do_insert(const value_type& value) noexcept
+    {
+        const auto bucket = find_or_allocate(value.first);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+            EMH_NEW(value.first, value.second, bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    std::pair<iterator, bool> do_insert(value_type&& value) noexcept
+    {
+        const auto bucket = find_or_allocate(value.first);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+            EMH_NEW(std::move(value.first), std::move(value.second), bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    template<typename K, typename V>
+    std::pair<iterator, bool> do_insert(K&& key, V&& val) noexcept
+    {
+        const auto bucket = find_or_allocate(key);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+            EMH_NEW(std::forward<K>(key), std::forward<V>(val), bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    template<typename K, typename V>
+    std::pair<iterator, bool> do_assign(K&& key, V&& val) noexcept
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+            EMH_NEW(std::forward<K>(key), std::forward<V>(val), bucket);
+        } else {
+            EMH_VAL(_pairs, bucket) = std::forward<V>(val);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    std::pair<iterator, bool> insert(const value_type& value) noexcept
+    {
+        check_expand_need();
+        return do_insert(value);
+    }
+
+    std::pair<iterator, bool> insert(value_type&& value) noexcept
+    {
+        check_expand_need();
+        return do_insert(std::move(value));
+    }
+
+    template< typename P >
+    std::pair<iterator, bool> insert(P&& value) noexcept
+    {
+        check_expand_need();
+        return do_insert(std::forward<P>(value));
+    }
+
+    iterator insert(const_iterator hint, const value_type& value)
+    {
+        if (hint.bucket() != _num_buckets && hint->first == value.first) {
+            return {this, hint.bucket()};
+        }
+
+        check_expand_need();
+        return do_insert(value).first;
+    }
+
+    iterator insert(const_iterator hint, value_type&& value)
+    {
+        if (hint.bucket() != _num_buckets && hint->first == value.first) {
+            return {this, hint.bucket()};
+        }
+
+        check_expand_need();
+        return do_insert(std::move(value)).first;
+    }
+
+    void insert(std::initializer_list<value_type> ilist)
+    {
+        reserve(ilist.size() + _num_filled);
+        for (auto it = ilist.begin(); it != ilist.end(); ++it)
+            do_insert(*it);
+    }
+
+    template <typename Iter>
+    void insert(Iter first, Iter last)
+    {
+        reserve(std::distance(first, last) + _num_filled);
+        for (; first != last; ++first)
+            emplace(*first);
+    }
+
+    //assert(bucket < _num_buckets)
+    ValueT* find_hint(const KeyT& key, size_t bucket)
+    {
+        if (!EMH_EMPTY(_pairs, bucket) && EMH_KEY(_pairs, bucket) == key)
+            return &EMH_VAL(_pairs, bucket);
+        return nullptr;
+    }
+
+#if 0
+    template <typename Iter>
+    void insert_unique(Iter begin, Iter end) noexcept
+    {
+        reserve(std::distance(begin, end) + _num_filled);
+        for (; begin != end; ++begin) {
+            insert_unique(*begin);
+        }
+    }
+#endif
+
+    template<typename K, typename V>
+    size_type insert_unique(K&& key, V&& val) noexcept
+    {
+        check_expand_need();
+        auto bucket = find_unique_bucket(key);
+        EMH_NEW(std::forward<K>(key), std::forward<V>(val), bucket);
+        return bucket;
+    }
+
+    inline size_type insert_unique(value_type&& value) noexcept
+    {
+        return insert_unique(std::move(value.first), std::move(value.second));
+    }
+
+    inline size_type insert_unique(const value_type& value)
+    {
+        return insert_unique(value.first, value.second);
+    }
+
+    template <class... Args>
+    inline size_type emplace_unique(Args&&... args) noexcept
+    {
+        return insert_unique(std::forward<Args>(args)...);
+    }
+
+    template <class... Args>
+    std::pair<iterator, bool> emplace(Args&&... args) noexcept
+    {
+        check_expand_need();
+        return do_insert(std::forward<Args>(args)...);
+    }
+
+    template <class... Args>
+    iterator emplace_hint(const_iterator hint, Args&&... args)
+    {
+        value_type value(std::forward<Args>(args)...);
+        auto bucket = hint.bucket();
+        if (bucket != _num_buckets && hint->first == value.first) {
+            return {this, bucket};
+        }
+
+        check_expand_need();
+        return do_insert(std::move(value)).first;
+    }
+
+    //TODO: fix tuple
+    template<class... Args>
+    std::pair<iterator, bool> try_emplace(const KeyT& key, Args&&... args)
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+//            EMH_NEW(key, std::forward_as_tuple(std::forward<Args>(args)...), bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    template<class... Args>
+    std::pair<iterator, bool> try_emplace(KeyT&& key, Args&&... args)
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto bempty = EMH_EMPTY(_pairs, bucket);
+        if (bempty) {
+//            EMH_NEW(std::move(key), std::forward_as_tuple(std::forward<Args>(args)...), bucket);
+        }
+        return { {this, bucket}, bempty };
+    }
+
+    template<class... Args>
+    iterator try_emplace(const_iterator hint, const KeyT& key, Args&&... args)
+    {
+        (void)hint;
+        return try_emplace(key, std::forward<Args>(args)...).first;
+    }
+
+    template<class... Args>
+    iterator try_emplace(const_iterator hint, KeyT&& key, Args&&... args)
+    {
+        (void)hint;
+        return try_emplace(std::move(key), std::forward<Args>(args)...).first;
+    }
+
+    template <class M>
+    std::pair<iterator, bool> insert_or_assign(const KeyT& key, M&& val) noexcept { return do_assign(key, std::forward<M>(val)); }
+    template <class M>
+    std::pair<iterator, bool> insert_or_assign(KeyT&& key, M&& val) noexcept { return do_assign(std::move(key), std::forward<M>(val)); }
+
+    template <class M>
+    iterator insert_or_assign(const_iterator hint, const KeyT& key, M&& val) {
+        auto bucket = hint.bucket();
+        if (bucket != _num_buckets && hint->first == key) {
+            hint->second = std::forward<M>(val);
+            return {this, bucket};
+        }
+
+        return do_assign(key, std::forward<M>(val)).first;
+    }
+
+    template <class M>
+    iterator insert_or_assign(const_iterator hint, KeyT&& key, M&& val) {
+        auto bucket = hint.bucket();
+        if (bucket != _num_buckets && hint->first == key) {
+            EMH_VAL(_pairs, bucket) = std::forward<M>(val);
+            return {this, bucket};
+        }
+
+        return do_assign(std::move(key), std::forward<M>(val)).first;
+    }
+
+    /// Like std::map<KeyT, ValueT>::operator[].
+    ValueT& operator[](const KeyT& key) noexcept
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        if (EMH_EMPTY(_pairs, bucket)) {
+            /* Check if inserting a new value rather than overwriting an old entry */
+            EMH_NEW(key, std::move(ValueT()), bucket);
+        }
+
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    ValueT& operator[](KeyT&& key) noexcept
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        if (EMH_EMPTY(_pairs, bucket)) {
+            EMH_NEW(std::move(key), std::move(ValueT()), bucket);
+        }
+
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    // -------------------------------------------------------
+    /// return 0 if not erase
+#if 0
+    size_type erase_node(const KeyT& key, const size_type slot)
+    {
+        if (slot < _num_buckets && _pairs[slot].second != INACTIVE && _pairs[slot].first == key) {
+            erase_bucket(slot);
+            return 1;
+        }
+        return erase(key);
+    }
+#endif
+
+    /// Erase an element from the hash table.
+    /// return 0 if element was not found
+    size_type erase(const KeyT& key) noexcept
+    {
+        const auto bucket = erase_key(key);
+        if (bucket == INACTIVE)
+            return 0;
+
+        clear_bucket(bucket);
+        return 1;
+    }
+
+#if 0
+    template <typename K=KeyT>
+    size_type erase(K&& key)
+    {
+        const auto bucket = erase_key(key);
+        if ((int)bucket < 0)
+            return 0;
+
+        clear_bucket(bucket);
+        return 1;
+    }
+#endif
+
+    //iterator erase(const_iterator begin_it, const_iterator end_it)
+    iterator erase(const_iterator cit) noexcept
+    {
+        const auto bucket = erase_bucket(cit._bucket);
+        clear_bucket(bucket);
+
+        iterator it(this, cit._bucket);
+        //erase from main bucket, return main bucket as next
+        return (bucket == it._bucket) ? ++it : it;
+    }
+
+    void _erase(const_iterator it) noexcept
+    {
+        const auto bucket = erase_bucket(it._bucket);
+        clear_bucket(bucket);
+    }
+
+    void _erase(iterator it) noexcept
+    {
+        const auto bucket = erase_bucket(it._bucket);
+        clear_bucket(bucket);
+    }
+
+    template<typename Pred>
+    size_type erase_if(Pred pred)
+    {
+        auto old_size = size();
+        for (auto it = begin(), last = end(); it != last; ) {
+            if (pred(*it))
+                it = erase(it);
+            else
+                ++it;
+        }
+        return old_size - size();
+    }
+
+    static constexpr bool is_triviall_destructable()
+    {
+#if __cplusplus >= 201402L || _MSC_VER > 1600
+        return !(std::is_trivially_destructible<KeyT>::value && std::is_trivially_destructible<ValueT>::value);
+#else
+        return !(std::is_pod<KeyT>::value && std::is_pod<ValueT>::value);
+#endif
+    }
+
+    static constexpr bool is_copy_trivially()
+    {
+#if __cplusplus >= 201103L || _MSC_VER > 1600
+        return (std::is_trivially_copyable<KeyT>::value && std::is_trivially_copyable<ValueT>::value);
+#else
+        return (std::is_pod<KeyT>::value && std::is_pod<ValueT>::value);
+#endif
+    }
+
+    void clearkv() noexcept
+    {
+        if (is_triviall_destructable()) {
+            for (size_type bucket = 0; _num_filled > 0; ++bucket) {
+                if (!EMH_EMPTY(_pairs, bucket))
+                    clear_bucket(bucket, false);
+            }
+        }
+    }
+
+#if EMH_FIND_HIT
+    void reset_bucket(size_type bucket)
+    {
+        if constexpr (std::is_integral<KeyT>::value) {
+            auto& key = EMH_KEY(_pairs, bucket); key = KeyT(0-2);
+//            if (bucket != _zero_index)                 return;
+            while (key_to_bucket(key) == bucket) key += 1610612741;
+        }
+    }
+#endif
+
+    /// Remove all elements, keeping full capacity.
+    void clear() noexcept
+    {
+#if EMH_HIGH_LOAD
+        if (_ehead > 0)
+            clear_empty();
+        clearkv();
+#else
+        if (is_triviall_destructable())
+            clearkv();
+        else if (_num_filled)
+            memset((char*)_pairs, INACTIVE, sizeof(_pairs[0]) * _num_buckets);
+#endif
+#if EMH_FIND_HIT
+        if constexpr (std::is_integral<KeyT>::value)
+        reset_bucket(hash_main(0));
+#endif
+
+        _last = _num_filled = 0;
+    }
+
+    void shrink_to_fit(const float min_factor = EMH_DEFAULT_LOAD_FACTOR / 4)
+    {
+        if (load_factor() < min_factor) //safe guard
+            rehash(_num_filled + 1);
+    }
+
+    /// Make room for this many elements
+    bool reserve(uint64_t num_elems)
+    {
+#if EMH_HIGH_LOAD < 1000
+#if EMH_PACK_TAIL
+        const auto required_buckets = 1 + (size_type)(num_elems * _mlf >> 27);
+        if (EMH_LIKELY(required_buckets < _num_buckets))
+#else
+        const auto required_buckets = (size_type)(num_elems * _mlf >> 27);
+        if (EMH_LIKELY(required_buckets < _mask))
+#endif
+            return false;
+#else
+        const auto required_buckets = (size_type)(num_elems + num_elems * 1 / 9);
+        if (EMH_LIKELY(required_buckets < _mask))
+            return false;
+
+        else if (_num_buckets < 16 && _num_filled < _num_buckets)
+            return false;
+
+        else if (_num_buckets > EMH_HIGH_LOAD) {
+            if (_ehead == 0) {
+                set_empty();
+                return false;
+            } else if (/*_num_filled + 100 < _num_buckets && */EMH_BUCKET(_pairs, _ehead) != 0-_ehead) {
+                return false;
+            }
+        }
+#endif
+
+#if EMH_STATIS
+        if (_num_filled > EMH_STATIS) dump_statics();
+#endif
+
+        //assert(required_buckets < max_size());
+        rehash(required_buckets + 2);
+        return true;
+    }
+
+    void rehash(uint64_t required_buckets)
+    {
+        if (required_buckets < _num_filled)
+            return;
+
+#if EMH_SMALL_SIZE
+        uint64_t buckets = _num_filled > (1u << 16) ? (1u << 16) : EMH_SMALL_SIZE;
+        static_assert(EMH_SMALL_SIZE >= 2 && EMH_SMALL_SIZE < 1024);
+        static_assert((EMH_SMALL_SIZE & (EMH_SMALL_SIZE - 1)) == 0);
+#else
+        uint64_t buckets = _num_filled > (1u << 16) ? (1u << 16) : 2;
+#endif
+        while (buckets < required_buckets) { buckets *= 2; }
+
+        // no need alloc too many bucket for small key.
+        // if maybe fail set small load_factor and then call reserve() TODO:
+        if (sizeof(KeyT) < sizeof(size_type) && buckets >= (1ul << (2 * 8)))
+            buckets = 2ul << (sizeof(KeyT) * 8);
+
+        assert(buckets < max_size() && buckets > _num_filled);
+
+        auto num_buckets = (size_type)buckets;
+        auto old_num_filled  = _num_filled;
+        auto* old_pairs   = _pairs;
+        auto old_buckets = _num_buckets;
+
+#if EMH_REHASH_LOG
+        auto omask = _mask;
+        auto last = _last;
+        size_type collision = 0;
+#endif
+#if EMH_HIGH_LOAD
+        _ehead = 0;
+#endif
+
+        _num_filled  = 0;
+        _mask        = num_buckets - 1;
+        _last        = num_buckets / 4;
+
+#if EMH_PACK_TAIL > 1 && EMH_PACK_TAIL <= 100
+        _last = num_buckets;
+        num_buckets += num_buckets * EMH_PACK_TAIL / 100; //add more 5-10%
+#endif
+        _num_buckets = num_buckets;
+
+#if EMH_SMALL_SIZE
+        if (num_buckets <= EMH_SMALL_SIZE && old_pairs != (PairT*)_small)
+            _pairs = (PairT*)_small;
+        else
+#endif
+        _pairs = (PairT*)alloc_bucket(num_buckets);
+        memset((char*)_pairs, INACTIVE, sizeof(_pairs[0]) * num_buckets);
+        memset((char*)(_pairs + num_buckets), 0, sizeof(PairT) * 2);
+
+#if EMH_FIND_HIT
+        if constexpr (std::is_integral<KeyT>::value)
+        reset_bucket(hash_main(0));
+#endif
+
+        (void)old_buckets;
+        if (0 && is_copy_trivially() && old_num_filled && num_buckets >= 2 * old_buckets) {
+            memcpy((char*)_pairs, old_pairs, old_buckets * sizeof(PairT));
+            for (size_type src_bucket = 0; src_bucket < old_buckets; src_bucket++) {
+                if (EMH_EMPTY(_pairs, src_bucket))
+                    continue;
+
+                _num_filled ++;
+                auto nbucket = hash_main(src_bucket);
+                if (nbucket < old_buckets)
+                    continue;
+
+                auto bucket = move_unique_bucket(src_bucket, nbucket);
+                _pairs[bucket] = std::move(_pairs[src_bucket]);
+                erase_bucket(src_bucket);
+                if ((int)EMH_BUCKET(_pairs, src_bucket) >= 0)
+                    src_bucket --;
+
+                EMH_BUCKET(_pairs, bucket) = bucket;
+            }
+        } else {
+            //for (size_type src_bucket = 0; _num_filled < old_num_filled; src_bucket++) {
+            for (size_type src_bucket = old_buckets - 1; _num_filled < old_num_filled; src_bucket--) {
+                if (EMH_EMPTY(old_pairs, src_bucket))
+                    continue;
+#if EMH_REHASH_LOG
+                else if (src_bucket != EMH_BUCKET(old_pairs, src_bucket))
+                    collision ++;
+#endif
+
+                const auto& key = EMH_KEY(old_pairs, src_bucket);
+                const auto bucket = find_unique_bucket(key);
+                new(_pairs + bucket) PairT(std::move(old_pairs[src_bucket])); _num_filled ++;
+                EMH_BUCKET(_pairs, bucket) = bucket;
+                if (is_triviall_destructable())
+                    old_pairs[src_bucket].~PairT();
+            }
+        }
+
+#if EMH_REHASH_LOG
+        if (_num_filled > EMH_REHASH_LOG) {
+            auto mbucket = _num_filled - collision;
+            char buff[255] = {0};
+            sprintf(buff, "    _num_filled/aver_size/K.V/pack/collision|last = %u/%.2lf/%s.%s/%zd|%.2lf%%,%.2lf%%",
+                    _num_filled, double (_num_filled) / mbucket, typeid(KeyT).name(), typeid(ValueT).name(),
+                    sizeof(_pairs[0]), collision * 100.0 / _num_filled, last * 100.0 / omask);
+#ifdef EMH_LOG
+            static uint32_t ihashs = 0; EMH_LOG() << "hash_nums = " << ihashs ++ << "|" <<__FUNCTION__ << "|" << buff << endl;
+#else
+            puts(buff);
+#endif
+        }
+#endif
+#if EMH_SMALL_SIZE
+        if (old_pairs != (PairT*)_small)
+#endif
+        free(old_pairs);
+        assert(old_num_filled == _num_filled);
+    }
+
+private:
+
+    static PairT* alloc_bucket(size_type num_buckets)
+    {
+        //TODO: call realloc
+#ifdef EMH_ALLOC
+        auto* new_pairs = (PairT*)aligned_alloc(EMH_MALIGN, (2 + num_buckets) * sizeof(PairT));
+#else
+        auto* new_pairs = (PairT*)malloc((2 + num_buckets) * sizeof(PairT));
+#endif
+        return new_pairs;
+    }
+
+#if EMH_HIGH_LOAD
+    void set_empty()
+    {
+        auto prev = 0;
+        for (int32_t bucket = 1; bucket < _num_buckets; ++bucket) {
+            if (EMH_EMPTY(_pairs, bucket)) {
+                if (prev != 0) {
+                    EMH_PREVET(_pairs, bucket) = prev;
+                    EMH_BUCKET(_pairs, prev) = -bucket;
+                }
+                else
+                    _ehead = bucket;
+                prev = bucket;
+            }
+        }
+
+        EMH_PREVET(_pairs, _ehead) = prev;
+        EMH_BUCKET(_pairs, prev) = 0-_ehead;
+        _ehead = 0-EMH_BUCKET(_pairs, _ehead);
+    }
+
+    void clear_empty()
+    {
+        auto prev = EMH_PREVET(_pairs, _ehead);
+        while (prev != _ehead) {
+            EMH_BUCKET(_pairs, prev) = INACTIVE;
+            prev = EMH_PREVET(_pairs, prev);
+        }
+        EMH_BUCKET(_pairs, _ehead) = INACTIVE;
+        _ehead = 0;
+    }
+
+    //prev-ehead->next
+    size_type pop_empty(const size_type bucket)
+    {
+        const auto prev_bucket = EMH_PREVET(_pairs, bucket);
+        int next_bucket = (int)(0-EMH_BUCKET(_pairs, bucket));
+//        assert(next_bucket > 0 && _ehead > 0);
+//        assert(next_bucket <= _mask && prev_bucket <= _mask);
+
+        EMH_PREVET(_pairs, next_bucket) = prev_bucket;
+        EMH_BUCKET(_pairs, prev_bucket) = -next_bucket;
+
+        _ehead = next_bucket;
+        return bucket;
+    }
+
+    //ehead->bucket->next
+    void push_empty(const int32_t bucket)
+    {
+        const int next_bucket = 0-EMH_BUCKET(_pairs, _ehead);
+        assert(next_bucket > 0);
+
+        EMH_PREVET(_pairs, bucket) = _ehead;
+        EMH_BUCKET(_pairs, bucket) = -next_bucket;
+
+        EMH_PREVET(_pairs, next_bucket) = bucket;
+        EMH_BUCKET(_pairs, _ehead) = -bucket;
+        //        _ehead = bucket;
+    }
+#endif
+
+    // Can we fit another element?
+    inline bool check_expand_need()
+    {
+        return reserve(_num_filled);
+    }
+
+    void clear_bucket(size_type bucket, bool bclear = true) noexcept
+    {
+        if (is_triviall_destructable()) {
+            //EMH_BUCKET(_pairs, bucket) = INACTIVE; //loop call in destructor
+            _pairs[bucket].~PairT();
+        }
+        EMH_BUCKET(_pairs, bucket) = INACTIVE; //the status is reset by destructor by some compiler
+        _num_filled--;
+
+#if EMH_HIGH_LOAD
+        if (_ehead && bclear) {
+            if (10 * _num_filled < 8 * _num_buckets)
+                clear_empty();
+            else if (bucket)
+                push_empty(bucket);
+        }
+#endif
+#if EMH_FIND_HIT
+        reset_bucket(bucket);
+#endif
+    }
+
+    template <typename K=KeyT>
+    size_type erase_key(const K& key) noexcept
+    {
+        const auto bucket = key_to_bucket(key);
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if (EMH_UNLIKELY((int)next_bucket < 0))
+            return INACTIVE;
+
+        const auto equalk = _eq(key, EMH_KEY(_pairs, bucket));
+#if 1
+        if (next_bucket == bucket)
+            return equalk ? bucket : INACTIVE;
+        else if (equalk) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+            EMH_BUCKET(_pairs, bucket) = (nbucket == next_bucket) ? bucket : nbucket;
+            return next_bucket;
+        }/* else if (EMH_UNLIKELY(bucket != hash_main(bucket)))
+            return INACTIVE;
+        */
+#else
+        if (equalk) {
+            if (next_bucket != bucket) {
+                const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+                EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+                EMH_BUCKET(_pairs, bucket) = (nbucket == next_bucket) ? bucket : nbucket;
+            }
+            return next_bucket;
+        } else if (next_bucket == bucket)
+            return INACTIVE;
+#endif
+
+        auto prev_bucket = bucket;
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (_eq(key, EMH_KEY(_pairs, next_bucket))) {
+#ifndef EMH_RNEXT
+                EMH_BUCKET(_pairs, prev_bucket) = (nbucket == next_bucket) ? prev_bucket : nbucket;
+                return next_bucket;
+#else
+                if (nbucket == next_bucket) {
+                    EMH_BUCKET(_pairs, prev_bucket) = prev_bucket;
+                    return next_bucket;
+                }
+
+                const auto last = EMH_BUCKET(_pairs, nbucket);
+                EMH_PKV(_pairs, next_bucket) = std::move(EMH_PKV(_pairs, nbucket));
+                EMH_BUCKET(_pairs, next_bucket) = (nbucket == last) ? next_bucket : last;
+                return nbucket;
+#endif
+            }
+
+            if (nbucket == next_bucket)
+                break;
+            prev_bucket = next_bucket;
+            next_bucket = nbucket;
+        }
+
+        return INACTIVE;
+    }
+
+    size_type erase_bucket(const size_type bucket) noexcept
+    {
+#if 1
+        const auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if (EMH_LIKELY(next_bucket == bucket)) {
+            const auto main_bucket = hash_main(bucket);
+            if (main_bucket != bucket) {
+                const auto prev_bucket = find_prev_bucket(main_bucket, bucket);
+                EMH_BUCKET(_pairs, prev_bucket) = prev_bucket;
+            }
+        } else {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+            EMH_BUCKET(_pairs, bucket) = (nbucket == next_bucket) ? bucket : nbucket;
+        }
+
+        return next_bucket;
+#else
+        const auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        const auto main_bucket = hash_main(bucket);
+        if (bucket == main_bucket) {
+            if (bucket != next_bucket) {
+                const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+                EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+                EMH_BUCKET(_pairs, bucket) = (nbucket == next_bucket) ? bucket : nbucket;
+            }
+            return next_bucket;
+        }
+
+        const auto prev_bucket = find_prev_bucket(main_bucket, bucket);
+        EMH_BUCKET(_pairs, prev_bucket) = (bucket == next_bucket) ? prev_bucket : next_bucket;
+        return bucket;
+#endif
+    }
+
+    template<typename K=KeyT>
+    inline size_type find_filled_bucket(const K& key) const noexcept
+    {
+        return find_hash_bucket(key, hash_key(key));
+    }
+
+    // Find the bucket with this key, or return bucket size
+    template<typename K=KeyT>
+    size_type find_hash_bucket(const K& key, size_type key_hash) const noexcept
+    {
+        const auto bucket = key_hash & _mask;
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+
+#if EMH_FIND_HIT == 0
+        if ((int)next_bucket < 0)
+            return _num_buckets;
+        else if (_eq(key, EMH_KEY(_pairs, bucket)))
+            return bucket;
+#else
+        if constexpr (std::is_integral<KeyT>::value) {
+            if (_eq(key, EMH_KEY(_pairs, bucket)))
+                return bucket;
+            else if ((int)next_bucket < 0)
+                return _num_buckets;
+        } else {
+            if ((int)next_bucket < 0)
+                return _num_buckets;
+            else if (_eq(key, EMH_KEY(_pairs, bucket)))
+                return bucket;
+        }
+#endif
+
+        if (next_bucket == bucket)
+            return _num_buckets;
+//        else if (key_to_bucket(EMH_KEY(_pairs, bucket)) != bucket)
+//            return _num_buckets;
+
+        while (true) {
+            if (_eq(key, EMH_KEY(_pairs, next_bucket)))
+                return next_bucket;
+
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                return _num_buckets;
+            next_bucket = nbucket;
+        }
+
+        return 0;
+    }
+
+    //kick out bucket and find empty to occpuy
+    //it will break the orgin link and relnik again.
+    //before: main_bucket-->prev_bucket --> kbucket   --> next_bucket
+    //after : main_bucket-->prev_bucket --> (removed)--> new_bucket(kbucket)--> next_bucket
+    size_type kickout_bucket(const size_type kmain, const size_type kbucket) noexcept
+    {
+        const auto next_bucket = EMH_BUCKET(_pairs, kbucket);
+        const auto new_bucket  = find_empty_bucket(next_bucket, 2);
+        const auto prev_bucket = find_prev_bucket(kmain, kbucket);
+        EMH_BUCKET(_pairs, prev_bucket) = new_bucket;
+        new(_pairs + new_bucket) PairT(std::move(_pairs[kbucket]));
+        if (next_bucket == kbucket)
+            EMH_BUCKET(_pairs, new_bucket) = new_bucket;
+
+        clear_bucket(kbucket, false);
+        _num_filled ++;
+        return kbucket;
+    }
+
+/*
+** inserts a new key into a hash table; first, check whether key's main
+** bucket/position is free. If not, check whether colliding node/bucket is in its main
+** position or not: if it is not, move colliding bucket to an empty place and
+** put new key in its main position; otherwise (colliding bucket is in its main
+** position), new key goes to an empty position.
+*/
+    template<typename K=KeyT>
+    size_type find_or_allocate(const K& key) noexcept
+    {
+        const auto bucket = key_to_bucket(key);
+        const auto& bucket_key = EMH_KEY(_pairs, bucket);
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0) {
+#if EMH_HIGH_LOAD
+            if (next_bucket != INACTIVE)
+                pop_empty(bucket);
+#endif
+            return bucket;
+        } else if (_eq(key, bucket_key))
+            return bucket;
+
+        //check current bucket_key is in main bucket or not
+        const auto kmain = key_to_bucket(bucket_key);
+        if (kmain != bucket)
+            return kickout_bucket(kmain, bucket);
+        else if (next_bucket == bucket)
+            return EMH_BUCKET(_pairs, next_bucket) = find_empty_bucket(next_bucket, 1);
+
+        int csize = 0;
+#if EMH_LRU_SET
+        auto prev_bucket = bucket;
+#endif
+        //find next linked bucket and check key
+        while (true) {
+            if (_eq(key, EMH_KEY(_pairs, next_bucket))) {
+#if EMH_LRU_SET
+                EMH_PKV(_pairs, next_bucket).swap(EMH_PKV(_pairs, prev_bucket));
+                return prev_bucket;
+#else
+                return next_bucket;
+#endif
+            }
+
+#if EMH_LRU_SET
+            prev_bucket = next_bucket;
+#endif
+
+            csize += 1;
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                break;
+            next_bucket = nbucket;
+        }
+
+        //find a new empty and link it to tail
+        const auto new_bucket = find_empty_bucket(next_bucket, csize);
+        return EMH_BUCKET(_pairs, next_bucket) = new_bucket;
+    }
+
+    template<typename K=KeyT>
+    size_type find_unique_bucket(const K& key) noexcept
+    {
+        const auto bucket = key_to_bucket(key);
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0) {
+#if EMH_HIGH_LOAD
+            if (next_bucket != INACTIVE)
+                pop_empty(bucket);
+#endif
+            return bucket;
+        }
+
+        //check current bucket_key is in main bucket or not
+        const auto kmain = hash_main(bucket);
+        if (EMH_UNLIKELY(kmain != bucket))
+            return kickout_bucket(kmain, bucket);
+        else if (EMH_UNLIKELY(next_bucket != bucket))
+            next_bucket = find_last_bucket(next_bucket);
+
+        //find a new empty and link it to tail
+        return EMH_BUCKET(_pairs, next_bucket) = find_unique_empty(next_bucket);
+    }
+
+    size_type move_unique_bucket(size_type old_bucket, size_type bucket) noexcept
+    {
+        (void)old_bucket;
+        auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return bucket;
+
+        next_bucket = find_last_bucket(next_bucket);
+
+        //find a new empty and link it to tail
+        return EMH_BUCKET(_pairs, next_bucket) = find_unique_empty(next_bucket);
+    }
+
+/***
+  Different probing techniques usually provide a trade-off between memory locality and avoidance of clustering.
+Since Robin Hood hashing is relatively resilient to clustering (both primary and secondary), linear probing the most cache-friendly alternative is typically used.
+
+    It's the core algorithm of this hash map with highly optimization/benchmark.
+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
+one-way search strategy.
+
+1. linear or quadratic probing a few cache line for less cache miss from input slot "bucket_from".
+2. the first  search  slot from member variant "_last", init with 0
+3. the second search slot from calculated pos "(_num_filled + _last) & _mask", it's like a rand value
+*/
+    // key is not in this map. Find a place to put it.
+    size_type find_empty_bucket(const size_type bucket_from, uint32_t csize) noexcept
+    {
+#if EMH_HIGH_LOAD
+        if (_ehead)
+            return pop_empty(_ehead);
+#endif
+
+        auto bucket = bucket_from;
+        if (EMH_EMPTY(_pairs, ++bucket) || EMH_EMPTY(_pairs, ++bucket))
+            return bucket;
+
+#ifndef _MSC_VER
+        //__builtin_prefetch(static_cast<const void*>(_pairs + bucket + 1), 0, 1);
+#endif
+        constexpr auto linear_probe_length = 5;//2-3 cache line miss
+        for (size_type step = 2, slot = bucket + 1 + csize / 2; ; slot += step++) {
+            if (step < linear_probe_length) {
+                auto bucket1 = slot & _mask;
+                if (EMH_EMPTY(_pairs, bucket1) || EMH_EMPTY(_pairs, ++bucket1))
+                    return bucket1;
+            } else { //if (step++ > 5) {
+                if (EMH_EMPTY(_pairs, ++_last))// || EMH_EMPTY(_pairs, _last++))
+                    return _last;
+
+                _last &= _mask;
+#if EMH_PACK_TAIL
+                auto tail = _num_buckets - _last;
+                if (EMH_EMPTY(_pairs, tail) || EMH_EMPTY(_pairs, ++tail))
+                    return tail;
+#else
+                auto medium = (_num_buckets / 2 + _last) & _mask;
+                if (EMH_EMPTY(_pairs, medium))// && EMH_EMPTY(_pairs, ++medium))
+                    return _last = medium;
+#endif
+            }
+        }
+
+        return 0;
+    }
+
+    size_type find_unique_empty(const size_type bucket_from) noexcept
+    {
+        auto bucket = bucket_from;
+        if (EMH_EMPTY(_pairs, ++bucket) || EMH_EMPTY(_pairs, ++bucket))
+            return bucket;
+
+        for (size_type slot = bucket + 2, step = 2; ; slot += ++step) {
+            auto nbucket = slot & _mask;
+            if (EMH_EMPTY(_pairs, nbucket) || EMH_EMPTY(_pairs, ++nbucket))
+                return nbucket;
+        }
+
+        return 0;
+    }
+
+    size_type find_last_bucket(size_type main_bucket) const noexcept
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, main_bucket);
+        if (next_bucket == main_bucket)
+            return main_bucket;
+
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                return next_bucket;
+            next_bucket = nbucket;
+        }
+    }
+
+    size_type find_prev_bucket(const size_type main_bucket, const size_type bucket) const noexcept
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, main_bucket);
+        if (next_bucket == bucket)
+            return main_bucket;
+
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == bucket)
+                return next_bucket;
+            next_bucket = nbucket;
+        }
+    }
+
+    template<typename K=KeyT>
+    inline size_type key_to_bucket(const K& key) const noexcept
+    {
+        return (size_type)hash_key(key) & _mask;
+    }
+
+    inline size_type hash_main(const size_type bucket) const noexcept
+    {
+        return (size_type)hash_key(EMH_KEY(_pairs, bucket)) & _mask;
+    }
+
+#if EMH_INT_HASH
+    static constexpr uint64_t KC = UINT64_C(11400714819323198485);
+    inline uint64_t hash64(uint64_t key)
+    {
+#if __SIZEOF_INT128__ && EMH_INT_HASH == 1
+        __uint128_t r = key; r *= KC;
+        return (uint64_t)(r >> 64) + (uint64_t)r;
+#elif EMH_INT_HASH == 2
+        //MurmurHash3Mixer
+        uint64_t h = key;
+        h ^= h >> 33;
+        h *= 0xff51afd7ed558ccd;
+        h ^= h >> 33;
+        h *= 0xc4ceb9fe1a85ec53;
+        h ^= h >> 33;
+        return h;
+#elif _WIN64 && EMH_INT_HASH == 1
+        uint64_t high;
+        return _umul128(key, KC, &high) + high;
+#elif EMH_INT_HASH == 3
+        auto ror  = (key >> 32) | (key << 32);
+        auto low  = key * 0xA24BAED4963EE407ull;
+        auto high = ror * 0x9FB21C651E98DF25ull;
+        auto mix  = low + high;
+        return mix;
+#elif EMH_INT_HASH == 1
+        uint64_t r = key * UINT64_C(0xca4bcaa75ec3f625);
+        return (r >> 32) + r;
+#elif EMH_WYHASH64
+        return wyhash64(key, KC);
+#else
+        uint64_t x = key;
+        x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
+        x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
+        x = x ^ (x >> 31);
+        return x;
+#endif
+    }
+#endif
+
+    template<typename UType, typename std::enable_if<std::is_integral<UType>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType key) const
+    {
+#if EMH_INT_HASH
+        return hash64(key);
+#elif EMH_IDENTITY_HASH
+        return key + (key >> 24);
+#else
+        return (size_type)_hasher(key);
+#endif
+    }
+
+    template<typename UType, typename std::enable_if<std::is_same<UType, std::string>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType& key) const
+    {
+#if EMH_WY_HASH
+        return (size_type)wyhash(key.data(), key.size(), 0);
+#else
+        return (size_type)_hasher(key);
+#endif
+    }
+
+    template<typename UType, typename std::enable_if<!std::is_integral<UType>::value && !std::is_same<UType, std::string>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType& key) const
+    {
+        return (size_type)_hasher(key);
+    }
+
+private:
+    PairT*    _pairs;
+#if EMH_SMALL_SIZE
+    char      _small[(EMH_SMALL_SIZE + 2) * sizeof(PairT)];
+#endif
+
+    HashT     _hasher;
+    EqT       _eq;
+    uint32_t  _mlf;
+    size_type _mask;
+    size_type _num_buckets;
+    size_type _num_filled;
+    size_type _last;
+#if EMH_HIGH_LOAD
+    size_type _ehead;
+#endif
+};
+} // namespace emhash
+
+//#define ehmap emhash5::HashMap
+#if __cplusplus > 199711
+//template <class Key, class Val> using emhash5 = ehmap<Key, Val, std::hash<Key>>;
+#endif

+ 1817 - 0
src/hash_table6.hpp

@@ -0,0 +1,1817 @@
+// emhash6::HashMap for C++11/14/17
+// version 1.7.1
+// https://github.com/ktprime/ktprime/blob/master/hash_table6.hpp
+//
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2019-2023 Huang Yuanbing & bailuzhou AT 163.com
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE
+
+#pragma once
+
+#include <cstring>
+#include <string>
+#include <cmath>
+#include <cstdlib>
+#include <type_traits>
+#include <cassert>
+#include <utility>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <algorithm>
+
+#if EMH_WY_HASH
+    #include "wyhash.h"
+#endif
+
+#ifdef EMH_KEY
+    #undef  EMH_KEY
+    #undef  EMH_VAL
+    #undef  EMH_PKV
+    #undef  EMH_NEW
+    #undef  EMH_SET
+    #undef  EMH_BUCKET
+    #undef  EMH_EMPTY
+#endif
+
+// likely/unlikely
+#if (__GNUC__ >= 4 || __clang__)
+#    define EMH_LIKELY(condition)   __builtin_expect(condition, 1)
+#    define EMH_UNLIKELY(condition) __builtin_expect(condition, 0)
+#else
+#    define EMH_LIKELY(condition)   condition
+#    define EMH_UNLIKELY(condition) condition
+#endif
+
+#ifndef EMH_BUCKET_INDEX
+    #define EMH_BUCKET_INDEX 1
+#endif
+
+#if EMH_BUCKET_INDEX == 0
+    #define EMH_KEY(p,n)     p[n].second.first
+    #define EMH_VAL(p,n)     p[n].second.second
+    #define EMH_BUCKET(p,n)  p[n].first / 2
+    #define EMH_ADDR(p,n)    p[n].first
+    #define EMH_EMPTY(p,n)   ((int)p[n].first < 0)
+    #define EMH_PKV(p,n)     p[n].second
+    #define EMH_NEW(key, val, bucket, next) new(_pairs + bucket) PairT(next, value_type(key, val)), _num_filled ++; EMH_SET(bucket)
+#elif EMH_BUCKET_INDEX == 2
+    #define EMH_KEY(p,n)     p[n].first.first
+    #define EMH_VAL(p,n)     p[n].first.second
+    #define EMH_BUCKET(p,n)  p[n].second / 2
+    #define EMH_ADDR(p,n)    p[n].second
+    #define EMH_EMPTY(p,n)   ((int)p[n].second < 0)
+    #define EMH_PKV(p,n)     p[n].first
+    #define EMH_NEW(key, val, bucket, next) new(_pairs + bucket) PairT(value_type(key, val), next), _num_filled ++; EMH_SET(bucket)
+#else
+    #define EMH_KEY(p,n)     p[n].first
+    #define EMH_VAL(p,n)     p[n].second
+    #define EMH_BUCKET(p,n)  p[n].bucket / 2
+    #define EMH_ADDR(p,n)    p[n].bucket
+    #define EMH_EMPTY(p,n)   (0 > (int)p[n].bucket)
+    #define EMH_PKV(p,n)     p[n]
+    #define EMH_NEW(key, val, bucket, next) new(_pairs + bucket) PairT(key, val, next), _num_filled ++; EMH_SET(bucket)
+#endif
+
+#define EMH_MASK(bucket) 1 << (bucket % MASK_BIT)
+#define EMH_SET(bucket)  _bitmask[bucket / MASK_BIT] &= ~(EMH_MASK(bucket))
+#define EMH_CLS(bucket)  _bitmask[bucket / MASK_BIT] |= EMH_MASK(bucket)
+//#define EMH_EMPTY(bitmask, bucket)     (_bitmask[bucket / MASK_BIT] & (EMH_MASK(bucket))) != 0
+
+#if _WIN32
+    #include <intrin.h>
+#if _WIN64
+    #pragma intrinsic(_umul128)
+#endif
+#endif
+
+namespace emhash6 {
+
+#ifdef EMH_SIZE_TYPE_16BIT
+    typedef uint16_t size_type;
+    static constexpr size_type INACTIVE = 0xFFFF;
+#elif EMH_SIZE_TYPE_64BIT
+    typedef uint64_t size_type;
+    static constexpr size_type INACTIVE = 0 - 0x1ull;
+#else
+    typedef uint32_t size_type;
+    static constexpr size_type INACTIVE = 0 - 0x1u;
+#endif
+
+static_assert((int)INACTIVE < 0, "INACTIVE must be even and < 0(to int)");
+
+//https://gist.github.com/jtbr/1896790eb6ad50506d5f042991906c30
+static size_type CTZ(size_t n)
+{
+#if defined(__x86_64__) || defined(_WIN32) || (__BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+
+#elif __BIG_ENDIAN__ || (__BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+    n = __builtin_bswap64(n);
+#else
+    static uint32_t endianness = 0x12345678;
+    const auto is_big = *(const char *)&endianness == 0x12;
+    if (is_big)
+    n = __builtin_bswap64(n);
+#endif
+
+#if _WIN32
+    unsigned long index;
+    #if defined(_WIN64)
+    _BitScanForward64(&index, n);
+    #else
+    _BitScanForward(&index, n);
+    #endif
+#elif defined (__LP64__) || (SIZE_MAX == UINT64_MAX) || defined (__x86_64__)
+    auto index = __builtin_ctzll(n);
+#elif 1
+    auto index = __builtin_ctzl(n);
+#else
+    #if defined (__LP64__) || (SIZE_MAX == UINT64_MAX) || defined (__x86_64__)
+    size_type index;
+    __asm__("bsfq %1, %0\n" : "=r" (index) : "rm" (n) : "cc");
+    #else
+    size_type index;
+    __asm__("bsf %1, %0\n" : "=r" (index) : "rm" (n) : "cc");
+    #endif
+#endif
+
+    return (size_type)index;
+}
+
+template <typename First, typename Second>
+struct entry {
+    using first_type =  First;
+    using second_type = Second;
+    entry(const First& key, const Second& val, size_type ibucket) :second(val), first(key) { bucket = ibucket; }
+    entry(First&& key, Second&& val, size_type ibucket) :second(std::move(val)), first(std::move(key)) { bucket = ibucket; }
+
+    entry(const std::pair<First, Second>& pair) :second(pair.second), first(pair.first) { bucket = INACTIVE; }
+    entry(std::pair<First, Second>&& pair) :second(std::move(pair.second)), first(std::move(pair.first)) { bucket = INACTIVE; }
+
+    entry(const entry& pairT) :second(pairT.second), first(pairT.first) { bucket = pairT.bucket; }
+    entry(entry&& pairT) noexcept :second(std::move(pairT.second)), first(std::move(pairT.first)) { bucket = pairT.bucket; }
+
+    template<typename K, typename V>
+    entry(K&& key, V&& val, size_type ibucket)
+        :second(std::forward<V>(val)), first(std::forward<K>(key))
+    {
+        bucket = ibucket;
+    }
+
+    entry& operator = (entry&& pairT) noexcept
+    {
+        second = std::move(pairT.second);
+        bucket = pairT.bucket;
+        first = std::move(pairT.first);
+        return *this;
+    }
+
+    entry& operator = (const entry& o)
+    {
+        second = o.second;
+        bucket = o.bucket;
+        first  = o.first;
+        return *this;
+    }
+
+    bool operator == (const std::pair<First, Second>& p) const
+    {
+        return first == p.first && second == p.second;
+    }
+
+    bool operator == (const entry<First, Second>& p) const
+    {
+        return first == p.first && second == p.second;
+    }
+
+    void swap(entry<First, Second>& o)
+    {
+        std::swap(second, o.second);
+        std::swap(first, o.first);
+    }
+
+#if EMH_ORDER_KV || EMH_SIZE_TYPE_64BIT
+    First first;
+    size_type bucket;
+    Second second;
+#else
+    Second second;
+    size_type bucket;
+    First first;
+#endif
+};
+
+/// A cache-friendly hash table with open addressing, linear/qua probing and power-of-two capacity
+template <typename KeyT, typename ValueT, typename HashT = std::hash<KeyT>, typename EqT = std::equal_to<KeyT>>
+class HashMap
+{
+#ifndef EMH_DEFAULT_LOAD_FACTOR
+    constexpr static float EMH_DEFAULT_LOAD_FACTOR = 0.80f;
+    constexpr static float EMH_MIN_LOAD_FACTOR     = 0.25f; //< 0.5
+#endif
+
+public:
+    typedef HashMap<KeyT, ValueT, HashT, EqT> htype;
+    typedef std::pair<KeyT, ValueT>           value_type;
+
+#if EMH_BUCKET_INDEX == 0
+    typedef value_type                        value_pair;
+    typedef std::pair<size_type, value_type>  PairT;
+#elif EMH_BUCKET_INDEX == 2
+    typedef value_type                        value_pair;
+    typedef std::pair<value_type, size_type>  PairT;
+#else
+    typedef entry<KeyT, ValueT>               value_pair;
+    typedef entry<KeyT, ValueT>               PairT;
+#endif
+
+    typedef KeyT   key_type;
+    typedef ValueT val_type;
+    typedef ValueT mapped_type;
+    typedef HashT  hasher;
+    typedef EqT    key_equal;
+    typedef PairT&       reference;
+    typedef const PairT& const_reference;
+
+    class const_iterator;
+    class iterator
+    {
+    public:
+        typedef std::forward_iterator_tag iterator_category;
+        typedef std::ptrdiff_t            difference_type;
+        typedef value_pair                value_type;
+
+        typedef value_pair*               pointer;
+        typedef value_pair&               reference;
+
+        iterator() = default;
+        iterator(const const_iterator& it) : _map(it._map), _bucket(it._bucket), _from(it._from), _bmask(it._bmask) { }
+        iterator(const htype* hash_map, size_type bucket, bool) : _map(hash_map), _bucket(bucket) { init(); }
+#if EMH_ITER_SAFE
+        iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { init(); }
+#else
+        iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { _bmask = _from = 0; }
+#endif
+
+        void init()
+        {
+            _from = (_bucket / SIZE_BIT) * SIZE_BIT;
+            if (_bucket < _map->bucket_count()) {
+                _bmask = *(size_t*)((size_t*)_map->_bitmask + _from / SIZE_BIT);
+                _bmask |= (1ull << _bucket % SIZE_BIT) - 1;
+                _bmask = ~_bmask;
+            } else {
+                _bmask = 0;
+            }
+        }
+
+        size_type bucket() const
+        {
+            return _bucket;
+        }
+
+        void clear(size_type bucket)
+        {
+            if (_bucket / SIZE_BIT == bucket / SIZE_BIT)
+                _bmask &= ~(1ull << (bucket % SIZE_BIT));
+        }
+
+        iterator& next()
+        {
+            goto_next_element();
+            return *this;
+        }
+
+        iterator& operator++()
+        {
+            _bmask &= _bmask - 1;
+            goto_next_element();
+            return *this;
+        }
+
+        iterator operator++(int)
+        {
+            iterator old = *this;
+            _bmask &= _bmask - 1;
+            goto_next_element();
+            return old;
+        }
+
+        reference operator*() const
+        {
+            return _map->EMH_PKV(_pairs, _bucket);
+        }
+
+        pointer operator->() const
+        {
+            return &(_map->EMH_PKV(_pairs, _bucket));
+        }
+
+        bool operator==(const iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const iterator& rhs) const { return _bucket != rhs._bucket; }
+        bool operator==(const const_iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const const_iterator& rhs) const { return _bucket != rhs._bucket; }
+
+    private:
+        void goto_next_element()
+        {
+            if (_bmask != 0) {
+                _bucket = _from + CTZ(_bmask);
+                return;
+            }
+
+            do {
+                _bmask = ~*(size_t*)((size_t*)_map->_bitmask + (_from += SIZE_BIT) / SIZE_BIT);
+            } while (_bmask == 0);
+
+            _bucket = _from + CTZ(_bmask);
+        }
+
+    public:
+        const htype* _map;
+        size_type _bucket;
+        size_type _from;
+        size_t    _bmask;
+    };
+
+    class const_iterator
+    {
+    public:
+        typedef std::forward_iterator_tag iterator_category;
+        typedef std::ptrdiff_t            difference_type;
+        typedef value_pair                value_type;
+
+        typedef const value_pair*          pointer;
+        typedef const value_pair&          reference;
+
+        const_iterator(const iterator& it) : _map(it._map), _bucket(it._bucket), _from(it._from), _bmask(it._bmask) { }
+        const_iterator(const htype* hash_map, size_type bucket, bool) : _map(hash_map), _bucket(bucket) { init(); }
+#if EMH_ITER_SAFE
+        const_iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { init(); }
+#else
+        const_iterator(const htype* hash_map, size_type bucket) : _map(hash_map), _bucket(bucket) { _bmask = _from = 0; }
+#endif
+
+        void init()
+        {
+            _from = (_bucket / SIZE_BIT) * SIZE_BIT;
+            if (_bucket < _map->bucket_count()) {
+                _bmask = *(size_t*)((size_t*)_map->_bitmask + _from / SIZE_BIT);
+                _bmask |= (1ull << _bucket % SIZE_BIT) - 1;
+                _bmask = ~_bmask;
+            } else {
+                _bmask = 0;
+            }
+        }
+
+        size_type bucket() const
+        {
+            return _bucket;
+        }
+
+        const_iterator& operator++()
+        {
+            goto_next_element();
+            return *this;
+        }
+
+        const_iterator operator++(int)
+        {
+            const_iterator old(*this);
+            goto_next_element();
+            return old;
+        }
+
+        reference operator*() const
+        {
+            return _map->EMH_PKV(_pairs, _bucket);
+        }
+
+        pointer operator->() const
+        {
+            return &(_map->EMH_PKV(_pairs, _bucket));
+        }
+
+        bool operator==(const const_iterator& rhs) const { return _bucket == rhs._bucket; }
+        bool operator!=(const const_iterator& rhs) const { return _bucket != rhs._bucket; }
+
+    private:
+        void goto_next_element()
+        {
+            _bmask &= _bmask - 1;
+            if (_bmask != 0) {
+                _bucket = _from + CTZ(_bmask);
+                return;
+            }
+
+            do {
+                _bmask = ~*(size_t*)((size_t*)_map->_bitmask + (_from += SIZE_BIT) / SIZE_BIT);
+            } while (_bmask == 0);
+
+            _bucket = _from + CTZ(_bmask);
+        }
+
+    public:
+        const htype* _map;
+        size_type _bucket;
+        size_type _from;
+        size_t    _bmask;
+    };
+
+    void init(size_type bucket, float lf = EMH_DEFAULT_LOAD_FACTOR)
+    {
+#if EMH_SAFE_HASH
+        _num_main = _hash_inter = 0;
+#endif
+        _mask = 0;
+        _pairs = nullptr;
+        _bitmask = nullptr;
+        _num_filled = 0;
+        max_load_factor(lf);
+        rehash(bucket);
+    }
+
+    HashMap(size_type bucket = 4, float lf = EMH_DEFAULT_LOAD_FACTOR)
+    {
+        init(bucket, lf);
+    }
+
+    size_t AllocSize(uint64_t num_buckets) const
+    {
+        return (num_buckets + PACK_SIZE) * sizeof(PairT) + (num_buckets + 7) / 8 + BIT_PACK;
+    }
+
+    HashMap(const HashMap& rhs)
+    {
+        if (rhs.load_factor() > EMH_MIN_LOAD_FACTOR) {
+            _pairs = (PairT*)malloc(AllocSize(rhs._mask + 1));
+            clone(rhs);
+        } else {
+            init(rhs._num_filled + 2, EMH_DEFAULT_LOAD_FACTOR);
+            for (auto it = rhs.begin(); it != rhs.end(); ++it)
+                insert_unique(it->first, it->second);
+        }
+    }
+
+    HashMap(HashMap&& rhs) noexcept
+    {
+#ifndef EMH_ZERO_MOVE
+        init(4);
+#else
+        _mask = _num_filled = 0;
+        _pairs = nullptr;
+#endif
+        swap(rhs);
+    }
+
+    HashMap(std::initializer_list<value_type> ilist)
+    {
+        init((size_type)ilist.size());
+        for (auto it = ilist.begin(); it != ilist.end(); ++it)
+            do_insert(*it);
+    }
+
+    template<class InputIt>
+    HashMap(InputIt first, InputIt last, size_type bucket_count=4)
+    {
+        init(std::distance(first, last) + bucket_count);
+        for (; first != last; ++first)
+            emplace(*first);
+    }
+
+    HashMap& operator= (const HashMap& rhs) noexcept
+    {
+        if (this == &rhs)
+            return *this;
+
+        if (rhs.load_factor() < EMH_MIN_LOAD_FACTOR) {
+            clear(); free(_pairs); _pairs = nullptr;
+            rehash(rhs._num_filled + 2);
+            for (auto it = rhs.begin(); it != rhs.end(); ++it)
+                insert_unique(it->first, it->second);
+            return *this;
+        }
+
+        if (is_triviall_destructable())
+            clearkv();
+
+        if (_mask != rhs._mask) {
+            free(_pairs);
+            _pairs = (PairT*)malloc(AllocSize(1 + rhs._mask));
+        }
+
+        clone(rhs);
+        return *this;
+    }
+
+    HashMap& operator= (HashMap&& rhs) noexcept
+    {
+        if (this != &rhs) {
+            swap(rhs);
+            rhs.clear();
+        }
+        return *this;
+    }
+
+    template<typename Con>
+    bool operator == (const Con& rhs) const
+    {
+        if (size() != rhs.size())
+            return false;
+
+        for (auto it = begin(), last = end(); it != last; ++it) {
+            auto oi = rhs.find(it->first);
+            if (oi == rhs.end() || it->second != oi->second)
+                return false;
+        }
+        return true;
+    }
+
+    template<typename Con>
+    bool operator != (const Con& rhs) const { return !(*this == rhs); }
+
+    ~HashMap() noexcept
+    {
+        if (is_triviall_destructable()) {
+            for (auto it = cbegin(); _num_filled; ++it) {
+                _num_filled --;
+                it->~value_pair();
+            }
+        }
+        free(_pairs);
+    }
+
+    void clone(const HashMap& rhs)
+    {
+        _hasher      = rhs._hasher;
+//        _eq          = rhs._eq;
+#if EMH_SAFE_HASH
+        _num_main    = rhs._num_main;
+        _hash_inter  = rhs._hash_inter;
+#endif
+        _num_filled  = rhs._num_filled;
+        _mask        = rhs._mask;
+        _mlf         = rhs._mlf;
+        _bitmask     = decltype(_bitmask)((char*)_pairs + ((char*)rhs._bitmask - (char*)rhs._pairs));
+        auto opairs  = rhs._pairs;
+
+        auto _num_buckets = _mask + 1;
+        if (is_copy_trivially())
+            memcpy(_pairs, opairs, _num_buckets * sizeof(PairT));
+        else {
+            for (size_type bucket = 0; bucket < _num_buckets; bucket++) {
+                auto next_bucket = EMH_ADDR(_pairs, bucket) = EMH_ADDR(opairs, bucket);
+                if ((int)next_bucket >= 0)
+                    new(_pairs + bucket) PairT(opairs[bucket]);
+            }
+        }
+        memcpy(_pairs + _num_buckets, opairs + _num_buckets, PACK_SIZE * sizeof(PairT) + _num_buckets / 8 + BIT_PACK);
+    }
+
+    void swap(HashMap& rhs)
+    {
+        std::swap(_hasher, rhs._hasher);
+//      std::swap(_eq, rhs._eq);
+        std::swap(_pairs, rhs._pairs);
+#if EMH_SAFE_HASH
+        std::swap(_num_main, rhs._num_main);
+        std::swap(_hash_inter, rhs._hash_inter);
+#endif
+        std::swap(_num_filled, rhs._num_filled);
+        std::swap(_mask, rhs._mask);
+        std::swap(_mlf, rhs._mlf);
+        std::swap(_bitmask, rhs._bitmask);
+        //std::swap(EMH_ADDR(_pairs, _mask + 1), EMH_ADDR(rhs._pairs, rhs._mask + 1));
+    }
+
+    // -------------------------------------------------------------
+    iterator begin() noexcept
+    {
+#ifdef EMH_ZERO_MOVE
+        if (0 == _num_filled)
+            return {this, _mask + 1};
+#endif
+
+        const auto bmask = ~(*(size_t*)_bitmask);
+        if (bmask != 0)
+            return {this, CTZ(bmask), true};
+
+        iterator it(this, sizeof(bmask) * 8 - 1);
+        return it.next();
+    }
+
+    const_iterator cbegin() const noexcept
+    {
+#ifdef EMH_ZERO_MOVE
+        if (0 == _num_filled)
+            return {this, _mask + 1};
+#endif
+
+        const auto bmask = ~(*(size_t*)_bitmask);
+        if (bmask != 0)
+            return {this, CTZ(bmask), true};
+
+        iterator it(this, sizeof(bmask) * 8 - 1);
+        return it.next();
+    }
+
+    iterator last() const
+    {
+        if (_num_filled == 0)
+            return end();
+
+        auto bucket = _mask;
+        while (EMH_EMPTY(_pairs, bucket)) bucket--;
+        return {this, bucket, true};
+    }
+
+    const_iterator begin() const noexcept { return cbegin(); }
+
+    iterator end() noexcept { return {this, _mask + 1}; }
+    const_iterator cend() const { return {this, _mask + 1}; }
+    const_iterator end() const { return {this, _mask + 1}; }
+
+    size_type size() const { return _num_filled; }
+    bool empty() const { return _num_filled == 0; }
+
+    size_type bucket_count() const { return _mask + 1; }
+    float load_factor() const { return static_cast<float>(_num_filled) / (_mask + 1); }
+
+    HashT& hash_function() const { return _hasher; }
+    EqT& key_eq() const { return _eq; }
+
+    void max_load_factor(float mlf)
+    {
+        if (mlf < 0.999f && mlf > EMH_MIN_LOAD_FACTOR)
+            _mlf = decltype(_mlf)((1 << 27) / mlf);
+    }
+
+    constexpr float max_load_factor() const { return (1 << 27) / (float)_mlf; }
+    constexpr size_type max_size() const { return 1ull << ((sizeof(size_type) * 8) - 1); }
+    constexpr size_type max_bucket_count() const { return max_size(); }
+
+#if EMH_STATIS
+    //Returns the bucket number where the element with key k is located.
+    size_type bucket(const KeyT& key) const
+    {
+        const auto bucket = hash_key(key) & _mask;
+        const auto next_bucket = EMH_ADDR(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return 0;
+        else if (bucket == next_bucket * 2)
+            return bucket + 1;
+
+        return hash_main(bucket);
+    }
+
+    //Returns the number of elements in bucket n.
+    size_type bucket_size(const size_type bucket) const
+    {
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return 0;
+
+        const auto& bucket_key = EMH_KEY(_pairs, bucket);
+        next_bucket = hash_key(bucket_key) & _mask;
+        size_type bucket_size = 1;
+
+        //iterator each item in current main bucket
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket) {
+                break;
+            }
+            bucket_size++;
+            next_bucket = nbucket;
+        }
+        return bucket_size;
+    }
+
+    size_type get_main_bucket(const size_type bucket) const
+    {
+        if (EMH_EMPTY(_pairs, bucket))
+            return -1u;
+
+        return hash_main(bucket);
+    }
+
+    int get_cache_info(size_type bucket, size_type next_bucket) const
+    {
+        auto pbucket = reinterpret_cast<std::ptrdiff_t>(&_pairs[bucket]);
+        auto pnext   = reinterpret_cast<std::ptrdiff_t>(&_pairs[next_bucket]);
+        if (pbucket / 64 == pnext / 64)
+            return 0;
+        auto diff = pbucket > pnext ? (pbucket - pnext) : pnext - pbucket;
+        if (diff < 127 * 64)
+            return diff / 64 + 1;
+        return 127;
+    }
+
+    int get_bucket_info(const size_type bucket, size_type steps[], const size_type slots) const
+    {
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+        if ((int)next_bucket < 0)
+            return -1;
+
+        const auto main_bucket = hash_main(bucket);
+        if (main_bucket != bucket)
+            return 0;
+        else if (next_bucket == bucket)
+            return 1;
+
+        steps[get_cache_info(bucket, next_bucket) % slots] ++;
+        size_type ibucket_size = 2;
+        //find a new empty and linked it to tail
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                break;
+
+            steps[get_cache_info(nbucket, next_bucket) % slots] ++;
+            ibucket_size ++;
+            next_bucket = nbucket;
+        }
+        return ibucket_size;
+    }
+
+    void dump_statics() const
+    {
+        size_type buckets[129] = {0};
+        size_type steps[129]   = {0};
+        for (size_type bucket = 0; bucket <= _mask; ++bucket) {
+            auto bsize = get_bucket_info(bucket, steps, 128);
+            if (bsize > 0)
+                buckets[bsize] ++;
+        }
+
+        size_type sumb = 0, collision = 0, sumc = 0, finds = 0, sumn = 0;
+        puts("============== buckets size ration ========");
+        for (size_type i = 0; i < sizeof(buckets) / sizeof(buckets[0]); i++) {
+            const auto bucketsi = buckets[i];
+            if (bucketsi == 0)
+                continue;
+            sumb += bucketsi;
+            sumn += bucketsi * i;
+            collision += bucketsi * (i - 1);
+            finds += bucketsi * i * (i + 1) / 2;
+            printf("  %2u  %8u  %0.8lf  %2.3lf\n", i, bucketsi, bucketsi * 1.0 * i / _num_filled, sumn * 100.0 / _num_filled);
+        }
+
+        puts("========== collision miss ration ===========");
+        for (size_type i = 0; i < sizeof(steps) / sizeof(steps[0]); i++) {
+            sumc += steps[i];
+            if (steps[i] <= 2)
+                continue;
+            printf("  %2u  %8u  %0.2lf  %.2lf\n", i, steps[i], steps[i] * 100.0 / collision, sumc * 100.0 / collision);
+        }
+
+        if (sumb == 0)  return;
+        printf("    _num_filled/aver_size/packed collision/cache_miss/hit_find = %u/%.2lf/%zd/ %.2lf%%/%.2lf%%/%.2lf\n",
+                _num_filled, _num_filled * 1.0 / sumb, sizeof(PairT), (collision * 100.0 / _num_filled), (collision - steps[0]) * 100.0 / _num_filled, finds * 1.0 / _num_filled);
+        assert(sumn == _num_filled);
+        assert(sumc == collision);
+        puts("============== buckets size end =============");
+    }
+#endif
+
+    // ------------------------------------------------------------
+    template<typename Key = KeyT>
+    inline iterator find(const Key& key, size_t key_hash) noexcept
+    {
+        return {this, find_filled_hash(key, key_hash)};
+    }
+
+    template<typename Key = KeyT>
+    inline const_iterator find(const Key& key, size_t key_hash) const noexcept
+    {
+        return {this, find_filled_hash(key, key_hash)};
+    }
+
+    template<typename Key=KeyT>
+    inline iterator find(const Key& key) noexcept
+    {
+        return {this, find_filled_bucket(key)};
+    }
+
+    template<typename Key = KeyT>
+    inline const_iterator find(const Key& key) const noexcept
+    {
+        return {this, find_filled_bucket(key)};
+    }
+
+    template<typename Key = KeyT>
+    inline ValueT& at(const KeyT& key)
+    {
+        const auto bucket = find_filled_bucket(key);
+        //throw
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename Key = KeyT>
+    inline const ValueT& at(const KeyT& key) const
+    {
+        const auto bucket = find_filled_bucket(key);
+        //throw
+        return EMH_VAL(_pairs, bucket);
+    }
+
+    template<typename Key = KeyT>
+    inline bool contains(const Key& key) const noexcept
+    {
+        return find_filled_bucket(key) <= _mask;
+    }
+
+    template<typename Key = KeyT>
+    inline size_type count(const Key& key) const noexcept
+    {
+        return find_filled_bucket(key) <= _mask ? 1 : 0;
+    }
+
+    template<typename Key = KeyT>
+    std::pair<iterator, iterator> equal_range(const Key& key) const noexcept
+    {
+        const auto found = find(key);
+        if (found.bucket() > _mask)
+            return { found, found };
+        else
+            return { found, std::next(found) };
+    }
+
+    template<typename K=KeyT>
+    std::pair<const_iterator, const_iterator> equal_range(const K& key) const
+    {
+        const auto found = find(key);
+        if (found.bucket() > _mask)
+            return { found, found };
+        else
+            return { found, std::next(found) };
+    }
+
+    void merge(HashMap& rhs)
+    {
+        if (empty()) {
+            *this = std::move(rhs);
+            return;
+        }
+
+        for (auto rit = rhs.begin(); rit != rhs.end(); ) {
+            auto fit = find(rit->first);
+            if (fit.bucket() > _mask) {
+                insert_unique(rit->first, std::move(rit->second));
+                rit = rhs.erase(rit);
+            } else {
+                ++rit;
+            }
+        }
+    }
+
+#ifdef EMH_EXT
+    bool try_get(const KeyT& key, ValueT& val) const noexcept
+    {
+        const auto bucket = find_filled_bucket(key);
+        const auto found = bucket <= _mask;
+        if (found) {
+            val = EMH_VAL(_pairs, bucket);
+        }
+        return found;
+    }
+
+    /// Returns the matching ValueT or nullptr if k isn't found.
+    ValueT* try_get(const KeyT& key) noexcept
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket <= _mask ? &EMH_VAL(_pairs, bucket) : nullptr;
+    }
+
+    /// Const version of the above
+    ValueT* try_get(const KeyT& key) const noexcept
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket <= _mask ? &EMH_VAL(_pairs, bucket) : nullptr;
+    }
+
+    /// Convenience function.
+    ValueT get_or_return_default(const KeyT& key) const noexcept
+    {
+        const auto bucket = find_filled_bucket(key);
+        return bucket <= _mask ? EMH_VAL(_pairs, bucket) : ValueT();
+    }
+#endif
+
+    // -----------------------------------------------------
+    /// Returns a pair consisting of an iterator to the inserted element
+    /// (or to the element that prevented the insertion)
+    /// and a bool denoting whether the insertion took place.
+    std::pair<iterator, bool> do_insert(const value_type& value)
+    {
+        const auto bucket = find_or_allocate(value.first);
+        const auto next   = bucket / 2;
+        const auto found  = EMH_EMPTY(_pairs, next);
+        if (found) {
+            EMH_NEW(value.first, value.second, next, bucket);
+        }
+        return { {this, next}, found };
+    }
+
+    std::pair<iterator, bool> do_insert(value_type&& value)
+    {
+        const auto bucket = find_or_allocate(value.first);
+        const auto next   = bucket / 2;
+        const auto found  = EMH_EMPTY(_pairs, next);
+        if (found) {
+            EMH_NEW(std::move(value.first), std::move(value.second), next, bucket);
+        }
+        return { {this, next}, found };
+    }
+
+    template<typename K, typename V>
+    std::pair<iterator, bool> do_insert(K&& key, V&& val)
+    {
+        const auto bucket = find_or_allocate(key);
+        const auto next   = bucket / 2;
+        const auto found  = EMH_EMPTY(_pairs, next);
+        if (found) {
+            EMH_NEW(std::forward<K>(key), std::forward<V>(val), next, bucket);
+        }
+        return { {this, next}, found };
+    }
+
+    template<typename K, typename V>
+    std::pair<iterator, bool> do_assign(K&& key, V&& val)
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto next   = bucket / 2;
+        const auto found = EMH_EMPTY(_pairs, next);
+        if (found) {
+            EMH_NEW(std::forward<K>(key), std::forward<V>(val), next, bucket);
+        } else {
+            EMH_VAL(_pairs, next) = std::move(val);
+        }
+        return { {this, next}, found };
+    }
+
+    std::pair<iterator, bool> insert(const value_type& value)
+    {
+        check_expand_need();
+        return do_insert(value);
+    }
+
+    std::pair<iterator, bool> insert(value_type&& value)
+    {
+        check_expand_need();
+        return do_insert(std::move(value));
+    }
+
+    void insert(std::initializer_list<value_type> ilist)
+    {
+        reserve(ilist.size() + _num_filled);
+        for (auto it = ilist.begin(); it != ilist.end(); ++it)
+            do_insert(*it);
+    }
+
+    template <typename Iter>
+    void insert(Iter first, Iter last)
+    {
+        reserve(std::distance(first, last) + _num_filled);
+        for (auto it = first; it != last; ++it)
+            do_insert(it->first, it->second);
+    }
+
+#if 0
+    template <typename Iter>
+    void insert_unique(Iter begin, Iter end)
+    {
+        reserve(std::distance(begin, end) + _num_filled);
+        for (; begin != end; ++begin)
+            do_insert_unqiue(*begin);
+    }
+#endif
+
+    template<typename K, typename V>
+    inline size_type insert_unique(K&& key, V&& val)
+    {
+        return do_insert_unqiue(std::forward<K>(key), std::forward<V>(val));
+    }
+
+    inline size_type insert_unique(value_type&& value)
+    {
+        return do_insert_unqiue(std::move(value.first), std::move(value.second));
+    }
+
+    inline size_type insert_unique(const value_type& value)
+    {
+        return do_insert_unqiue(value.first, value.second);
+    }
+
+    template<typename K, typename V>
+    inline size_type do_insert_unqiue(K&& key, V&& val)
+    {
+        check_expand_need();
+        auto bucket = find_unique_bucket(key);
+        EMH_NEW(std::forward<K>(key), std::forward<V>(val), bucket / 2, bucket);
+        return bucket;
+    }
+
+    std::pair<iterator, bool> insert_or_assign(const KeyT& key, ValueT&& val) { return do_assign(key, std::forward<ValueT>(val)); }
+    std::pair<iterator, bool> insert_or_assign(KeyT&& key, ValueT&& val) { return do_assign(std::move(key), std::forward<ValueT>(val)); }
+
+    template <typename... Args>
+    inline std::pair<iterator, bool> emplace(Args&&... args) noexcept
+    {
+        check_expand_need();
+        return do_insert(std::forward<Args>(args)...);
+    }
+
+    template <class... Args>
+    iterator emplace_hint(const_iterator hint, Args&&... args)
+    {
+        (void)hint;
+        check_expand_need();
+        return do_insert(std::forward<Args>(args)...).first;
+    }
+
+    template<class... Args>
+    std::pair<iterator, bool> try_emplace(const KeyT& key, Args&&... args)
+    {
+        check_expand_need();
+        return do_insert(key, std::forward<Args>(args)...);
+    }
+
+    template<class... Args>
+    std::pair<iterator, bool> try_emplace(KeyT&& key, Args&&... args)
+    {
+        check_expand_need();
+        return do_insert(std::forward<KeyT>(key), std::forward<Args>(args)...);
+    }
+
+    template <class... Args>
+    inline size_type emplace_unique(Args&&... args) noexcept
+    {
+        return insert_unique(std::forward<Args>(args)...);
+    }
+
+    ValueT& operator[](const KeyT& key) noexcept
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto next   = bucket / 2;
+        /* Check if inserting a new value rather than overwriting an old entry */
+        if (EMH_EMPTY(_pairs, next)) {
+            EMH_NEW(key, std::move(ValueT()), next, bucket);
+        }
+
+        //bugs here if return local reference rehash happens
+        return EMH_VAL(_pairs, next);
+    }
+
+    ValueT& operator[](KeyT&& key) noexcept
+    {
+        check_expand_need();
+        const auto bucket = find_or_allocate(key);
+        const auto next   = bucket / 2;
+        if (EMH_EMPTY(_pairs, next)) {
+            EMH_NEW(std::move(key), std::move(ValueT()), next, bucket);
+        }
+
+        return EMH_VAL(_pairs, next);
+    }
+
+    // -------------------------------------------------------
+    /// Erase an element from the hash table.
+    /// return 0 if element was not found
+    template<typename Key = KeyT>
+    size_type erase(const Key& key)
+    {
+        const auto bucket = erase_key(key);
+        if (bucket == INACTIVE)
+            return 0;
+
+        clear_bucket(bucket);
+        return 1;
+    }
+
+    //iterator erase const_iterator
+    iterator erase(const_iterator cit)
+    {
+        iterator it(cit);
+        return erase(it);
+    }
+
+    /// Erase an element typedef an iterator.
+    /// Returns an iterator to the next element (or end()).
+    iterator erase(iterator it)
+    {
+        const auto bucket = erase_bucket(it._bucket);
+        clear_bucket(bucket);
+        if (bucket == it._bucket) {
+            return ++it;
+        } else {
+            //erase main bucket as next
+            it.clear(bucket);
+            return it;
+        }
+    }
+
+    /// Erase an element typedef an iterator without return next iterator
+    void _erase(const_iterator it)
+    {
+        const auto bucket = erase_bucket(it._bucket);
+        clear_bucket(bucket);
+    }
+
+    template<typename Pred>
+    size_type erase_if(Pred pred)
+    {
+        auto old_size = size();
+        for (auto it = begin(), last = end(); it != last; ) {
+            if (pred(*it))
+                it = erase(it);
+            else
+                ++it;
+        }
+        return old_size - size();
+    }
+
+    static constexpr bool is_triviall_destructable()
+    {
+#if __cplusplus >= 201402L || _MSC_VER > 1600
+        return !(std::is_trivially_destructible<KeyT>::value && std::is_trivially_destructible<ValueT>::value);
+#else
+        return !(std::is_pod<KeyT>::value && std::is_pod<ValueT>::value);
+#endif
+    }
+
+    static constexpr bool is_copy_trivially()
+    {
+#if __cplusplus >= 201402L || _MSC_VER > 1600
+        return (std::is_trivially_copyable<KeyT>::value && std::is_trivially_copyable<ValueT>::value);
+#else
+        return (std::is_pod<KeyT>::value && std::is_pod<ValueT>::value);
+#endif
+    }
+
+    void clearkv()
+    {
+        for (auto it = cbegin(); _num_filled; ++it)
+            clear_bucket(it.bucket());
+    }
+
+    /// Remove all elements, keeping full capacity.
+    void clear()
+    {
+        if (is_triviall_destructable())
+            clearkv();
+        else if (_num_filled) {
+            memset(_bitmask, 0xFFFFFFFF, (_mask + 1) / 8);
+            memset(_pairs, -1, sizeof(_pairs[0]) * (_mask + 1));
+#if EMH_FIND_HIT
+            if constexpr (std::is_integral<KeyT>::value)
+            reset_bucket(hash_main(0));
+#endif
+        }
+
+        _num_filled = 0;
+
+#if EMH_SAFE_HASH
+        _num_main = _hash_inter = 0;
+#endif
+    }
+
+    void shrink_to_fit()
+    {
+        rehash(_num_filled + 1);
+    }
+
+    /// Make room for this many elements
+    bool reserve(uint64_t num_elems)
+    {
+        const auto required_buckets = (uint64_t)(num_elems * _mlf >> 27) + 1;
+        if (EMH_LIKELY(required_buckets <= _mask))
+            return false;
+
+#if EMH_STATIS
+        if (_num_filled > EMH_STATIS) dump_statics();
+#endif
+        rehash(required_buckets + 1);
+        return true;
+    }
+
+    ///three ways may incr rehash: bad hash function, load_factor is high, or need shrink
+    void rehash(uint64_t required_buckets)
+    {
+        if (required_buckets < _num_filled)
+            return;
+#if 0 //(__GNUC__ >= 4 || __clang__)
+        size_type num_buckets = 1ul << (sizeof(required_buckets) * 8 - __builtin_clz(required_buckets));
+        if (num_buckets < sizeof(size_t))
+            num_buckets = sizeof(size_t);
+#else
+        uint64_t buckets = _num_filled > (1u << 16) ? (1u << 16) : sizeof(size_t);
+        while (buckets < required_buckets) { buckets *= 2; }
+
+        // no need alloc too many bucket for small key.
+        // if maybe fail set small load_factor and then call reserve() TODO:
+        if (sizeof(KeyT) < sizeof(size_type) && buckets >= (1ul << (2 * 8)))
+            buckets = 2ul << (sizeof(KeyT) * 8);
+
+        assert(buckets < max_size() && buckets > _num_filled);
+        //assert(num_buckets == (2 << CTZ(required_buckets)));
+#endif
+
+        auto num_buckets = (size_type)buckets;
+        //assert(num_buckets > _num_filled);
+        auto old_num_filled  = _num_filled;
+        auto old_mask        = _mask;
+        auto* new_pairs = (PairT*)malloc(AllocSize(num_buckets));
+#if EMH_EXCEPTION
+        if (EMH_UNLIKELY(!new_pairs))
+            throw std::bad_alloc();
+#else
+        assert(!!new_pairs);
+#endif
+
+        auto old_pairs = _pairs;
+
+        _bitmask = decltype(_bitmask)(new_pairs + PACK_SIZE + num_buckets);
+        const auto bitmask_pack = ((size_t)_bitmask) % sizeof(size_t);
+        if (bitmask_pack != 0) {
+            _bitmask = decltype(_bitmask)((char*)_bitmask + sizeof(size_t) - bitmask_pack);
+            assert(0 == ((size_t)_bitmask) % sizeof(size_t));
+        }
+
+        _num_filled  = 0;
+        _mask        = num_buckets - 1;
+        _pairs       = new_pairs;
+
+#if EMH_SAFE_HASH
+        if (old_num_filled > 100 && _hash_inter == 0)
+            _hash_inter = old_num_filled / (_num_main * 3);
+        _num_main = 0;
+#endif
+
+        memset(_pairs, -1, sizeof(_pairs[0]) * num_buckets);
+
+#if EMH_FIND_HIT
+        if constexpr (std::is_integral<KeyT>::value)
+        reset_bucket(hash_main(0));
+#endif
+
+        //pack tail two tombstones for fast iterator and find empty_bucket without checking overflow
+        memset((char*)(_pairs + num_buckets), 0, sizeof(PairT) * PACK_SIZE);
+
+        /***************** init bitmask ---------------------- ***********/
+        const auto mask_byte = (num_buckets + 7) / 8;
+        memset(_bitmask, 0xFFFFFFFF, mask_byte);
+        memset((char*)_bitmask + mask_byte, 0, BIT_PACK);
+        if (num_buckets < 8)
+            _bitmask[0] = (1 << num_buckets) - 1;
+        //pack last position to bit 0
+        /**************** -------------------------------- *************/
+
+#if EMH_REHASH_LOG
+        auto collision = 0;
+#endif
+        //for (size_type src_bucket = 0; _num_filled < old_num_filled; src_bucket++) {
+        for (size_type src_bucket = old_mask; _num_filled < old_num_filled; src_bucket --) {
+            if (EMH_EMPTY(old_pairs, src_bucket))
+                continue;
+
+            auto&& key = EMH_KEY(old_pairs, src_bucket);
+            const auto bucket = find_unique_bucket(key);
+            EMH_NEW(std::move(key), std::move(EMH_VAL(old_pairs, src_bucket)), bucket / 2, bucket);
+#if EMH_REHASH_LOG
+            if (bucket / 2 != hash_main(bucket / 2))
+                collision++;
+#endif
+            if (is_triviall_destructable())
+                old_pairs[src_bucket].~PairT();
+        }
+
+#if EMH_REHASH_LOG
+        if (_num_filled > EMH_REHASH_LOG) {
+#ifndef EMH_SAFE_HASH
+            auto _num_main = old_num_filled - collision;
+#endif
+            const auto num_buckets = _mask + 1;
+            auto last = EMH_ADDR(_pairs, num_buckets);
+            char buff[255] = {0};
+            sprintf(buff, "    _num_filled/aver_size/K.V/pack/collision|last = %u/%.2lf/%s.%s/%zd/%.2lf%%|%.2lf%%",
+                    _num_filled,(double)_num_filled / _num_main, typeid(KeyT).name(), typeid(ValueT).name(), sizeof(_pairs[0]), (collision * 100.0 / num_buckets), (last * 100.0 / num_buckets));
+#ifdef EMH_LOG
+            static size_type ihashs = 0;
+            EMH_LOG() << "EMH_BUCKET_INDEX = " << EMH_BUCKET_INDEX << "|rhash_nums = " << ihashs ++ << "|" <<__FUNCTION__ << "|" << buff << endl;
+#else
+            puts(buff);
+#endif
+        }
+#endif
+
+        free(old_pairs);
+        assert(old_num_filled == _num_filled);
+    }
+
+private:
+    // Can we fit another element?
+    inline bool check_expand_need()
+    {
+#if EMH_SAFE_HASH > 1
+        if (EMH_UNLIKELY(_num_main * 3 < _num_filled) && _num_filled > 100 && _hash_inter == 0) {
+            rehash(_num_filled);
+            return true;
+        }
+#endif
+        return reserve(_num_filled);
+    }
+
+#if EMH_FIND_HIT
+    void reset_bucket(size_type bucket)
+    {
+        if constexpr (std::is_integral<KeyT>::value) {
+            auto& key = EMH_KEY(_pairs, bucket); key ++;
+//            if (bucket != _zero_index) return;
+            while ((hash_key(key) & _mask) == bucket) key += 1610612741;
+        }
+    }
+#endif
+
+    void clear_bucket(size_type bucket)
+    {
+        EMH_CLS(bucket);
+        _num_filled--;
+        if (is_triviall_destructable())
+            _pairs[bucket].~PairT();
+
+        EMH_ADDR(_pairs, bucket) = INACTIVE;
+#if EMH_FIND_HIT
+        reset_bucket(bucket);
+#endif
+    }
+
+    template<typename UType, typename std::enable_if<std::is_integral<UType>::value, size_type>::type = 0>
+    size_type erase_key(const UType& key)
+    {
+        const auto empty_bucket = INACTIVE;
+        const auto bucket = hash_key(key) & _mask;
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+
+        if (next_bucket == bucket * 2) {
+            const auto eqkey = _eq(key, EMH_KEY(_pairs, bucket));
+#if EMH_SAFE_HASH
+            return eqkey ? (_num_main --, bucket) : empty_bucket;
+#else
+            return eqkey ? bucket : empty_bucket;
+#endif
+        }
+        else if (next_bucket % 2 > 0)
+            return empty_bucket;
+        else if (_eq(key, EMH_KEY(_pairs, bucket))) {
+            next_bucket /= 2;
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+            EMH_ADDR(_pairs, bucket) = (next_bucket == nbucket ? bucket : nbucket) * 2;
+            return next_bucket;
+        }
+
+        next_bucket /= 2;
+        auto prev_bucket = bucket;
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (_eq(key, EMH_KEY(_pairs, next_bucket))) {
+                EMH_ADDR(_pairs, prev_bucket) = (nbucket == next_bucket ? prev_bucket : nbucket) * 2 + (1 - (prev_bucket == bucket));
+                return next_bucket;
+            }
+
+            if (nbucket == next_bucket)
+                break;
+            prev_bucket = next_bucket;
+            next_bucket = nbucket;
+        }
+
+        return empty_bucket;
+    }
+
+    template<typename UType, typename std::enable_if<!std::is_integral<UType>::value, size_type>::type = 0>
+    size_type erase_key(const UType& key)
+    {
+        const auto empty_bucket = INACTIVE;
+        const auto bucket = hash_key(key) & _mask;
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+
+        if (next_bucket == bucket * 2) { //only one main bucket
+            const auto eqkey = _eq(key, EMH_KEY(_pairs, bucket));
+#if EMH_SAFE_HASH
+            return eqkey ? (_num_main --, bucket) : empty_bucket;
+#else
+            return eqkey ? bucket : empty_bucket;
+#endif
+        }
+        else if (next_bucket % 2 > 0)
+            return empty_bucket;
+
+        //find erase key and swap to last bucket
+        size_type prev_bucket = bucket, find_bucket = empty_bucket;
+        next_bucket = bucket;
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (_eq(key, EMH_KEY(_pairs, next_bucket))) {
+                find_bucket = next_bucket;
+                if (nbucket == next_bucket) {
+                    EMH_ADDR(_pairs, prev_bucket) = prev_bucket * 2 + 1 - (prev_bucket == bucket);
+                    break;
+                }
+            }
+            if (nbucket == next_bucket) {
+                if ((int)find_bucket >= 0) {
+                    EMH_PKV(_pairs, find_bucket).swap(EMH_PKV(_pairs, nbucket));
+//                    EMH_PKV(_pairs, find_bucket) = EMH_PKV(_pairs, nbucket);
+                    EMH_ADDR(_pairs, prev_bucket) = prev_bucket * 2 + 1 - (prev_bucket == bucket);
+                    find_bucket = nbucket;
+                }
+                break;
+            }
+            prev_bucket = next_bucket;
+            next_bucket = nbucket;
+        }
+
+        return find_bucket;
+    }
+
+    size_type erase_bucket(const size_type bucket)
+    {
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+        if (next_bucket == bucket * 2) {
+#if EMH_SAFE_HASH
+            _num_main--;
+#endif
+            return bucket;
+        }
+        else if (next_bucket % 2 == 0) {
+            next_bucket /= 2;
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            EMH_PKV(_pairs, bucket) = std::move(EMH_PKV(_pairs, next_bucket));
+            EMH_ADDR(_pairs, bucket) = (next_bucket == nbucket ? bucket : nbucket) * 2;
+            return next_bucket;
+        }
+
+        const auto main_bucket = hash_main(bucket);
+        next_bucket /= 2;
+        const auto prev_bucket = find_prev_bucket(main_bucket, bucket);
+        const auto odd_bucket = (prev_bucket == main_bucket ? 0 : 1);
+        if (bucket == next_bucket)
+            EMH_ADDR(_pairs, prev_bucket) = prev_bucket * 2 + odd_bucket;
+        else
+            EMH_ADDR(_pairs, prev_bucket) = next_bucket * 2 + odd_bucket;
+        return bucket;
+    }
+
+    // Find the bucket with this key, or return bucket size
+    template<typename K>
+    size_type find_filled_hash(const K& key, const size_t key_hash) const
+    {
+        const auto bucket = size_type(key_hash & _mask);
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+        const auto _num_buckets = _mask + 1;
+#ifndef EMH_FIND_HIT
+        if (next_bucket % 2 > 0)
+            return _num_buckets;
+        else if (_eq(key, EMH_KEY(_pairs, bucket)))
+            return bucket;
+        else if (next_bucket == bucket * 2)
+            return _num_buckets;
+#else
+        if constexpr (std::is_integral<K>::value) {
+            if (_eq(key, EMH_KEY(_pairs, bucket)))
+                return bucket;
+            else if (next_bucket % 2 > 0)
+                return _num_buckets;
+//            else if (next_bucket == bucket * 2)
+//                return _num_buckets;
+//            else if (next_bucket != bucket * 2 + 1)
+//                return _num_buckets;
+//            else if (hash_main(bucket) != bucket)
+//                return _num_buckets;
+        } else {
+            if (next_bucket % 2 > 0)
+                return _num_buckets;
+            else if (_eq(key, EMH_KEY(_pairs, bucket)))
+                return bucket;
+            else if (next_bucket == bucket * 2)
+                return _num_buckets;
+        }
+#endif
+
+        next_bucket /= 2;
+        while (true) {
+            if (_eq(key, EMH_KEY(_pairs, next_bucket)))
+                return next_bucket;
+
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                return _num_buckets;
+            next_bucket = nbucket;
+        }
+
+        return 0;
+    }
+
+    // Find the bucket with this key, or return bucket size
+    //1. next_bucket = INACTIVE, empty bucket
+    //2. next_bucket % 2 == 0 is main bucket
+    template<typename Key=KeyT>
+    inline size_type find_filled_bucket(const Key& key) const
+    {
+        return find_filled_hash(key, hash_key(key));
+    }
+
+    //kick out bucket and find empty to occpuy
+    //it will break the orgin link and relnik again.
+    //before: main_bucket-->prev_bucket --> bucket   --> next_bucket
+    //atfer : main_bucket-->prev_bucket --> (removed)--> new_bucket--> next_bucket
+    size_type kickout_bucket(const size_type bucket)
+    {
+        const auto next_bucket = EMH_BUCKET(_pairs, bucket);
+        const auto new_bucket  = find_empty_bucket(next_bucket);
+        const auto kmain_bucket = hash_main(bucket);
+        const auto prev_bucket = find_prev_bucket(kmain_bucket, bucket);
+        new(_pairs + new_bucket) PairT(std::move(_pairs[bucket])); EMH_SET(new_bucket);
+        if (next_bucket == bucket)
+            EMH_ADDR(_pairs, new_bucket) = new_bucket * 2 + 1;
+
+        EMH_ADDR(_pairs, prev_bucket) += (new_bucket - bucket) * 2;
+#if EMH_SAFE_HASH
+        _num_main ++;
+#endif
+        clear_bucket(bucket); _num_filled ++;
+        return bucket * 2;
+    }
+
+/***
+** inserts a new key into a hash table; first check whether key's main
+** bucket/position is free. If not, check whether colliding node/bucket is in its main
+** position or not: if it is not, move colliding bucket to an empty place and
+** put new key in its main position; otherwise (colliding bucket is in its main
+** position), new key goes to an empty position. ***/
+    template<typename K=KeyT>
+    size_type find_or_allocate(const K& key)
+    {
+        const auto bucket = hash_key(key) & _mask;
+        auto next_bucket = EMH_ADDR(_pairs, bucket);
+#if EMH_SAFE_HASH
+        if ((int)next_bucket < 0)
+            return _num_main ++, bucket * 2;
+        else if (_eq(key, EMH_KEY(_pairs, bucket)))
+            return bucket * 2;
+#else
+        if ((int)next_bucket < 0 || _eq(key, EMH_KEY(_pairs, bucket)))
+            return bucket * 2;
+#endif
+
+        //check current bucket_key is in main bucket or not
+        if (next_bucket == bucket * 2)
+            return (EMH_ADDR(_pairs, bucket) = find_empty_bucket(bucket) * 2) + 1;
+        else if (next_bucket % 2 > 0)
+            return kickout_bucket(bucket);
+
+        //int collisions = 2;
+        next_bucket /= 2;
+        //find next linked bucket and check key
+        while (true) {
+            if (_eq(key, EMH_KEY(_pairs, next_bucket))) {
+#if EMH_LRU_SET
+                EMH_PKV(_pairs, next_bucket).swap(EMH_PKV(_pairs, bucket));
+                return bucket * 2;
+#else
+                return next_bucket * 2;
+#endif
+            }
+
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                break;
+            next_bucket = nbucket;
+            //collisions++;
+        }
+
+        //find a new empty and link it to tail
+        const auto new_bucket = find_empty_bucket(bucket);
+        return EMH_ADDR(_pairs, next_bucket) = new_bucket * 2 + 1;
+    }
+
+    // key is not in this map. Find a place to put it.
+    size_type find_empty_bucket(const size_type bucket_from)
+    {
+#ifdef EMH_ALIGN64
+        const auto boset  = bucket_from % MASK_BIT;
+        auto* const align = _bitmask + bucket_from / MASK_BIT;
+        const auto bmask  = ((size_t)align[1] << (MASK_BIT - boset)) | (align[0] >> boset);
+        static_assert(sizeof(size_t) > 4);
+#elif EMH_ITER_SAFE
+        const auto boset = bucket_from % 8;
+        auto* const start = (uint8_t*)_bitmask + bucket_from / 8;
+        size_t bmask; memcpy(&bmask, start + 0, sizeof(bmask)); bmask >>= boset;
+#else //maybe not aligned
+        const auto boset  = bucket_from % 8;
+        auto* const align = (uint8_t*)_bitmask + bucket_from / 8;
+        const auto bmask  = (*(size_t*)(align) >> boset); //maybe not aligned and warning
+#endif
+        if (EMH_LIKELY(bmask != 0))
+            return bucket_from + CTZ(bmask);
+
+        const auto qmask = _mask / SIZE_BIT;
+        if (0) {
+            const auto step = (bucket_from - SIZE_BIT / 4) & qmask;
+            const auto bmask3 = *((size_t*)_bitmask + step);
+            if (bmask3 != 0)
+                return step * SIZE_BIT + CTZ(bmask3);
+        }
+
+        auto& _last = EMH_ADDR(_pairs, _mask + 1);
+        while ( true ) {
+            const auto bmask2 = *((size_t*)_bitmask + _last);
+            if (bmask2 != 0)
+                return _last * SIZE_BIT + CTZ(bmask2);
+
+            const auto next1 = (qmask / 2 + _last) & qmask;
+            const auto bmask1 = *((size_t*)_bitmask + next1);
+            if (bmask1 != 0) {
+                return next1 * SIZE_BIT + CTZ(bmask1);
+            }
+
+            _last = (_last + 1) & qmask;
+        }
+        return 0;
+    }
+
+    size_type find_last_bucket(size_type main_bucket) const
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, main_bucket);
+        if (next_bucket == main_bucket)
+            return main_bucket;
+
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == next_bucket)
+                return next_bucket;
+            next_bucket = nbucket;
+        }
+    }
+
+    size_type find_prev_bucket(size_type main_bucket, const size_type bucket) const
+    {
+        auto next_bucket = EMH_BUCKET(_pairs, main_bucket);
+        if (next_bucket == bucket)
+            return main_bucket;
+
+        while (true) {
+            const auto nbucket = EMH_BUCKET(_pairs, next_bucket);
+            if (nbucket == bucket)
+                return next_bucket;
+            next_bucket = nbucket;
+        }
+    }
+
+    size_type find_unique_bucket(const KeyT& key)
+    {
+        const auto bucket = size_type(hash_key(key) & _mask);
+        const auto next_bucket = EMH_ADDR(_pairs, bucket);
+        if ((int)next_bucket < 0) {
+#if EMH_SAFE_HASH
+            _num_main ++;
+#endif
+            return bucket * 2;
+        }
+
+        //check current bucket_key is in main bucket or not
+        if (next_bucket == bucket * 2)
+            return (EMH_ADDR(_pairs, bucket) = find_empty_bucket(bucket) * 2) + 1;
+        else if (next_bucket % 2 > 0)
+            return kickout_bucket(bucket);
+
+        const auto last_bucket = find_last_bucket(next_bucket / 2);
+        //find a new empty and link it to tail
+        return EMH_ADDR(_pairs, last_bucket) = find_empty_bucket(last_bucket) * 2 + 1;
+    }
+
+#if EMH_INT_HASH
+    static constexpr uint64_t KC = UINT64_C(11400714819323198485);
+    inline uint64_t hash64(uint64_t key)
+    {
+#if __SIZEOF_INT128__ && EMH_INT_HASH == 1
+        __uint128_t r = key; r *= KC;
+        return (uint64_t)(r >> 64) + (uint64_t)r;
+#elif EMH_INT_HASH == 2
+        //MurmurHash3Mixer
+        uint64_t h = key;
+        h ^= h >> 33;
+        h *= 0xff51afd7ed558ccd;
+        h ^= h >> 33;
+        h *= 0xc4ceb9fe1a85ec53;
+        h ^= h >> 33;
+        return h;
+#elif _WIN64 && EMH_INT_HASH == 1
+        uint64_t high;
+        return _umul128(key, KC, &high) + high;
+#elif EMH_INT_HASH == 3
+        auto ror  = (key >> 32) | (key << 32);
+        auto low  = key * 0xA24BAED4963EE407ull;
+        auto high = ror * 0x9FB21C651E98DF25ull;
+        auto mix  = low + high;
+        return mix;
+#elif EMH_INT_HASH == 1
+        uint64_t r = key * UINT64_C(0xca4bcaa75ec3f625);
+        return (r >> 32) + r;
+#elif EMH_WYHASH64
+        return wyhash64(key, KC);
+#else
+        uint64_t x = key;
+        x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
+        x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
+        x = x ^ (x >> 31);
+        return x;
+#endif
+    }
+#endif
+
+    inline size_type hash_main(const size_type bucket) const
+    {
+        return hash_key(EMH_KEY(_pairs, bucket)) & _mask;
+    }
+
+    template<typename UType, typename std::enable_if<std::is_integral<UType>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType key) const
+    {
+#if EMH_INT_HASH
+        return hash64(key);
+#elif EMH_SAFE_HASH
+        return _hash_inter == 0 ? _hasher(key) : hash64(key);
+#elif EMH_IDENTITY_HASH
+        return key + (key >> 24);
+#else
+        return (size_type)_hasher(key);
+#endif
+    }
+
+    template<typename UType, typename std::enable_if<std::is_same<UType, std::string>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType& key) const
+    {
+#if EMH_WY_HASH
+        return wyhash(key.data(), key.size(), 0);
+#else
+        return (size_type)_hasher(key);
+#endif
+    }
+
+    template<typename UType, typename std::enable_if<!std::is_integral<UType>::value && !std::is_same<UType, std::string>::value, size_type>::type = 0>
+    inline size_type hash_key(const UType& key) const
+    {
+        return (size_type)_hasher(key);
+    }
+
+    //8 * 2 + 4 * 5 = 16 + 20 = 32
+private:
+    PairT*    _pairs;
+    uint32_t* _bitmask;
+    HashT     _hasher;
+    EqT       _eq;
+    size_type _mask;
+    size_type _num_filled;
+    uint32_t _mlf;
+
+#if EMH_FIND_HIT
+//    size_type _zero_index;
+#endif
+
+#if EMH_SAFE_HASH
+    size_type _num_main;
+    size_type _hash_inter;
+#endif
+
+    static constexpr uint32_t BIT_PACK = sizeof(_bitmask[0]) * 2;
+    static constexpr uint32_t MASK_BIT = sizeof(_bitmask[0]) * 8;
+    static constexpr uint32_t SIZE_BIT = sizeof(size_t) * 8;
+    static constexpr uint32_t PACK_SIZE = 2; // > 1
+};
+}
+// namespace emhash6
+#if __cplusplus >= 201103L
+//template <class Key, class Val> using ehmap6 = emhash6::HashMap<Key, Val, std::hash<Key>>;
+#endif

+ 1 - 1
src/hash_table8.hpp

@@ -1,4 +1,4 @@
-// emhash8::HashMap for C++11/14/17
+// pkpy::HashMap for C++11/14/17
 // version 1.6.3
 // version 1.6.3
 //
 //
 // Licensed under the MIT License <http://opensource.org/licenses/MIT>.
 // Licensed under the MIT License <http://opensource.org/licenses/MIT>.

+ 2 - 2
src/parser.h

@@ -38,8 +38,8 @@ constexpr TokenIndex TK(const char* const token) {
 const TokenIndex kTokenKwBegin = TK("class");
 const TokenIndex kTokenKwBegin = TK("class");
 const TokenIndex kTokenKwEnd = TK("raise");
 const TokenIndex kTokenKwEnd = TK("raise");
 
 
-const emhash8::HashMap<std::string_view, TokenIndex> kTokenKwMap = [](){
-    emhash8::HashMap<std::string_view, TokenIndex> map;
+const pkpy::HashMap<std::string_view, TokenIndex> kTokenKwMap = [](){
+    pkpy::HashMap<std::string_view, TokenIndex> map;
     for(int k=kTokenKwBegin; k<=kTokenKwEnd; k++) map[kTokens[k]] = k;
     for(int k=kTokenKwBegin; k<=kTokenKwEnd; k++) map[kTokens[k]] = k;
     return map;
     return map;
 }();
 }();

+ 6 - 3
src/pocketpy.h

@@ -125,10 +125,13 @@ void init_builtins(VM* _vm) {
     _vm->bind_builtin_func<1>("dir", [](VM* vm, pkpy::Args& args) {
     _vm->bind_builtin_func<1>("dir", [](VM* vm, pkpy::Args& args) {
         std::vector<StrName> names;
         std::vector<StrName> names;
         if(args[0]->is_attr_valid()){
         if(args[0]->is_attr_valid()){
-            for (auto& [k, _] : args[0]->attr()) names.push_back(k);
+            for(auto it = args[0]->attr().begin(); it != args[0]->attr().end(); ++it){
+                names.push_back(it->first);
+            }
         }
         }
-        for (auto& [k, _] : vm->_t(args[0])->attr()) {
-            if (std::find(names.begin(), names.end(), k) == names.end()) names.push_back(k);
+        const pkpy::NameDict& t_attr = vm->_t(args[0])->attr();
+        for(auto it = t_attr.begin(); it != t_attr.end(); ++it){
+            if (std::find(names.begin(), names.end(), it->first) == names.end()) names.push_back(it->first);
         }
         }
         pkpy::List ret;
         pkpy::List ret;
         for (const auto& name : names) ret.push_back(vm->PyStr(name.str()));
         for (const auto& name : names) ret.push_back(vm->PyStr(name.str()));

+ 1 - 1
src/safestl.h

@@ -33,7 +33,7 @@ public:
     using std::vector<PyVar>::vector;
     using std::vector<PyVar>::vector;
 };
 };
 
 
-typedef emhash8::HashMap<StrName, PyVar> NameDict;
+typedef pkpy::HashMap<StrName, PyVar> NameDict;
 
 
 }
 }
 
 

+ 5 - 1
src/str.h

@@ -160,7 +160,7 @@ bool is_unicode_Lo_char(uint32_t c) {
 
 
 
 
 struct StrName {
 struct StrName {
-    const int index;
+    int index;
     StrName(int index): index(index) {}
     StrName(int index): index(index) {}
     StrName(const char* s): index(get(s).index) {}
     StrName(const char* s): index(get(s).index) {}
     StrName(const Str& s): index(get(s).index) {}
     StrName(const Str& s): index(get(s).index) {}
@@ -191,6 +191,10 @@ struct StrName {
     }
     }
 };
 };
 
 
+// declare static members
+std::map<Str, int, std::less<>> StrName::_interned;
+std::vector<Str> StrName::_r_interned;
+
 template<>
 template<>
 struct std::hash<StrName> {
 struct std::hash<StrName> {
     inline size_t operator()(const StrName& name) const {
     inline size_t operator()(const StrName& name) const {

+ 3 - 3
src/vm.h

@@ -25,7 +25,7 @@ public:
 
 
     pkpy::NameDict _types;
     pkpy::NameDict _types;
     pkpy::NameDict _modules;                            // loaded modules
     pkpy::NameDict _modules;                            // loaded modules
-    emhash8::HashMap<StrName, Str> _lazy_modules;       // lazy loaded modules
+    pkpy::HashMap<StrName, Str> _lazy_modules;       // lazy loaded modules
     PyVar None, True, False, Ellipsis;
     PyVar None, True, False, Ellipsis;
 
 
     bool use_stdio;
     bool use_stdio;
@@ -634,8 +634,8 @@ public:
         setattr(_t(tp_type), __base__, _t(tp_object));
         setattr(_t(tp_type), __base__, _t(tp_object));
         setattr(_t(tp_object), __base__, None);
         setattr(_t(tp_object), __base__, None);
         
         
-        for (auto& [name, type] : _types) {
-            setattr(type, __name__, PyStr(name.str()));
+        for(auto it = _types.begin(); it != _types.end(); it++){
+            setattr(it->second, __name__, PyStr(it->first.str()));
         }
         }
 
 
         std::vector<Str> pb_types = {"type", "object", "bool", "int", "float", "str", "list", "tuple", "range"};
         std::vector<Str> pb_types = {"type", "object", "bool", "int", "float", "str", "list", "tuple", "range"};