Line | Count | Source |
1 | | /*****************************************************************************/ |
2 | | /* LibreDWG - free implementation of the DWG file format */ |
3 | | /* */ |
4 | | /* Copyright (C) 2018-2019,2023 Free Software Foundation, Inc. */ |
5 | | /* */ |
6 | | /* This library is free software, licensed under the terms of the GNU */ |
7 | | /* General Public License as published by the Free Software Foundation, */ |
8 | | /* either version 3 of the License, or (at your option) any later version. */ |
9 | | /* You should have received a copy of the GNU General Public License */ |
10 | | /* along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
11 | | /*****************************************************************************/ |
12 | | |
13 | | /* |
14 | | * hash.c: int hashmap for the object_ref map. |
15 | | * uses linear probing for best cache usage. |
16 | | * values are inlined into the array. The 0 key is disallowed. |
17 | | * written by Reini Urban |
18 | | */ |
19 | | |
20 | | #include "hash.h" |
21 | | #include <stdlib.h> |
22 | | #include <stdio.h> |
23 | | #include <string.h> |
24 | | #include "logging.h" |
25 | | |
26 | | dwg_inthash * |
27 | | hash_new (uint64_t size) |
28 | 16.5k | { |
29 | 16.5k | dwg_inthash *hash = (dwg_inthash *)malloc (sizeof (dwg_inthash)); |
30 | 16.5k | uint64_t cap; |
31 | 16.5k | if (!hash) |
32 | 0 | return NULL; |
33 | | // multiply with load factor, |
34 | | // and round size to next power of 2 (fast) or prime (secure), |
35 | 16.5k | if (size < 15) |
36 | 14.5k | size = 15; |
37 | 16.5k | cap = (uint64_t)(size * 100.0 / HASH_LOAD); |
38 | | // this is slow, but only done once. clz would be much faster |
39 | 33.0k | while (size <= cap) |
40 | 16.5k | size <<= 1U; |
41 | 16.5k | hash->array = (struct _hashbucket *)calloc ( |
42 | 16.5k | size, sizeof (struct _hashbucket)); // key+value pairs |
43 | 16.5k | hash->elems = 0; |
44 | 16.5k | hash->size = size; |
45 | 16.5k | return hash; |
46 | 16.5k | } |
47 | | |
48 | | // if exceeds load factor |
49 | | static inline int |
50 | | hash_need_resize (dwg_inthash *hash) |
51 | 3.09k | { |
52 | 3.09k | return (uint64_t)(hash->elems * 100.0 / HASH_LOAD) > hash->size; |
53 | 3.09k | } |
54 | | |
55 | | static void |
56 | | hash_resize (dwg_inthash *hash) |
57 | 3.09k | { |
58 | 3.09k | dwg_inthash oldhash = *hash; |
59 | 3.09k | uint64_t size = hash->size * 2; |
60 | 3.09k | uint64_t i; |
61 | | |
62 | | // allocate key+value pairs afresh |
63 | 3.09k | hash->array |
64 | 3.09k | = (struct _hashbucket *)calloc (size, sizeof (struct _hashbucket)); |
65 | 3.09k | if (!hash->array) |
66 | 0 | { |
67 | 0 | *hash = oldhash; |
68 | 0 | return; |
69 | 0 | } |
70 | 3.09k | hash->elems = 0; |
71 | 3.09k | hash->size = size; |
72 | 3.09k | memset (hash->array, 0, size * sizeof (struct _hashbucket)); |
73 | | // spread out the old elements in double space, less collisions |
74 | 421k | for (i = 0; i < oldhash.size; i++) |
75 | 418k | { |
76 | 418k | if (oldhash.array[i].key) |
77 | 418k | hash_set (hash, oldhash.array[i].key, oldhash.array[i].value); |
78 | 418k | } |
79 | 3.09k | free (oldhash.array); |
80 | 3.09k | return; |
81 | 3.09k | } |
82 | | |
83 | | // found this gem by Thomas Mueller at stackoverflow. triviality threshold. |
84 | | // it's like a normal murmur or jenkins finalizer, |
85 | | // just statistically tested to be optimal. |
86 | | // 2023: changed to 64bit, checked at https://nullprogram.com/blog/2018/07/31/ |
87 | | // Note that this is entirely "insecure", the inverse func is trivial. |
88 | | // We don't care as we deal with DWG and had linear search before. |
89 | | static inline uint64_t |
90 | | hash_func (uint64_t key) |
91 | 3.71M | { |
92 | 3.71M | key = ((key >> 32) ^ key) * UINT64_C (0xd6e8feb86659fd93); |
93 | 3.71M | key = ((key >> 32) ^ key) * UINT64_C (0xd6e8feb86659fd93); |
94 | 3.71M | key = (key >> 32) ^ key; |
95 | 3.71M | return key; |
96 | 3.71M | } |
97 | | |
98 | | // 0 is disallowed as key, even if there's no deletion. |
99 | | uint64_t |
100 | | hash_get (dwg_inthash *hash, uint64_t key) |
101 | 2.71M | { |
102 | 2.71M | uint64_t i = hash_func (key) % hash->size; |
103 | 2.71M | uint64_t j = i; |
104 | 19.2M | while (hash->array[i].key && hash->array[i].key != key) |
105 | 16.5M | { |
106 | | // HANDLER (OUTPUT, "get collision at %d\n", i); |
107 | 16.5M | i++; // linear probing with wrap around |
108 | 16.5M | if (i == hash->size) |
109 | 206k | i = 0; |
110 | 16.5M | if (i == j) // not found |
111 | 3.26k | return HASH_NOT_FOUND; |
112 | 16.5M | } |
113 | 2.71M | if (hash->array[i].key) |
114 | 289k | return hash->array[i].value; |
115 | 2.42M | else |
116 | 2.42M | return HASH_NOT_FOUND; |
117 | 2.71M | } |
118 | | |
119 | | // search or insert. key 0 is forbidden. |
120 | | void |
121 | | hash_set (dwg_inthash *hash, uint64_t key, uint64_t value) |
122 | 994k | { |
123 | 994k | uint64_t i = hash_func (key) % hash->size; |
124 | 994k | uint64_t j = i; |
125 | 994k | if (key == 0) |
126 | 164 | { |
127 | 164 | HANDLER (OUTPUT, "forbidden 0 key\n"); |
128 | 164 | return; |
129 | 164 | } |
130 | | // empty slot |
131 | 994k | if (!hash->array[i].key) |
132 | 487k | { |
133 | 487k | hash->array[i].key = key; |
134 | 487k | hash->array[i].value = value; |
135 | 487k | hash->elems++; |
136 | 487k | return; |
137 | 487k | } |
138 | 6.99M | while (hash->array[i].key) |
139 | 6.65M | { |
140 | 6.65M | if (hash->array[i].key == key) |
141 | 163k | { // found |
142 | 163k | hash->array[i].value = value; |
143 | 163k | return; |
144 | 163k | } |
145 | | // HANDLER (OUTPUT, "set collision at %d\n", i); |
146 | 6.49M | i++; // linear probing with wrap around |
147 | 6.49M | if (i == hash->size) |
148 | 17.2k | i = 0; |
149 | 6.49M | if (i == j) // not found |
150 | 3.09k | { |
151 | | // HANDLER (OUTPUT, "set not found at %d\n", i); |
152 | | // if does not exist, add at i+1 |
153 | 3.09k | if (hash_need_resize (hash)) |
154 | 3.09k | { |
155 | | // HANDLER (OUTPUT, "resize at %d\n", hash->size); |
156 | 3.09k | hash_resize (hash); |
157 | 3.09k | hash_set (hash, key, value); |
158 | 3.09k | return; |
159 | 3.09k | } |
160 | 0 | while (hash->array[i].key) // find next empty slot |
161 | 0 | { |
162 | | // up to here we have no coverage! |
163 | | // HANDLER (OUTPUT, "set 2nd collision at %d\n", i); |
164 | 0 | i++; // again linear probing with wrap around |
165 | 0 | if (i == hash->size) |
166 | 0 | i = 0; |
167 | 0 | if (i == j) // not found |
168 | 0 | { |
169 | | // HANDLER (OUTPUT, "not found resize at %d\n", hash->size); |
170 | 0 | hash_resize (hash); // guarantees new empty slots |
171 | 0 | hash_set (hash, key, value); |
172 | 0 | return; |
173 | 0 | } |
174 | 0 | else |
175 | 0 | { // insert at empty slot |
176 | 0 | hash->array[i].key = key; |
177 | 0 | hash->array[i].value = value; |
178 | 0 | hash->elems++; |
179 | 0 | return; |
180 | 0 | } |
181 | 0 | } |
182 | 0 | } |
183 | 6.49M | } |
184 | | // empty slot |
185 | 339k | hash->array[i].key = key; |
186 | 339k | hash->array[i].value = value; |
187 | 339k | hash->elems++; |
188 | 339k | return; |
189 | 506k | } |
190 | | |
191 | | void |
192 | | hash_free (dwg_inthash *hash) |
193 | 16.5k | { |
194 | 16.5k | free (hash->array); |
195 | | hash->array = NULL; |
196 | 16.5k | hash->size = 0; |
197 | 16.5k | hash->elems = 0; |
198 | 16.5k | free (hash); |
199 | 16.5k | } |