├── .gitignore
├── .travis.yml
├── test
├── makefile
└── test_sho.cc
├── .gitattributes
├── sho.natvis
├── LICENSE
├── README.md
└── sho.h
/.gitignore:
--------------------------------------------------------------------------------
1 | test/*
2 | !test/test_sho.cc
3 | !test/makefile
4 | #README.md#
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 |
3 | os:
4 | - linux
5 | - osx
6 |
7 | compiler:
8 | - clang
9 | - gcc
10 |
11 | dist: trusty
12 | sudo: false
13 |
14 | script: cd test && make && make test
15 |
--------------------------------------------------------------------------------
/test/makefile:
--------------------------------------------------------------------------------
1 | CXXFLAGS = -O2 -std=c++11 -I..
2 | CXXFLAGS += -Wall -pedantic -Wextra -D_XOPEN_SOURCE=700
3 | DEPS = ../sho.h
4 | TARGETS = test_sho
5 |
6 |
7 | ifeq ($(OS),Windows_NT)
8 | LDFLAGS = -lpsapi
9 | endif
10 |
11 | all: $(TARGETS)
12 |
13 | clean:
14 | rm -rf $(TARGETS)
15 |
16 | test:
17 | ./test_sho
18 |
19 | %: %.cc $(DEPS) makefile
20 | $(CXX) $(CXXFLAGS) -DNDEBUG $< -o $@ $(LDFLAGS)
21 |
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/sho.natvis:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ size={_cnt} }}
7 | {*(Map *)_cnt}
8 |
9 | (Map *)_cnt
10 |
11 | _cnt
12 | _items
13 |
14 |
15 |
16 |
17 |
18 | {*_p}
19 | {_it}
20 |
21 | *_p
22 | _it
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------------
2 | // Copyright (c) 2017, Gregory Popovitch - greg7mdp@gmail.com
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in all
12 | // copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | // SOFTWARE.
21 | // -----------------------------------------------------------------------------
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/greg7mdp/sho)
2 |
3 | # SHO: The Small Hash Optimization
4 |
5 | When you need a key to value map with the fastest lookup, it is hard to beat a good hash map. Hash maps are so efficient that they are sometimes used as essential parts of other data structures, causing many instances of a hash map to exist in memory.
6 |
7 | I decided to implement this class after working with Michael, a user of my [sparsepp](https://github.com/greg7mdp/sparsepp) hash map. His application builds a trie with close to one hundred million nodes, and each node keeps track of its children using a hash map. He originally used [std::unordered_map](http://www.cplusplus.com/reference/unordered_map/unordered_map/), but then switched to using [sparsepp](https://github.com/greg7mdp/sparsepp) in order to reduce the memory usage of his application.
8 |
9 | Michael was pretty happy with the reduced memory usage of sparsepp (memory usage went down from 49GB to 33GB, and execution time was reduced a little from 13 to 12 minutes. However we remained in contact and discussed whether using a custom allocator would help reducing memory usage even further.
10 |
11 | When testing with the new optional [allocator](https://github.com/greg7mdp/sparsepp/blob/master/sparsepp/spp_dlalloc.h) from sparsepp based on Doug Lea [malloc](http://g.oswego.edu/dl/html/malloc.html), Michael reported that memory usage was *significantly increased*, not what we had hoped for!
12 |
13 | What happened was that Michael's application creates close to one hundred million hash maps. While some of them are very large, most have zero or very few entries. The new allocator, which reserve a private memory space for each hash map, was very ill suited for the task and increased greatly the memory usage of all those (almost) empty hash maps.
14 |
15 | So we decided to try a small hash optimization, which would use a small array of `` pairs for small hash maps, and would expand to a full blown hash map (dynamically allocated) when the small array overflowed. This is similar to LLVM's [Small Vector Class](http://llvm.org/docs/doxygen/html/classllvm_1_1SmallVector.html), which uses a local array to avoid memory allocations (and fragmentation) for small vectors.
16 |
17 | This small hash optimization was a success even beyond our expectations for Michael's project, reducing the memory usage further from 33GB to 17GB, and the execution time from 12 to 11 minutes.
18 |
19 | ## Installation
20 |
21 | No compilation is needed, as this is a header-only library. The installation consist in copying the sho.h header wherever it will be convenient to include in your project(s).
22 |
23 | ## Example
24 |
25 | Suppose you are using a std::unordered_map through a typedef such as:
26 |
27 |
28 | ```c++
29 | typedef std::unordered_map MyMap;
30 | ```
31 |
32 | Adding the Small Hash Optimization is as easy as including sho.h and updating the typedef as follows:
33 |
34 | ```c++
35 | #include
36 |
37 | typedef sho::smo<3, std::unordered_map, uint32_t, void *> MyMap;
38 | ```
39 |
40 | The number 3 passed as the first template parameter means that the local array cache will be dimensioned to contain a maximum of 3 entries (you may want to test a few numbers to see which one offers the best size and speed improvement for your use case.
41 |
42 | The second template parameter is the hash map used when the array overflows. I have tested the code with both [std::unordered_map](http://www.cplusplus.com/reference/unordered_map/unordered_map/) and [sparsepp](https://github.com/greg7mdp/sparsepp)
43 |
44 | ## API support
45 |
46 | I implemented sho.h over a week-end, and it does not yet support the full std::unordered_map interface. Feel free to enter issues for extra APIs, or even better send me pull requests.
47 |
48 | ## TODO
49 |
50 | * Properly store the comparison functor and the allocator in sho, with minimal memory cost
51 | * Support maps parametrized with types without default constructors
52 | * support full std::unordered_map API
53 | * support std::unordered_set
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/test/test_sho.cc:
--------------------------------------------------------------------------------
1 | // compile on windows with: CL -O2 -I.. -EHsc test_sho.cc -o test_sho
2 | // ----------------------------------------------------------------------
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #include
14 | #include
15 |
16 | // -----------------------------------------------------------
17 | // Timer
18 | // -----------------------------------------------------------
19 | namespace dltest
20 | {
21 | template
22 | class Timer
23 | {
24 | public:
25 | Timer() { reset(); }
26 | void reset() { _start = _snap = clock::now(); }
27 | void snap() { _snap = clock::now(); }
28 |
29 | float get_total() const { return get_diff(_start, clock::now()); }
30 | float get_delta() const { return get_diff(_snap, clock::now()); }
31 |
32 | private:
33 | using clock = std::chrono::high_resolution_clock;
34 | using point = std::chrono::time_point;
35 |
36 | template
37 | static T get_diff(const point& start, const point& end)
38 | {
39 | using duration_t = std::chrono::duration;
40 |
41 | return std::chrono::duration_cast(end - start).count();
42 | }
43 |
44 | point _start;
45 | point _snap;
46 | };
47 | }
48 |
49 | // -----------------------------------------------------------
50 | // Memory usage
51 | // -----------------------------------------------------------
52 | #if defined(_WIN32) || defined( __CYGWIN__)
53 | #define SHO_TEST_WIN
54 | #endif
55 |
56 | #ifdef SHO_TEST_WIN
57 | #include
58 | #include
59 | #undef min
60 | #undef max
61 | #elif !defined(__APPLE__)
62 | #include
63 | #include
64 | #endif
65 |
66 | namespace dltest
67 | {
68 | uint64_t GetSystemMemory()
69 | {
70 | #ifdef SHO_TEST_WIN
71 | MEMORYSTATUSEX memInfo;
72 | memInfo.dwLength = sizeof(MEMORYSTATUSEX);
73 | GlobalMemoryStatusEx(&memInfo);
74 | return static_cast(memInfo.ullTotalPageFile);
75 | #elif !defined(__APPLE__)
76 | struct sysinfo memInfo;
77 | sysinfo (&memInfo);
78 | auto totalVirtualMem = memInfo.totalram;
79 |
80 | totalVirtualMem += memInfo.totalswap;
81 | totalVirtualMem *= memInfo.mem_unit;
82 | return static_cast(totalVirtualMem);
83 | #else
84 | return 0;
85 | #endif
86 | }
87 |
88 | uint64_t GetTotalMemoryUsed()
89 | {
90 | #ifdef SHO_TEST_WIN
91 | MEMORYSTATUSEX memInfo;
92 | memInfo.dwLength = sizeof(MEMORYSTATUSEX);
93 | GlobalMemoryStatusEx(&memInfo);
94 | return static_cast(memInfo.ullTotalPageFile - memInfo.ullAvailPageFile);
95 | #elif !defined(__APPLE__)
96 | struct sysinfo memInfo;
97 | sysinfo(&memInfo);
98 | auto virtualMemUsed = memInfo.totalram - memInfo.freeram;
99 |
100 | virtualMemUsed += memInfo.totalswap - memInfo.freeswap;
101 | virtualMemUsed *= memInfo.mem_unit;
102 |
103 | return static_cast(virtualMemUsed);
104 | #else
105 | return 0;
106 | #endif
107 | }
108 |
109 | uint64_t GetProcessMemoryUsed()
110 | {
111 | #ifdef SHO_TEST_WIN
112 | PROCESS_MEMORY_COUNTERS_EX pmc;
113 | GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&pmc), sizeof(pmc));
114 | return static_cast(pmc.PrivateUsage);
115 | #elif !defined(__APPLE__)
116 | auto parseLine =
117 | [](char* line)->int
118 | {
119 | auto i = strlen(line);
120 |
121 | while(*line < '0' || *line > '9')
122 | {
123 | line++;
124 | }
125 |
126 | line[i-3] = '\0';
127 | i = atoi(line);
128 | return i;
129 | };
130 |
131 | auto file = fopen("/proc/self/status", "r");
132 | auto result = -1;
133 | char line[128];
134 |
135 | while(fgets(line, 128, file) != nullptr)
136 | {
137 | if(strncmp(line, "VmSize:", 7) == 0)
138 | {
139 | result = parseLine(line);
140 | break;
141 | }
142 | }
143 |
144 | fclose(file);
145 | return static_cast(result) * 1024;
146 | #else
147 | return 0;
148 | #endif
149 | }
150 |
151 | uint64_t GetPhysicalMemory()
152 | {
153 | #ifdef SHO_TEST_WIN
154 | MEMORYSTATUSEX memInfo;
155 | memInfo.dwLength = sizeof(MEMORYSTATUSEX);
156 | GlobalMemoryStatusEx(&memInfo);
157 | return static_cast(memInfo.ullTotalPhys);
158 | #elif !defined(__APPLE__)
159 | struct sysinfo memInfo;
160 | sysinfo(&memInfo);
161 |
162 | auto totalPhysMem = memInfo.totalram;
163 |
164 | totalPhysMem *= memInfo.mem_unit;
165 | return static_cast(totalPhysMem);
166 | #else
167 | return 0;
168 | #endif
169 | }
170 | }
171 |
172 | // -----------------------------------------------------------
173 | // test code
174 | // -----------------------------------------------------------
175 | using namespace std;
176 |
177 | static float _to_mb(uint64_t m) { return (float)((double)m / (1024 * 1024)); }
178 |
179 | // -----------------------------------------------------------
180 | // -----------------------------------------------------------
181 | template
182 | void run_test(const char *container_name, size_t num_iter)
183 | {
184 | // ----------------------- some checks
185 | {
186 | Hash h;
187 |
188 | typename Hash::iterator it(h.begin());
189 | typename Hash::const_iterator cit(h.cbegin());
190 | cit = it; // assign const & non-const iters
191 | //it = cit;
192 | if (cit != h.end() || it != h.cend()) // compare const & non-const iters
193 | abort();
194 | }
195 |
196 | printf("---------------- testing %s\n", container_name);
197 |
198 | printf("\nmem usage before the test: %4.1f\n", _to_mb(dltest::GetProcessMemoryUsed()));
199 | srand(43); // always same sequence of random numbers
200 |
201 | Hash *hashes = new Hash[num_iter];
202 |
203 | dltest::Timer timer;
204 |
205 | for (size_t i=0; i
250 | #define BASE_MAP spp::sparse_hash_map
251 | #else
252 | #define BASE_MAP std::unordered_map
253 | #endif
254 |
255 | // -----------------------------------------------------------
256 | // -----------------------------------------------------------
257 | int main()
258 | {
259 | typedef BASE_MAP StdMap;
260 | typedef sho::smo<3, BASE_MAP, uint32_t, void *> ShoMap;
261 |
262 | run_test(TOSTRING(BASE_MAP), 5000000);
263 |
264 | char cont_name[128];
265 | sprintf(cont_name, "%s with sho", TOSTRING(BASE_MAP));
266 | run_test(cont_name, 5000000);
267 | }
268 |
--------------------------------------------------------------------------------
/sho.h:
--------------------------------------------------------------------------------
1 | #ifndef sho__h__
2 | #define sho__h__
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | // ---------------------------------------------------------------------
12 | // S M A L L H A S H O P T I M I Z A T I O N
13 | // ------------------------------------------------
14 | //
15 | // When you have a lot of very small hash maps with just zero to a couple
16 | // entries => store a few element in a small array of possible, else
17 | // allocate a full hash_map.
18 | //
19 | // Assumptions:
20 | // - we assume that the value of a pointer is always greater then N.
21 | // ---------------------------------------------------------------------
22 |
23 | namespace sho
24 | {
25 |
26 | // ----------------
27 | template
28 | class ShoIterator : public std::iterator
29 | {
30 | public:
31 | typedef Val value_type;
32 |
33 | template
34 | ShoIterator(const ShoIterator &o) : _p((value_type *)o._p), _it(*((MapIt *)&o._it)) {}
35 |
36 | explicit ShoIterator(const value_type *p = 0) : _p((value_type *)p) {}
37 | explicit ShoIterator(const MapIt &o) : _p(0), _it(o) {}
38 |
39 | ShoIterator& operator++() { if (_p) ++_p; else ++_it; return *this; }
40 | ShoIterator& operator--() { if (_p) --_p; else --_it; return *this; }
41 | ShoIterator operator++(int) { ShoIterator tmp(*this); ++*this; return tmp; }
42 | ShoIterator operator--(int) { ShoIterator tmp(*this); --*this; return tmp; }
43 |
44 | bool operator==(const ShoIterator &o) const { return _p ? _p == o._p : _it == o._it; }
45 | bool operator!=(const ShoIterator &o) const { return !(*this == o); }
46 |
47 | value_type& operator*() const { return _p ? *_p : *_it; }
48 | value_type* operator->() const { return &(operator*()); }
49 |
50 | const MapIt& getMapIt() const { assert(!_p); return _it; }
51 | value_type * getPtr() const { assert(_p); return _p; }
52 |
53 | value_type *_p;
54 | MapIt _it;
55 | };
56 |
57 | // --------------------------------ShoI----------------------------------------
58 | // S M A L L M A P O P T I M I Z A T I O N
59 | // ------------------------------------------------------------------------
60 | template class RefMap,
62 | class K,
63 | class V,
64 | class Hash = std::hash,
65 | class Pred = std::equal_to,
66 | class Alloc = std::allocator > >
67 | class smo
68 | {
69 | public:
70 | typedef RefMap Map;
71 | typedef typename Map::key_type key_type;
72 | typedef typename Map::mapped_type mapped_type;
73 | typedef typename Map::value_type value_type;
74 | typedef typename Map::hasher hasher;
75 | typedef typename Map::key_equal key_equal;
76 | typedef typename Map::allocator_type allocator_type;
77 | typedef std::pair mutable_value_type;
78 |
79 | typedef typename Map::size_type size_type;
80 | typedef typename Map::difference_type difference_type;
81 |
82 | typedef typename Map::iterator map_iterator;
83 | typedef typename Map::const_iterator map_const_iterator;
84 |
85 |
86 | // ----------------
87 | typedef ShoIterator iterator;
88 | typedef ShoIterator const_iterator;
89 |
90 | smo(size_type = 0) : _cnt(0) {}
91 |
92 | ~smo()
93 | {
94 | if (_hasMap())
95 | _cleanMap();
96 | }
97 |
98 | smo(const smo &o) : _cnt(0)
99 | {
100 | if (o._hasMap())
101 | _cnt = (uintptr_t)(new Map(o._getMap()));
102 | else
103 | _copyLocal(o);
104 | }
105 |
106 | smo& operator=(const smo &o)
107 | {
108 | if (this == &o)
109 | return *this;
110 |
111 | if (_hasMap())
112 | _cleanMap();
113 | if (o._hasMap())
114 | _cnt = (uintptr_t)(new Map(o._getMap()));
115 | else
116 | _copyLocal(o);
117 | return *this;
118 | }
119 |
120 | // Iterator functions
121 | iterator begin() const
122 | {
123 | return _hasMap() ? iterator(_getMap()->begin()) : iterator(&_items[0]);
124 | }
125 |
126 | iterator end() const
127 | {
128 | return _hasMap() ? iterator(_getMap()->end()) : iterator(&_items[_cnt]);
129 | }
130 |
131 | const_iterator cbegin() const
132 | {
133 | return _hasMap() ? const_iterator(_getMap()->begin()) : const_iterator(&_items[0]);
134 | }
135 |
136 | const_iterator cend() const
137 | {
138 | return _hasMap() ? const_iterator(_getMap()->end()) : const_iterator(&_items[_cnt]);
139 | }
140 |
141 | iterator find(const key_type& key)
142 | {
143 | if (_hasMap())
144 | return iterator(_getMap()->find(key));
145 | else
146 | for (size_t i=0; i<_cnt; ++i)
147 | if (key_equal()(_items[i].first, key))
148 | return iterator(&_items[i]);
149 | return end();
150 | }
151 |
152 | const_iterator find(const key_type& key) const
153 | {
154 | if (_hasMap())
155 | return const_iterator(_getMap()->find(key));
156 | else
157 | for (size_t i=0; i<_cnt; ++i)
158 | if (key_equal()(_items[i].first, key))
159 | return const_iterator(&_items[i]);
160 | return cend();
161 | }
162 |
163 | size_type erase(const key_type& k)
164 | {
165 | if (_hasMap())
166 | return _getMap()->erase(k);
167 |
168 | for (size_t i=0; i<_cnt; ++i)
169 | {
170 | if (key_equal()(_items[i].first, k))
171 | {
172 | // rotate item to delete to last position
173 | std::rotate((mutable_value_type *)&_items[i],
174 | (mutable_value_type *)&_items[i+1],
175 | (mutable_value_type *)&_items[_cnt]);
176 | --_cnt;
177 | allocator_type().destroy(&_items[_cnt]);
178 | return 1;
179 | }
180 | }
181 | return 0;
182 | }
183 |
184 | iterator erase(const_iterator it)
185 | {
186 | if (_hasMap())
187 | {
188 | map_iterator res = _getMap()->erase(it.getMapIt());
189 | return iterator(res);
190 | }
191 | const value_type *cur = it.getPtr();
192 | size_t idx = cur - &_items[0];
193 | assert(idx <= N);
194 |
195 | if (idx < N && _cnt)
196 | {
197 | // rotate item to delete to last position
198 | std::rotate((mutable_value_type *)cur,
199 | (mutable_value_type *)cur+1,
200 | (mutable_value_type *)&_items[_cnt]);
201 | --_cnt;
202 | allocator_type().destroy(&_items[_cnt]);
203 | return iterator(cur);
204 | }
205 | return end();
206 | }
207 |
208 | mapped_type& operator[](const key_type& key)
209 | {
210 | if (_hasMap())
211 | return _getMap()->operator[](key);
212 | else
213 | {
214 | for (size_t i=0; i<_cnt; ++i)
215 | if (key_equal()(_items[i].first, key))
216 | return _items[i].second;
217 | }
218 | insert(value_type(key, mapped_type()));
219 | return this->operator[](key);
220 | }
221 |
222 | mapped_type& at(const key_type& key)
223 | {
224 | if (_hasMap())
225 | return _getMap()->at(key); // throws if not found
226 |
227 | for (size_t i=0; i<_cnt; ++i)
228 | if (key_equal()(_items[i].first, key))
229 | return _items[i].second;
230 | throw std::out_of_range("at: key not present");
231 | }
232 |
233 |
234 | std::pair insert(const value_type& val)
235 | {
236 | if (_hasMap())
237 | {
238 | std::pair res = _getMap()->insert(val);
239 | return std::make_pair(iterator(res.first), res.second);
240 | }
241 |
242 | for (size_t i=0; i<_cnt; ++i)
243 | if (key_equal()(_items[i].first, val.first))
244 | return std::make_pair(iterator(&_items[i]), false);
245 |
246 | // not present ... insert it
247 | if (_cnt == N)
248 | {
249 | _switchToMap();
250 | std::pair res = _getMap()->insert(val);
251 | assert(res.second);
252 | return std::make_pair(iterator(res.first), true);
253 | }
254 | *((K *)(&_items[_cnt].first)) = val.first;
255 | _items[_cnt].second = val.second;
256 | return std::make_pair(iterator(&_items[_cnt++]), true);
257 | }
258 |
259 | size_type size() const { return _hasMap() ? _getMap()->size() : _cnt; }
260 |
261 | size_type bucket_count() const { return _hasMap() ? _getMap()->bucket_count() : _cnt; }
262 |
263 | size_type count (const key_type& k) const
264 | {
265 | return find(k) == cend() ? 0 : 1;
266 | }
267 |
268 | bool empty() const { return size() == 0; }
269 |
270 | void clear()
271 | {
272 | if (_hasMap())
273 | _cleanMap();
274 | else
275 | {
276 | allocator_type alloc;
277 | for (size_t i=0; i<_cnt; ++i)
278 | alloc.destroy(&_items[i]);
279 | _cnt = 0;
280 | }
281 | }
282 |
283 | private:
284 | bool _hasMap() const { return _cnt > N; }
285 |
286 | Map * _getMap() const { assert(_hasMap()); return (Map *)_cnt; }
287 |
288 | void _cleanMap() { delete _getMap(); _cnt = 0; }
289 |
290 | void _copyLocal(const smo &o)
291 | {
292 | assert(!_hasMap());
293 | _cnt = o._cnt;
294 | for (size_t i=0; i<_cnt; ++i)
295 | _items[i] = o._items[i];
296 | }
297 |
298 | void _createMap()
299 | {
300 | assert(!_hasMap());
301 | _cnt = (uintptr_t)(new Map);
302 | }
303 |
304 | void _switchToMap()
305 | {
306 | assert(!_hasMap());
307 |
308 | uintptr_t cnt = _cnt;
309 | _cnt = 0;
310 | _createMap();
311 |
312 | Map *map = _getMap();
313 | for (size_t i=0; iinsert(_items[i]);
315 | }
316 |
317 | uintptr_t _cnt;
318 | value_type _items[N];
319 | };
320 |
321 | } // sho namespace
322 |
323 |
324 | #endif // sho__h__
325 |
--------------------------------------------------------------------------------