;\n";
39 | for (unsigned i = 2; i < depth; i++) {
40 | std::cout << "\tusing S" << i << " = std::result_of_t;\n";
41 | }
42 | std::cout << "\tconstexpr unsigned M = Bubble + 1;\n"
43 | << "\tif (n < M*" << (depth-1) << ") {\n"
44 | << "\t\tunion {\n\t\t\t";
45 | for (unsigned i = 1; i < depth; i++) {
46 | std::cout << "S" << i << " s" << i << "; ";
47 | }
48 | std::cout << "\n\t\t} ctx[M*" << (depth-1) << "-1];\n"
49 | << "\t\tfor (size_t i = 0; i < n; i++) ctx[i].s1 = p1(i);\n";
50 | for (unsigned i = 2; i < depth; i++) {
51 | std::cout << "\t\tfor (size_t i = 0; i < n; i++) ctx[i].s" << i
52 | << " = p" << i << "(ctx[i].s" << (i-1) << ", i);\n";
53 | }
54 | std::cout << "\t\tfor (size_t i = 0; i < n; i++) p" << depth << "(ctx[i].s" << (depth-1) << ", i);\n"
55 | << "\t\treturn;\n"
56 | << "\t}\n";
57 | for (unsigned i = 1; i < depth; i++) {
58 | std::cout << "\tS" << i << " s" << i << "[M];\n";
59 | }
60 | for (unsigned i = 1; i < depth; i++) {
61 | std::cout << "\tfor (unsigned j = 0; j < M; j++) {\n";
62 | for (unsigned j = i; j > 1; j--) {
63 | std::cout << "\t\ts" << j << "[j] = p" << j << "(s" << (j-1) << "[j], M*" << (i-j) << "+j);\n";
64 | }
65 | std::cout << "\t\ts1[j] = p1(M*" << (i-1) << "+j);\n"
66 | << "\t}\n";
67 | }
68 | std::cout << "\tunsigned k = 0;\n"
69 | << "\tfor (size_t i = M*" << (depth-1) << "; i < n; i++) {\n"
70 | << "\t\tp" << depth << "(s" << (depth-1) << "[k], i-M*" << (depth-1) << ");\n";
71 | for (unsigned i = depth-1; i > 1; i--) {
72 | std::cout << "\t\ts" << i << "[k] = p" << i << "(s" << (i-1) << "[k], i-M*" << (i-1) << ");\n";
73 | }
74 | std::cout << "\t\ts1[k] = p1(i);\n"
75 | << "\t\tif (++k >= M) k = 0;\n"
76 | << "\t}\n";
77 | for (unsigned i = 1; i < depth; i++) {
78 | std::cout << "\tfor (unsigned j = 0; j < M; j++) {\n"
79 | << "\t\tp" << depth << "(s" << (depth-1) << "[k], n-M*" << (depth-i) << "+j);\n";
80 | for (unsigned j = depth-1; j > i; j--) {
81 | std::cout << "\t\ts" << j << "[k] = p" << j << "(s" << (j-1) << "[k], n-M*" << (j-i) << "+j);\n";
82 | }
83 | std::cout << "\t\tif (++k >= M) k = 0;\n"
84 | << "\t}\n";
85 | }
86 | std::cout << "}\n" << std::endl;
87 | }
88 | ======================================================================== */
89 |
90 | #include
91 |
92 | template
93 | static inline __attribute__((always_inline)) void
94 | Pipeline(size_t n, const P1& p1, const P2& p2) {
95 | using S1 = std::result_of_t;
96 | constexpr unsigned M = Bubble + 1;
97 | if (n < M*1) {
98 | union {
99 | S1 s1;
100 | } ctx[M*1-1];
101 | for (size_t i = 0; i < n; i++) ctx[i].s1 = p1(i);
102 | for (size_t i = 0; i < n; i++) p2(ctx[i].s1, i);
103 | return;
104 | }
105 | S1 s1[M];
106 | for (unsigned j = 0; j < M; j++) {
107 | s1[j] = p1(M*0+j);
108 | }
109 | unsigned k = 0;
110 | for (size_t i = M*1; i < n; i++) {
111 | p2(s1[k], i-M*1);
112 | s1[k] = p1(i);
113 | if (++k >= M) k = 0;
114 | }
115 | for (unsigned j = 0; j < M; j++) {
116 | p2(s1[k], n-M*1+j);
117 | if (++k >= M) k = 0;
118 | }
119 | }
120 |
121 | template
122 | static inline __attribute__((always_inline)) void
123 | Pipeline(size_t n, const P1& p1, const P2& p2, const P3& p3) {
124 | using S1 = std::result_of_t;
125 | using S2 = std::result_of_t;
126 | constexpr unsigned M = Bubble + 1;
127 | if (n < M*2) {
128 | union {
129 | S1 s1; S2 s2;
130 | } ctx[M*2-1];
131 | for (size_t i = 0; i < n; i++) ctx[i].s1 = p1(i);
132 | for (size_t i = 0; i < n; i++) ctx[i].s2 = p2(ctx[i].s1, i);
133 | for (size_t i = 0; i < n; i++) p3(ctx[i].s2, i);
134 | return;
135 | }
136 | S1 s1[M];
137 | S2 s2[M];
138 | for (unsigned j = 0; j < M; j++) {
139 | s1[j] = p1(M*0+j);
140 | }
141 | for (unsigned j = 0; j < M; j++) {
142 | s2[j] = p2(s1[j], M*0+j);
143 | s1[j] = p1(M*1+j);
144 | }
145 | unsigned k = 0;
146 | for (size_t i = M*2; i < n; i++) {
147 | p3(s2[k], i-M*2);
148 | s2[k] = p2(s1[k], i-M*1);
149 | s1[k] = p1(i);
150 | if (++k >= M) k = 0;
151 | }
152 | for (unsigned j = 0; j < M; j++) {
153 | p3(s2[k], n-M*2+j);
154 | s2[k] = p2(s1[k], n-M*1+j);
155 | if (++k >= M) k = 0;
156 | }
157 | for (unsigned j = 0; j < M; j++) {
158 | p3(s2[k], n-M*1+j);
159 | if (++k >= M) k = 0;
160 | }
161 | }
162 |
163 | template
164 | static inline __attribute__((always_inline)) void
165 | Pipeline(size_t n, const P1& p1, const P2& p2, const P3& p3, const P4& p4) {
166 | using S1 = std::result_of_t;
167 | using S2 = std::result_of_t;
168 | using S3 = std::result_of_t;
169 | constexpr unsigned M = Bubble + 1;
170 | if (n < M*3) {
171 | union {
172 | S1 s1; S2 s2; S3 s3;
173 | } ctx[M*3-1];
174 | for (size_t i = 0; i < n; i++) ctx[i].s1 = p1(i);
175 | for (size_t i = 0; i < n; i++) ctx[i].s2 = p2(ctx[i].s1, i);
176 | for (size_t i = 0; i < n; i++) ctx[i].s3 = p3(ctx[i].s2, i);
177 | for (size_t i = 0; i < n; i++) p4(ctx[i].s3, i);
178 | return;
179 | }
180 | S1 s1[M];
181 | S2 s2[M];
182 | S3 s3[M];
183 | for (unsigned j = 0; j < M; j++) {
184 | s1[j] = p1(M*0+j);
185 | }
186 | for (unsigned j = 0; j < M; j++) {
187 | s2[j] = p2(s1[j], M*0+j);
188 | s1[j] = p1(M*1+j);
189 | }
190 | for (unsigned j = 0; j < M; j++) {
191 | s3[j] = p3(s2[j], M*0+j);
192 | s2[j] = p2(s1[j], M*1+j);
193 | s1[j] = p1(M*2+j);
194 | }
195 | unsigned k = 0;
196 | for (size_t i = M*3; i < n; i++) {
197 | p4(s3[k], i-M*3);
198 | s3[k] = p3(s2[k], i-M*2);
199 | s2[k] = p2(s1[k], i-M*1);
200 | s1[k] = p1(i);
201 | if (++k >= M) k = 0;
202 | }
203 | for (unsigned j = 0; j < M; j++) {
204 | p4(s3[k], n-M*3+j);
205 | s3[k] = p3(s2[k], n-M*2+j);
206 | s2[k] = p2(s1[k], n-M*1+j);
207 | if (++k >= M) k = 0;
208 | }
209 | for (unsigned j = 0; j < M; j++) {
210 | p4(s3[k], n-M*2+j);
211 | s3[k] = p3(s2[k], n-M*1+j);
212 | if (++k >= M) k = 0;
213 | }
214 | for (unsigned j = 0; j < M; j++) {
215 | p4(s3[k], n-M*1+j);
216 | if (++k >= M) k = 0;
217 | }
218 | }
219 |
220 | template
221 | static inline __attribute__((always_inline)) void
222 | Pipeline(size_t n, const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7) {
223 | using S1 = std::result_of_t;
224 | using S2 = std::result_of_t;
225 | using S3 = std::result_of_t;
226 | using S4 = std::result_of_t;
227 | using S5 = std::result_of_t;
228 | using S6 = std::result_of_t;
229 | constexpr unsigned M = Bubble + 1;
230 | if (n < M*6) {
231 | union {
232 | S1 s1; S2 s2; S3 s3; S4 s4; S5 s5; S6 s6;
233 | } ctx[M*6-1];
234 | for (size_t i = 0; i < n; i++) ctx[i].s1 = p1(i);
235 | for (size_t i = 0; i < n; i++) ctx[i].s2 = p2(ctx[i].s1, i);
236 | for (size_t i = 0; i < n; i++) ctx[i].s3 = p3(ctx[i].s2, i);
237 | for (size_t i = 0; i < n; i++) ctx[i].s4 = p4(ctx[i].s3, i);
238 | for (size_t i = 0; i < n; i++) ctx[i].s5 = p5(ctx[i].s4, i);
239 | for (size_t i = 0; i < n; i++) ctx[i].s6 = p6(ctx[i].s5, i);
240 | for (size_t i = 0; i < n; i++) p7(ctx[i].s6, i);
241 | return;
242 | }
243 | S1 s1[M];
244 | S2 s2[M];
245 | S3 s3[M];
246 | S4 s4[M];
247 | S5 s5[M];
248 | S6 s6[M];
249 | for (unsigned j = 0; j < M; j++) {
250 | s1[j] = p1(M*0+j);
251 | }
252 | for (unsigned j = 0; j < M; j++) {
253 | s2[j] = p2(s1[j], M*0+j);
254 | s1[j] = p1(M*1+j);
255 | }
256 | for (unsigned j = 0; j < M; j++) {
257 | s3[j] = p3(s2[j], M*0+j);
258 | s2[j] = p2(s1[j], M*1+j);
259 | s1[j] = p1(M*2+j);
260 | }
261 | for (unsigned j = 0; j < M; j++) {
262 | s4[j] = p4(s3[j], M*0+j);
263 | s3[j] = p3(s2[j], M*1+j);
264 | s2[j] = p2(s1[j], M*2+j);
265 | s1[j] = p1(M*3+j);
266 | }
267 | for (unsigned j = 0; j < M; j++) {
268 | s5[j] = p5(s4[j], M*0+j);
269 | s4[j] = p4(s3[j], M*1+j);
270 | s3[j] = p3(s2[j], M*2+j);
271 | s2[j] = p2(s1[j], M*3+j);
272 | s1[j] = p1(M*4+j);
273 | }
274 | for (unsigned j = 0; j < M; j++) {
275 | s6[j] = p6(s5[j], M*0+j);
276 | s5[j] = p5(s4[j], M*1+j);
277 | s4[j] = p4(s3[j], M*2+j);
278 | s3[j] = p3(s2[j], M*3+j);
279 | s2[j] = p2(s1[j], M*4+j);
280 | s1[j] = p1(M*5+j);
281 | }
282 | unsigned k = 0;
283 | for (size_t i = M*6; i < n; i++) {
284 | p7(s6[k], i-M*6);
285 | s6[k] = p6(s5[k], i-M*5);
286 | s5[k] = p5(s4[k], i-M*4);
287 | s4[k] = p4(s3[k], i-M*3);
288 | s3[k] = p3(s2[k], i-M*2);
289 | s2[k] = p2(s1[k], i-M*1);
290 | s1[k] = p1(i);
291 | if (++k >= M) k = 0;
292 | }
293 | for (unsigned j = 0; j < M; j++) {
294 | p7(s6[k], n-M*6+j);
295 | s6[k] = p6(s5[k], n-M*5+j);
296 | s5[k] = p5(s4[k], n-M*4+j);
297 | s4[k] = p4(s3[k], n-M*3+j);
298 | s3[k] = p3(s2[k], n-M*2+j);
299 | s2[k] = p2(s1[k], n-M*1+j);
300 | if (++k >= M) k = 0;
301 | }
302 | for (unsigned j = 0; j < M; j++) {
303 | p7(s6[k], n-M*5+j);
304 | s6[k] = p6(s5[k], n-M*4+j);
305 | s5[k] = p5(s4[k], n-M*3+j);
306 | s4[k] = p4(s3[k], n-M*2+j);
307 | s3[k] = p3(s2[k], n-M*1+j);
308 | if (++k >= M) k = 0;
309 | }
310 | for (unsigned j = 0; j < M; j++) {
311 | p7(s6[k], n-M*4+j);
312 | s6[k] = p6(s5[k], n-M*3+j);
313 | s5[k] = p5(s4[k], n-M*2+j);
314 | s4[k] = p4(s3[k], n-M*1+j);
315 | if (++k >= M) k = 0;
316 | }
317 | for (unsigned j = 0; j < M; j++) {
318 | p7(s6[k], n-M*3+j);
319 | s6[k] = p6(s5[k], n-M*2+j);
320 | s5[k] = p5(s4[k], n-M*1+j);
321 | if (++k >= M) k = 0;
322 | }
323 | for (unsigned j = 0; j < M; j++) {
324 | p7(s6[k], n-M*2+j);
325 | s6[k] = p6(s5[k], n-M*1+j);
326 | if (++k >= M) k = 0;
327 | }
328 | for (unsigned j = 0; j < M; j++) {
329 | p7(s6[k], n-M*1+j);
330 | if (++k >= M) k = 0;
331 | }
332 | }
333 | #endif //SHD_PIPELINE_H_
334 |
--------------------------------------------------------------------------------
/src/search.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 | #include
21 | #include
22 | #include "internal.h"
23 | #include "pipeline.h"
24 |
25 | namespace shd {
26 |
27 | struct Step1 {
28 | const SegmentView* seg;
29 | V96 id;
30 | uint32_t l1pos;
31 | };
32 |
33 | struct Step2 {
34 | const SegmentView* seg;
35 | uint32_t section;
36 | uint8_t bit_off;
37 | };
38 |
39 | struct Step3 {
40 | const uint8_t* line;
41 | };
42 |
43 | static FORCE_INLINE Step1 Calc1(const PackView& index, const uint8_t* key, uint8_t key_len) {
44 | Step1 out;
45 | out.id = GenID(index.seed, key, key_len);
46 | out.seg = &index.segments[L0Hash(out.id) % index.l0sz];
47 | out.l1pos = SkewMap(L1Hash(out.id), out.seg->l1bd);
48 | return out;
49 | }
50 |
51 | static FORCE_INLINE Step1 Process1(const PackView& index, const uint8_t* key, uint8_t key_len) {
52 | Step1 out = Calc1(index, key, key_len);
53 | PrefetchForNext(&out.seg->cells[out.l1pos]);
54 | return out;
55 | }
56 |
57 | static FORCE_INLINE Step1 Process1(const PackView& pack, const uint8_t* key) {
58 | return Process1(pack, key, pack.key_len);
59 | }
60 |
61 | static FORCE_INLINE Step2 Calc2(const Step1& in) {
62 | Step2 out;
63 | out.seg = in.seg;
64 | const auto bit_pos = L2Hash(in.id, in.seg->cells[in.l1pos]) % in.seg->l2sz;
65 | out.section = bit_pos / BITMAP_SECTION_SIZE;
66 | out.bit_off = bit_pos % BITMAP_SECTION_SIZE;
67 | return out;
68 | }
69 |
70 | static FORCE_INLINE Step2 Process2(const Step1& in) {
71 | Step2 out = Calc2(in);
72 | PrefetchForNext(&out.seg->sections[out.section]);
73 | return out;
74 | }
75 |
76 | static FORCE_INLINE uint64_t CalcPos(const Step2& in) {
77 | auto& section = in.seg->sections[in.section];
78 | uint32_t cnt = section.step; //step is the last field of section
79 | auto v = (const uint64_t*)section.b32;
80 | const uint64_t mask = (1LL << (in.bit_off & 63U)) - 1U;
81 | switch (in.bit_off >> 6U) {
82 | case 3: cnt += PopCount64(*v++);
83 | case 2: cnt += PopCount64(*v++);
84 | case 1: cnt += PopCount64(*v++);
85 | case 0: cnt += PopCount64(*v & mask);
86 | }
87 | return in.seg->offset + cnt;
88 | }
89 |
90 | uint64_t CalcPos(const PackView& index, const uint8_t* key, uint8_t key_len) {
91 | return CalcPos(Calc2(Calc1(index, key, key_len)));
92 | }
93 |
94 | #ifndef CACHE_BLOCK_SIZE
95 | #define CACHE_BLOCK_SIZE 64U
96 | #endif
97 | static_assert(CACHE_BLOCK_SIZE >= 64U && (CACHE_BLOCK_SIZE&(CACHE_BLOCK_SIZE-1)) == 0);
98 |
99 | static FORCE_INLINE Step3 Process3(const PackView& pack, const Step2& in, bool fetch_val=false) {
100 | const auto pos = CalcPos(in);
101 | Step3 out;
102 | if (LIKELY(pos < pack.item)) {
103 | out.line = pack.content + pos*pack.line_size;
104 | PrefetchForNext(out.line);
105 | auto off = (uintptr_t)out.line & (CACHE_BLOCK_SIZE-1);
106 | auto blk = (const void*)(((uintptr_t)out.line & ~(uintptr_t)(CACHE_BLOCK_SIZE-1)) + CACHE_BLOCK_SIZE);
107 | if (off + pack.key_len > CACHE_BLOCK_SIZE) {
108 | PrefetchForNext(blk);
109 | } else if (fetch_val && off + pack.line_size > CACHE_BLOCK_SIZE) {
110 | PrefetchForFuture(blk);
111 | }
112 | } else {
113 | out.line = nullptr;
114 | }
115 | return out;
116 | }
117 |
118 | void BatchLocate(const PackView& index, unsigned batch, const uint8_t* __restrict__ keys,
119 | uint8_t key_len, uint64_t* __restrict__ out) {
120 | Pipeline<8>(batch,
121 | [&index, keys, key_len](unsigned i) -> Step1 {
122 | return Process1(index, keys+i*key_len, key_len);
123 | },
124 | [](const Step1& in, unsigned) -> Step2 {
125 | return Process2(in);
126 | },
127 | [&out](const Step2& in, unsigned i) {
128 | out[i] = CalcPos(in);
129 | }
130 | );
131 | }
132 |
133 | unsigned BatchSearch(const PackView& pack, unsigned batch, const uint8_t* const keys[], const uint8_t* out[]) {
134 | if (pack.type != Type::KV_INLINE && pack.type != Type::KEY_SET) {
135 | return 0;
136 | }
137 | unsigned hit = 0;
138 | Pipeline<7>(batch,
139 | [&pack, keys](unsigned i) -> Step1 {
140 | return Process1(pack, keys[i]);
141 | },
142 | [](const Step1& in, unsigned) -> Step2 {
143 | return Process2(in);
144 | },
145 | [&pack](const Step2& in, unsigned) -> Step3 {
146 | return Process3(pack, in);
147 | },
148 | [&pack, &hit, keys, out](const Step3& in, unsigned i) {
149 | if (LIKELY(in.line != nullptr) && Equal(keys[i], in.line, pack.key_len)) {
150 | hit++;
151 | out[i] = in.line + pack.key_len;
152 | } else {
153 | out[i] = nullptr;
154 | }
155 | }
156 | );
157 | return hit;
158 | }
159 |
160 | unsigned BatchFetch(const PackView& pack, const uint8_t* __restrict__ dft_val, unsigned batch,
161 | const uint8_t* __restrict__ keys, uint8_t* __restrict__ data, unsigned* __restrict__ miss) {
162 | if (pack.type != Type::KV_INLINE) {
163 | return 0;
164 | }
165 | unsigned hit = 0;
166 | Pipeline<6>(batch,
167 | [&pack, keys](unsigned i) -> Step1 {
168 | auto key = keys+i*pack.key_len;
169 | return Process1(pack, key);
170 | },
171 | [](const Step1& in, unsigned) -> Step2 {
172 | return Process2(in);
173 | },
174 | [&pack](const Step2& in, unsigned) -> Step3 {
175 | return Process3(pack, in, true);
176 | },
177 | [&pack, &hit, keys, data, dft_val, &miss](const Step3& in, unsigned i) {
178 | auto key = keys + i*pack.key_len;
179 | auto out = data + i*pack.val_len;
180 | auto src = in.line + pack.key_len;
181 | if (LIKELY(in.line != nullptr) && Equal(key, in.line, pack.key_len)) {
182 | hit++;
183 | } else if (dft_val != nullptr) {
184 | src = dft_val;
185 | } else if (miss != nullptr) {
186 | *miss++ = i;
187 | } else {
188 | return;
189 | }
190 | memcpy(out, src, pack.val_len);
191 | }
192 | );
193 | return hit;
194 | }
195 |
196 | template
197 | struct Relay {
198 | const uint8_t* v;
199 | T s;
200 | };
201 |
202 | using Step4 = Relay;
203 | using Step5 = Relay;
204 | using Step6 = Relay;
205 |
206 | unsigned BatchSearch(const PackView& base, const PackView& patch,
207 | unsigned batch, const uint8_t* const keys[], const uint8_t* out[]) {
208 | if ((base.type != Type::KV_INLINE && base.type != Type::KEY_SET)
209 | || base.type != patch.type || base.key_len != patch.key_len) {
210 | return 0;
211 | }
212 |
213 | unsigned hit = 0;
214 | Pipeline<4>(batch,
215 | [&patch, keys](unsigned i) -> Step1 {
216 | return Process1(patch, keys[i]);
217 | },
218 | [](const Step1& in, unsigned) -> Step2 {
219 | return Process2(in);
220 | },
221 | [&patch](const Step2& in, unsigned) -> Step3 {
222 | return Process3(patch, in);
223 | },
224 | [&base, &patch, keys](const Step3& in, unsigned i) -> Step4 {
225 | if (LIKELY(in.line != nullptr) && Equal(keys[i], in.line, patch.key_len)) {
226 | return {in.line + patch.key_len, Step1{}};
227 | } else {
228 | return {nullptr, Process1(base, keys[i])};
229 | }
230 | },
231 | [](const Step4& in, unsigned) -> Step5 {
232 | if (in.v != nullptr) {
233 | return {in.v, Step2{}};
234 | } else {
235 | return {nullptr, Process2(in.s)};
236 | }
237 | },
238 | [&base](const Step5& in, unsigned) -> Step6 {
239 | if (in.v != nullptr) {
240 | return {in.v, Step3{}};
241 | } else {
242 | return {nullptr, Process3(base, in.s)};
243 | }
244 | },
245 | [&base, &hit, keys, out](const Step6& in, unsigned i) {
246 | if (in.v != nullptr) {
247 | hit++;
248 | out[i] = in.v;
249 | } else if (LIKELY(in.s.line != nullptr) && Equal(keys[i], in.s.line, base.key_len)) {
250 | hit++;
251 | out[i] = in.s.line + base.key_len;
252 | } else {
253 | out[i] = nullptr;
254 | }
255 | }
256 | );
257 | return hit;
258 | }
259 |
260 | unsigned BatchFetch(const PackView& base, const PackView& patch, const uint8_t* __restrict__ dft_val, unsigned batch,
261 | const uint8_t* __restrict__ keys, uint8_t* __restrict__ data, unsigned* __restrict__ miss) {
262 | if (base.type != Type::KV_INLINE || base.type != patch.type
263 | || base.key_len != patch.key_len || base.val_len != patch.val_len) {
264 | return 0;
265 | }
266 |
267 | unsigned hit = 0;
268 | Pipeline<3>(batch,
269 | [&patch, keys](unsigned i) -> Step1 {
270 | auto key = keys+i*patch.key_len;
271 | return Process1(patch, key);
272 | },
273 | [](const Step1& in, unsigned) -> Step2 {
274 | return Process2(in);
275 | },
276 | [&patch](const Step2& in, unsigned) -> Step3 {
277 | return Process3(patch, in, true);
278 | },
279 | [&base, &patch, keys](const Step3& in, unsigned i) -> Step4 {
280 | auto key = keys + i*base.key_len;
281 | if (LIKELY(in.line != nullptr) && Equal(key, in.line, patch.key_len)) {
282 | return {in.line + patch.key_len, Step1{}};
283 | } else {
284 | return {nullptr, Process1(base, key)};
285 | }
286 | },
287 | [](const Step4& in, unsigned) -> Step5 {
288 | if (in.v != nullptr) {
289 | return {in.v, Step2{}};
290 | } else {
291 | return {nullptr, Process2(in.s)};
292 | }
293 | },
294 | [&base](const Step5& in, unsigned) -> Step6 {
295 | if (in.v != nullptr) {
296 | return {in.v, Step3{}};
297 | } else {
298 | return {nullptr, Process3(base, in.s, true)};
299 | }
300 | },
301 | [&base, &hit, keys, data, dft_val, &miss](const Step6& in, unsigned i) {
302 | auto key = keys + i*base.key_len;
303 | auto out = data + i*base.val_len;
304 | auto src = in.v;
305 | if (src != nullptr) {
306 | hit++;
307 | } else if (LIKELY(in.s.line != nullptr) && Equal(key, in.s.line, base.key_len)) {
308 | hit++;
309 | src = in.s.line + base.key_len;
310 | } else if (dft_val != nullptr) {
311 | src = dft_val;
312 | } else if (miss != nullptr) {
313 | *miss++ = i;
314 | } else {
315 | return;
316 | }
317 | memcpy(out, src, base.val_len);
318 | }
319 | );
320 | return hit;
321 | }
322 |
323 | static constexpr unsigned WINDOW_SIZE = 32;
324 |
325 | void BatchFindPos(const PackView& pack, size_t batch, const std::function& reader,
326 | const std::function& output, const uint8_t* bitmap) {
327 | if (pack.type == Type::INDEX_ONLY) return;
328 | auto buf = std::make_unique(WINDOW_SIZE*pack.key_len);
329 |
330 | union {
331 | Step1 s1;
332 | Step2 s2;
333 | struct {
334 | uint64_t pos;
335 | const uint8_t* line;
336 | } s3;
337 | } state[WINDOW_SIZE];
338 |
339 | for (size_t i = 0; i < batch; i += WINDOW_SIZE) {
340 | auto m = std::min(static_cast(WINDOW_SIZE), batch-i);
341 | for (unsigned j = 0; j < m; j++) {
342 | auto key = buf.get() + j * pack.key_len;
343 | reader(key);
344 | state[j].s1 = Process1(pack, key);
345 | }
346 | for (unsigned j = 0; j < m; j++) {
347 | state[j].s2 = Process2(state[j].s1);
348 | }
349 | for (unsigned j = 0; j < m; j++) {
350 | auto pos = CalcPos(state[j].s2);
351 | assert(pos < pack.item);
352 | auto line = pack.content + pos*pack.line_size;
353 | PrefetchForNext(line);
354 | if (bitmap != nullptr) {
355 | PrefetchBit(bitmap,pos);
356 | }
357 | state[j].s3 = {pos, line};
358 | }
359 | for (unsigned j = 0; j < m; j++) {
360 | auto key = buf.get() + j * pack.key_len;
361 | auto& s = state[j].s3;
362 | if (Equal(key, s.line, pack.key_len)) {
363 | output(s.pos);
364 | } else {
365 | output(UINT64_MAX);
366 | }
367 | }
368 | }
369 | }
370 |
371 | void BatchDataMapping(const PackView& index, uint8_t* space, size_t batch, const std::function& reader) {
372 | auto buf = std::make_unique(WINDOW_SIZE*index.line_size);
373 |
374 | union {
375 | Step1 s1;
376 | Step2 s2;
377 | struct {
378 | uint8_t* line;
379 | } s3;
380 | } state[WINDOW_SIZE];
381 |
382 | for (size_t i = 0; i < batch; i += WINDOW_SIZE) {
383 | auto m = std::min(static_cast(WINDOW_SIZE), batch - i);
384 | for (unsigned j = 0; j < m; j++) {
385 | auto line = buf.get() + j * index.line_size;
386 | reader(line);
387 | state[j].s1 = Process1(index, line);
388 | }
389 | for (unsigned j = 0; j < m; j++) {
390 | state[j].s2 = Process2(state[j].s1);
391 | }
392 | for (unsigned j = 0; j < m; j++) {
393 | auto line = space + CalcPos(state[j].s2)*index.line_size;
394 | PrefetchForWrite(line);
395 | auto off = (uintptr_t)line & (CACHE_BLOCK_SIZE-1);
396 | auto blk = (const void*)(((uintptr_t)line & ~(uintptr_t)(CACHE_BLOCK_SIZE-1)) + CACHE_BLOCK_SIZE);
397 | if (off + index.line_size > CACHE_BLOCK_SIZE) {
398 | PrefetchForWrite(blk);
399 | }
400 | state[j].s3.line = line;
401 | }
402 | for (unsigned j = 0; j < m; j++) {
403 | auto line = buf.get() + j * index.line_size;
404 | auto& s = state[j].s3;
405 | memcpy(s.line, line, index.line_size);
406 | }
407 | }
408 | }
409 |
410 | } //shd
411 |
--------------------------------------------------------------------------------
/src/shd.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 | #include
21 | #include "internal.h"
22 | #include "shd.h"
23 |
24 |
25 | namespace shd {
26 |
27 | std::unique_ptr CreatePackView(const uint8_t* addr, size_t size) {
28 | size_t addr_off = sizeof(Header);
29 | if (size < addr_off) return nullptr;
30 |
31 | auto header = (const Header*)addr;
32 | if (header->magic != SHD_MAGIC) {
33 | return nullptr;
34 | }
35 | switch (header->type) {
36 | case PerfectHashtable::KV_SEPARATED: if (header->val_len != OFFSET_FIELD_SIZE) return nullptr;
37 | case PerfectHashtable::KV_INLINE: if (header->val_len == 0) return nullptr;
38 | case PerfectHashtable::KEY_SET: if (header->key_len == 0) return nullptr;
39 | case PerfectHashtable::INDEX_ONLY: break;
40 | default: return nullptr;
41 | }
42 |
43 | if (header->seg_cnt == 0 || header->seg_cnt > MAX_SEGMENT) {
44 | return nullptr;
45 | }
46 | const auto parts = (const uint32_t*)(addr + addr_off);
47 | addr_off += header->seg_cnt*4U;
48 | if (size < addr_off) return nullptr;
49 |
50 | auto view = std::make_unique(sizeof(PackView) + sizeof(SegmentView) * header->seg_cnt);
51 | auto index = (PackView*)view.get();
52 | *index = PackView{};
53 | index->type = (Type)header->type;
54 | index->key_len = header->key_len;
55 | index->val_len = header->val_len;
56 | index->line_size = ((uint32_t)index->key_len) + index->val_len;
57 | index->seed = header->seed;
58 | index->l0sz = header->seg_cnt;
59 | index->item = ((((uint64_t)header->item_high)<<32U) | header->item);
60 |
61 | uint64_t total_item = 0;
62 | for (unsigned i = 0; i < header->seg_cnt; i++) {
63 | index->segments[i] = SegmentView{};
64 | index->segments[i].l1bd = L1Band(parts[i]);
65 | index->segments[i].l2sz = L2Size(parts[i]);
66 | index->segments[i].offset = total_item;
67 | total_item += parts[i];
68 | index->segments[i].cells = addr + addr_off;
69 | addr_off += L1Size(parts[i]);
70 | if (size < addr_off) return nullptr;
71 | }
72 | if (total_item != index->item) {
73 | return nullptr;
74 | }
75 | addr_off = (addr_off+31U)&(~31U);
76 | if (size < addr_off) return nullptr;
77 | for (unsigned i = 0; i < header->seg_cnt; i++) {
78 | index->segments[i].sections = (const BitmapSection*)(addr + addr_off);
79 | addr_off += SectionSize(parts[i]) * (size_t)sizeof(BitmapSection);
80 | if (size < addr_off) return nullptr;
81 | }
82 |
83 | if (header->type != PerfectHashtable::INDEX_ONLY) {
84 | index->content = addr + addr_off;
85 | addr_off += index->line_size * total_item;
86 | if (size < addr_off) return nullptr;
87 | if (header->type == PerfectHashtable::KV_SEPARATED) {
88 | index->extend = addr + addr_off;
89 | if (size < addr_off + total_item*2U) return nullptr;
90 | }
91 | }
92 | index->space_end = addr + size;
93 |
94 | return view;
95 | }
96 |
97 | PerfectHashtable::PerfectHashtable(const std::string& path, LoadPolicy load_policy) {
98 | if (load_policy == COPY_DATA) {
99 | auto mem = MemBlock::LoadFile(path.c_str());
100 | if (!mem) {
101 | return;
102 | }
103 | auto view = CreatePackView(mem.addr(), mem.size());
104 | if (view == nullptr) {
105 | return;
106 | }
107 | m_mem = std::move(mem);
108 | m_view = std::move(view);
109 | } else {
110 | MemMap::Policy policy = MemMap::MAP_ONLY;
111 | if (load_policy == MAP_FETCH) {
112 | policy = MemMap::FETCH;
113 | } else if (load_policy == MAP_OCCUPY) {
114 | policy = MemMap::OCCUPY;
115 | }
116 | MemMap res(path.c_str(), policy);
117 | if (!res) {
118 | return;
119 | }
120 | auto view = CreatePackView(res.addr(), res.size());
121 | if (view == nullptr) {
122 | return;
123 | }
124 | m_res = std::move(res);
125 | m_view = std::move(view);
126 | }
127 | _post_init();
128 | }
129 |
130 | void PerfectHashtable::_post_init() noexcept {
131 | auto index = (const PackView*)m_view.get();
132 | m_type = index->type;
133 | m_key_len = index->key_len;
134 | if (index->type == KV_SEPARATED) {
135 | m_val_len = 0;
136 | } else {
137 | m_val_len = index->val_len;
138 | }
139 | m_item = index->item;
140 | }
141 |
142 | PerfectHashtable::PerfectHashtable(size_t size, const std::function& load) {
143 | auto mem = MemBlock(size);
144 | if (!mem || !load(mem.addr())) {
145 | return;
146 | }
147 | auto view = CreatePackView(mem.addr(), mem.size());
148 | if (view == nullptr) {
149 | return;
150 | }
151 | m_mem = std::move(mem);
152 | m_view = std::move(view);
153 | _post_init();
154 | }
155 |
156 | size_t PerfectHashtable::locate(const uint8_t* key, uint8_t key_len) const noexcept {
157 | auto index = (const PackView*)m_view.get();
158 | if (UNLIKELY(index == nullptr || key == nullptr || key_len == 0)) {
159 | return 0;
160 | }
161 | return CalcPos(*index, key, key_len);
162 | }
163 |
164 | void PerfectHashtable::batch_locate(unsigned batch, const uint8_t* __restrict__ keys,
165 | uint8_t key_len, uint64_t* __restrict__ out) {
166 | auto index = (const PackView*)m_view.get();
167 | if (UNLIKELY(index == nullptr || keys == nullptr || key_len == 0
168 | || (index->type != INDEX_ONLY && key_len != index->key_len))) {
169 | return;
170 | }
171 | return BatchLocate(*index, batch, keys, key_len, out);
172 | }
173 |
174 | Slice SeparatedValue(const uint8_t* pt, const uint8_t* end) {
175 | static_assert(MAX_VALUE_LEN_BIT % 7U == 0, "MAX_VALUE_LEN_BIT should be 7x");
176 |
177 | uint64_t len = 0;
178 | for (unsigned sft = 0; sft < MAX_VALUE_LEN_BIT; sft += 7U) {
179 | if (pt >= end) {
180 | return {};
181 | }
182 | uint8_t b = *pt++;
183 | if (b & 0x80U) {
184 | len |= static_cast(b & 0x7fU) << sft;
185 | } else {
186 | len |= static_cast(b) << sft;
187 | if (pt+len > end) {
188 | return {};
189 | }
190 | return {pt, len};
191 | }
192 | }
193 | return {};
194 | }
195 |
196 | Slice PerfectHashtable::search(const uint8_t* key) const noexcept {
197 | auto pack = (const PackView*)m_view.get();
198 | if (UNLIKELY(pack == nullptr || key == nullptr || pack->type == INDEX_ONLY)) {
199 | return {};
200 | }
201 | auto pos = CalcPos(*pack, key, pack->key_len);
202 | auto line = pack->content + pos*pack->line_size;
203 | if (UNLIKELY(pos >= pack->item) || !Equal(line, key, pack->key_len)) {
204 | return {};
205 | }
206 | auto field = line + pack->key_len;
207 | if (pack->type != KV_SEPARATED) {
208 | return {field, pack->val_len};
209 | }
210 | return SeparatedValue(pack->extend+ReadOffsetField(field), pack->space_end);
211 | }
212 |
213 | unsigned PerfectHashtable::batch_search(unsigned batch, const uint8_t* const keys[], const uint8_t* out[],
214 | const PerfectHashtable* patch) const noexcept {
215 | auto base = (const PackView*)m_view.get();
216 | if (base == nullptr || keys == nullptr || out == nullptr) {
217 | return 0;
218 | }
219 | if (patch == nullptr) {
220 | return BatchSearch(*base, batch, keys, out);
221 | } else {
222 | auto delta = (const PackView*)patch->m_view.get();
223 | if (delta == nullptr) {
224 | return 0;
225 | }
226 | return BatchSearch(*base, *delta, batch, keys, out);
227 | }
228 | }
229 |
230 | unsigned PerfectHashtable::batch_fetch(unsigned batch, const uint8_t* __restrict__ keys, uint8_t* __restrict__ data,
231 | const uint8_t* __restrict__ dft_val, const PerfectHashtable* patch) const noexcept {
232 | auto base = (const PackView*)m_view.get();
233 | if (base == nullptr || keys == nullptr || data == nullptr) {
234 | return 0;
235 | }
236 | if (patch == nullptr) {
237 | return BatchFetch(*base, dft_val, batch, keys, data, nullptr);
238 | } else {
239 | auto delta = (const PackView*)patch->m_view.get();
240 | if (delta == nullptr) {
241 | return 0;
242 | }
243 | return BatchFetch(*base, *delta, dft_val, batch, keys, data, nullptr);
244 | }
245 | }
246 |
247 | unsigned PerfectHashtable::batch_try_fetch(unsigned batch, const uint8_t* __restrict__ keys, uint8_t* __restrict__ data,
248 | unsigned* __restrict__ miss, const PerfectHashtable* patch) const noexcept {
249 | auto base = (const PackView*)m_view.get();
250 | if (base == nullptr || keys == nullptr || data == nullptr) {
251 | return 0;
252 | }
253 | if (patch == nullptr) {
254 | return BatchFetch(*base, nullptr, batch, keys, data, miss);
255 | } else {
256 | auto delta = (const PackView*)patch->m_view.get();
257 | if (delta == nullptr) {
258 | return 0;
259 | }
260 | return BatchFetch(*base, *delta, nullptr, batch, keys, data, miss);
261 | }
262 | }
263 |
264 | BuildStatus PerfectHashtable::derive(const DataReaders& in, IDataWriter& out, Retry retry) const {
265 | auto base = (const PackView*)m_view.get();
266 | if (base == nullptr || base->type == INDEX_ONLY) {
267 | return BUILD_STATUS_BAD_INPUT;
268 | }
269 | return Rebuild(*base, in, out, retry);
270 | }
271 |
272 | } //shd
273 |
--------------------------------------------------------------------------------
/src/utils.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 |
29 | namespace shd {
30 |
31 | struct DefaultLogger : public Logger {
32 | void printf(const char* format, va_list args) override;
33 | static DefaultLogger instance;
34 | };
35 | void DefaultLogger::printf(const char *format, va_list args) {
36 | ::vfprintf(stderr, format, args);
37 | }
38 | DefaultLogger DefaultLogger::instance;
39 | Logger* Logger::s_instance = &DefaultLogger::instance;
40 |
41 | void Logger::Printf(const char* format, ... ) {
42 | if (s_instance != nullptr) {
43 | va_list args;
44 | va_start(args, format);
45 | s_instance->printf(format, args);
46 | va_end(args);
47 | }
48 | }
49 |
50 | static inline constexpr size_t RoundUp(size_t n) {
51 | constexpr size_t m = 0x1fffff;
52 | return (n+m)&(~m);
53 | };
54 |
55 | MemBlock::MemBlock(size_t size) noexcept : MemBlock() {
56 | if (size == 0) {
57 | return;
58 | }
59 | if (size >= 0x4000000) {
60 | auto round_up_size = RoundUp(size);
61 | void* addr = mmap(nullptr, round_up_size, PROT_READ | PROT_WRITE,
62 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
63 | if (addr == MAP_FAILED && errno == ENOMEM) {
64 | addr = mmap(nullptr, round_up_size, PROT_READ | PROT_WRITE,
65 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
66 | }
67 | if (addr != MAP_FAILED) {
68 | m_addr = static_cast(addr);
69 | m_size = size;
70 | m_mmap = 1;
71 | if (madvise(addr, round_up_size, MADV_DONTDUMP) != 0) {
72 | Logger::Printf("fail to madvise[%d]: %p | %lu\n", errno, addr, round_up_size);
73 | }
74 | return;
75 | }
76 | }
77 | m_addr = static_cast(malloc(size));
78 | if (m_addr != nullptr) {
79 | m_size = size;
80 | }
81 | }
82 |
83 | MemBlock::~MemBlock() noexcept {
84 | if (m_addr != nullptr) {
85 | if (m_mmap) {
86 | if (munmap(m_addr, RoundUp(m_size)) != 0) {
87 | Logger::Printf("fail to munmap[%d]: %p | %lu\n", errno, m_addr, m_size);
88 | };
89 | } else {
90 | free(m_addr);
91 | }
92 | }
93 | }
94 |
95 | static MemBlock LoadAll(int fd) noexcept {
96 | struct stat stat;
97 | if (fstat(fd, &stat) != 0 || stat.st_size <= 0) {
98 | return {};
99 | }
100 | MemBlock out(stat.st_size);
101 | if (!out) {
102 | return {};
103 | }
104 | auto data = out.addr();
105 | auto remain = out.size();
106 | constexpr size_t block = 16*1024*1024;
107 | size_t off = 0;
108 | while (remain > block) {
109 | auto next = off + block;
110 | readahead(fd, next, block);
111 | if (pread(fd, data, block, off) != block) {
112 | return {};
113 | }
114 | off = next;
115 | data += block;
116 | remain -= block;
117 | }
118 | if (pread(fd, data, remain, off) != remain) {
119 | return {};
120 | }
121 | return out;
122 | }
123 |
124 | MemBlock MemBlock::LoadFile(const char* path) noexcept {
125 | int fd = open(path, O_RDONLY);
126 | if (fd < 0) {
127 | Logger::Printf("fail to open file: %s\n", path);
128 | return {};
129 | }
130 | auto out = LoadAll(fd);
131 | close(fd);
132 | if (!out) {
133 | Logger::Printf("fail to read whole file: %s\n", path);
134 | }
135 | return out;
136 | }
137 |
138 |
139 | MemMap::MemMap(const char* path, Policy policy) noexcept {
140 | int fd = open(path, O_RDONLY);
141 | if (fd < 0) {
142 | Logger::Printf("fail to open file: %s\n", path);
143 | return;
144 | }
145 | struct stat stat;
146 | if (fstat(fd, &stat) != 0 || stat.st_size <= 0) {
147 | close(fd);
148 | return;
149 | }
150 | int flag = MAP_PRIVATE;
151 | if (policy != MAP_ONLY) {
152 | flag |= MAP_POPULATE;
153 | }
154 | if (policy == OCCUPY && geteuid() == 0) {
155 | flag |= MAP_LOCKED;
156 | }
157 | auto addr = mmap(nullptr, stat.st_size, PROT_READ, flag, fd, 0);
158 | close(fd);
159 | if (addr == MAP_FAILED) {
160 | return;
161 | }
162 | m_addr = static_cast(addr);
163 | m_size = stat.st_size;
164 | }
165 |
166 | MemMap::~MemMap() noexcept {
167 | if (m_addr != nullptr) {
168 | if (munmap(m_addr, m_size) != 0) {
169 | Logger::Printf("fail to munmap[%d]: %p | %lu\n", errno, m_addr, m_size);
170 | };
171 | }
172 | }
173 |
174 | FileWriter::FileWriter(const char* path) {
175 | m_fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644);
176 | if (m_fd < 0) {
177 | return;
178 | }
179 | m_buf = std::make_unique(BUFSZ);
180 | }
181 | FileWriter::~FileWriter() noexcept {
182 | if (m_fd >= 0) {
183 | _flush();
184 | ::close(m_fd);
185 | }
186 | }
187 | bool FileWriter::operator!() const noexcept {
188 | return m_fd < 0;
189 | }
190 |
191 | bool FileWriter::_write(const void* data, size_t n) noexcept {
192 | constexpr size_t block = 16*1024*1024;
193 | while (n > block) {
194 | if (::write(m_fd, data, block) != block) {
195 | ::close(m_fd);
196 | m_fd = -1;
197 | return false;
198 | }
199 | n -= block;
200 | data = (uint8_t*)data + block;
201 | }
202 | if (::write(m_fd, data, n) != n) {
203 | ::close(m_fd);
204 | m_fd = -1;
205 | return false;
206 | }
207 | return true;
208 | }
209 |
210 | bool FileWriter::flush() noexcept {
211 | if (m_fd < 0) {
212 | return false;
213 | }
214 | return _flush();
215 | }
216 | bool FileWriter::_flush() noexcept {
217 | if (m_off == 0) {
218 | return true;
219 | }
220 | auto n = m_off;
221 | m_off = 0;
222 | return _write(m_buf.get(), n);
223 | }
224 |
225 | bool FileWriter::write(const void* data, size_t n) noexcept {
226 | if (m_fd < 0) {
227 | return false;
228 | }
229 | if (m_off + n < BUFSZ) {
230 | memcpy(m_buf.get()+m_off, data, n);
231 | m_off += n;
232 | } else if (m_off + n < BUFSZ*2) {
233 | auto m = BUFSZ - m_off;
234 | memcpy(m_buf.get()+m_off, data, m);
235 | if (!_write(m_buf.get(), BUFSZ)) {
236 | return false;
237 | }
238 | m_off = n - m;
239 | memcpy(m_buf.get(), (const uint8_t*)data+m, m_off);
240 | } else {
241 | return _flush() && _write(data, n);
242 | }
243 | return true;
244 | }
245 |
246 | } //shd
--------------------------------------------------------------------------------
/test/test.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 |
21 | int main(int argc,char **argv){
22 | testing::InitGoogleTest(&argc,argv);
23 | return RUN_ALL_TESTS();
24 | }
--------------------------------------------------------------------------------
/test/test.h:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #pragma once
20 |
21 | #include
22 | #include
23 |
24 | class EmbeddingGenerator : public shd::IDataReader {
25 | public:
26 | static constexpr uint64_t MASK0 = 0xaaaaaaaaaaaaaaaaUL;
27 | static constexpr uint64_t MASK1 = 0x5555555555555555UL;
28 | explicit EmbeddingGenerator(uint64_t begin, uint64_t total, uint64_t mask=MASK0)
29 | : m_current(begin-1), m_begin(begin), m_total(total), m_mask(mask)
30 | {}
31 | EmbeddingGenerator(const EmbeddingGenerator&) = delete;
32 | EmbeddingGenerator& operator=(const EmbeddingGenerator&) = delete;
33 |
34 | void reset() override {
35 | m_current = m_begin-1;
36 | }
37 | size_t total() override {
38 | return m_total;
39 | }
40 | shd::Record read(bool) override {
41 | m_current++;
42 | auto arr = (uint64_t*)m_val;
43 | arr[0] = m_current ^ m_mask;
44 | arr[1] = m_current ^ m_mask;
45 | arr[2] = m_current ^ m_mask;
46 | arr[3] = m_current ^ m_mask;
47 | return {{(const uint8_t*)&m_current, sizeof(uint64_t)}, {m_val, VALUE_SIZE}};
48 | }
49 | static constexpr unsigned VALUE_SIZE = 32; //fp16 * 16
50 |
51 | private:
52 | uint64_t m_current;
53 | uint8_t m_val[VALUE_SIZE];
54 | const uint64_t m_begin;
55 | const uint64_t m_total;
56 | const uint64_t m_mask;
57 | };
58 |
59 | class VariedValueGenerator : public shd::IDataReader {
60 | public:
61 | explicit VariedValueGenerator(uint64_t begin, uint64_t total, unsigned shift=5U)
62 | : m_current(begin-1), m_begin(begin), m_total(total), m_shift(shift)
63 | {}
64 | VariedValueGenerator(const VariedValueGenerator&) = delete;
65 | VariedValueGenerator& operator=(const VariedValueGenerator&) = delete;
66 |
67 | void reset() override {
68 | m_current = m_begin-1;
69 | }
70 | size_t total() override {
71 | return m_total;
72 | }
73 | shd::Record read(bool) override {
74 | m_current++;
75 | const uint8_t len = m_current + m_shift;
76 | memset(m_val, len, len);
77 | return {{(const uint8_t*)&m_current, sizeof(uint64_t)}, {m_val, len}};
78 | }
79 |
80 | private:
81 | uint64_t m_current;
82 | uint8_t m_val[UINT8_MAX];
83 | const uint64_t m_begin;
84 | const uint64_t m_total;
85 | const unsigned m_shift;
86 | };
87 |
88 |
89 | class FakeWriter : public shd::IDataWriter {
90 | public:
91 | bool operator!() const noexcept override;
92 | bool flush() noexcept override;
93 | bool write(const void*, size_t) noexcept override;
94 | };
95 |
96 | bool FakeWriter::operator!() const noexcept {
97 | return false;
98 | }
99 | bool FakeWriter::flush() noexcept {
100 | return true;
101 | }
102 | bool FakeWriter::write(const void *, size_t) noexcept {
103 | return true;
104 | }
--------------------------------------------------------------------------------
/test/test_bbf.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Block Bloom Filter with 3.5% false positive rate
3 | // Copyright (C) 2025 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | TEST(BBF, SetAndTest) {
25 | bbf::BloomFilter bf(999);
26 | ASSERT_FALSE(!bf);
27 | ASSERT_EQ(1000, bf.capacity());
28 |
29 | for (unsigned i = 0; i < 500; i++) {
30 | ASSERT_TRUE(bf.set(reinterpret_cast(&i), sizeof(unsigned)));
31 | }
32 | ASSERT_EQ(500, bf.item());
33 | std::vector keys(500);
34 | for (unsigned i = 0; i < 500; i++) {
35 | keys[i] = i + 1000;
36 | }
37 | bf.batch_set(keys.size(), sizeof(unsigned), reinterpret_cast(keys.data()));
38 | ASSERT_LE(bf.item(), 1000);
39 | ASSERT_GE(bf.item(), 990);
40 |
41 | for (unsigned i = 0; i < 500; i++) {
42 | ASSERT_TRUE(bf.test(reinterpret_cast(&i), sizeof(unsigned)));
43 | }
44 |
45 | for (unsigned i = 0; i < 500; i++) {
46 | keys[i] = i * 2;
47 | }
48 | std::vector result(keys.size());
49 | unsigned hit = bf.batch_test(keys.size(), sizeof(unsigned),
50 | reinterpret_cast(keys.data()),
51 | reinterpret_cast(result.data()));
52 | for (unsigned i = 0; i < 250; i++) {
53 | ASSERT_TRUE(result[i]);
54 | }
55 | ASSERT_FALSE(result.back());
56 | ASSERT_GE(hit, 250);
57 | ASSERT_LE(hit, 260);
58 | }
59 |
60 | TEST(BBF, DumpAndLoad) {
61 | bbf::BloomFilter bf1(999);
62 | ASSERT_FALSE(!bf1);
63 | for (unsigned i = 0; i < 500; i++) {
64 | ASSERT_TRUE(bf1.set(reinterpret_cast(&i), sizeof(unsigned)));
65 | }
66 |
67 | const std::string filename = "tmp.bbf";
68 | {
69 | shd::FileWriter output(filename.c_str());
70 | ASSERT_TRUE(bf1.dump(output));
71 | }
72 |
73 | bbf::BloomFilter bf2(filename);
74 | ASSERT_FALSE(!bf2);
75 | for (unsigned i = 0; i < 500; i++) {
76 | ASSERT_TRUE(bf2.test(reinterpret_cast(&i), sizeof(unsigned)));
77 | }
78 | }
--------------------------------------------------------------------------------
/test/test_shd.cc:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | // Skew Hash and Displace Algorithm.
3 | // Copyright (C) 2020 Ruan Kunliang
4 | //
5 | // This library is free software; you can redistribute it and/or modify it under
6 | // the terms of the GNU Lesser General Public License as published by the Free
7 | // Software Foundation; either version 2.1 of the License, or (at your option)
8 | // any later version.
9 | //
10 | // This library is distributed in the hope that it will be useful, but WITHOUT
11 | // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 | // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 | // details.
14 | //
15 | // You should have received a copy of the GNU Lesser General Public License
16 | // along with the This Library; if not, see .
17 | //==============================================================================
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include "test.h"
26 |
27 | static constexpr unsigned PIECE = 1000;
28 |
29 | template
30 | static shd::DataReaders CreateReaders(unsigned n, Tips tips) {
31 | shd::DataReaders out;
32 | out.reserve(n);
33 | for (unsigned i = 0; i < n; i++) {
34 | out.push_back(std::make_unique(i*PIECE, PIECE, tips));
35 | }
36 | return out;
37 | }
38 |
39 | TEST(SHD, Build) {
40 | FakeWriter fake_output;
41 |
42 | shd::DataReaders fake_input;
43 | ASSERT_EQ(shd::BuildSet(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
44 | ASSERT_EQ(shd::BuildDict(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
45 | ASSERT_EQ(shd::BuildDictWithVariedValue(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
46 |
47 | fake_input.push_back(std::make_unique(0, 0));
48 | ASSERT_EQ(shd::BuildSet(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
49 | ASSERT_EQ(shd::BuildDict(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
50 | ASSERT_EQ(shd::BuildDictWithVariedValue(fake_input, fake_output), shd::BUILD_STATUS_BAD_INPUT);
51 |
52 | fake_input.push_back(std::make_unique(0, 1));
53 | ASSERT_EQ(shd::BuildSet(fake_input, fake_output), shd::BUILD_STATUS_OK);
54 | ASSERT_EQ(shd::BuildDict(fake_input, fake_output), shd::BUILD_STATUS_OK);
55 | ASSERT_EQ(shd::BuildDictWithVariedValue(fake_input, fake_output), shd::BUILD_STATUS_OK);
56 |
57 | auto emb_gen = CreateReaders(1, EmbeddingGenerator::MASK0);
58 | ASSERT_EQ(shd::BuildSet(emb_gen, fake_output), shd::BUILD_STATUS_OK);
59 | ASSERT_EQ(shd::BuildDict(emb_gen, fake_output), shd::BUILD_STATUS_OK);
60 | ASSERT_EQ(shd::BuildDictWithVariedValue(emb_gen, fake_output), shd::BUILD_STATUS_OK);
61 |
62 | auto var_gen = CreateReaders(1, 5U);
63 | ASSERT_EQ(shd::BuildDict(var_gen, fake_output), shd::BUILD_STATUS_BAD_INPUT);
64 | ASSERT_EQ(shd::BuildDictWithVariedValue(var_gen, fake_output), shd::BUILD_STATUS_OK);
65 |
66 | emb_gen = CreateReaders(3, EmbeddingGenerator::MASK0);
67 | ASSERT_EQ(shd::BuildSet(emb_gen, fake_output), shd::BUILD_STATUS_OK);
68 | ASSERT_EQ(shd::BuildDict(emb_gen, fake_output), shd::BUILD_STATUS_OK);
69 |
70 | var_gen = CreateReaders(3, 5U);
71 | ASSERT_EQ(shd::BuildDictWithVariedValue(var_gen, fake_output), shd::BUILD_STATUS_OK);
72 | }
73 |
74 | TEST(SHD, KeySet) {
75 | const std::string filename = "keyset.shd";
76 | {
77 | shd::FileWriter output(filename.c_str());
78 | auto input = CreateReaders(2, EmbeddingGenerator::MASK0);
79 | ASSERT_EQ(shd::BuildSet(input, output), shd::BUILD_STATUS_OK);
80 | }
81 | shd::PerfectHashtable dict(filename);
82 | ASSERT_FALSE(!dict);
83 | ASSERT_EQ(dict.type(), shd::PerfectHashtable::KEY_SET);
84 | ASSERT_EQ(dict.key_len(), sizeof(uint64_t));
85 | ASSERT_EQ(dict.val_len(), 0);
86 | ASSERT_EQ(dict.item(), PIECE*2);
87 |
88 | union {
89 | uint64_t v;
90 | uint8_t p[8];
91 | } tmp;
92 | for (unsigned i = 0; i < PIECE*2; i++) {
93 | tmp.v = i;
94 | auto val = dict.search(tmp.p);
95 | ASSERT_NE(val.ptr, nullptr);
96 | ASSERT_EQ(val.len, 0);
97 | }
98 | for (unsigned i = PIECE*2; i < PIECE*3; i++) {
99 | tmp.v = i;
100 | auto val = dict.search(tmp.p);
101 | ASSERT_EQ(val.ptr, nullptr);
102 | ASSERT_EQ(val.len, 0);
103 | }
104 |
105 | std::vector keys(PIECE*2);
106 | for (unsigned i = 0; i < PIECE; i++) {
107 | keys[i*2] = i;
108 | keys[i*2+1] = PIECE*2+i;
109 | }
110 | std::vector in(keys.size());
111 | for (unsigned i = 0; i < keys.size(); i++) {
112 | in[i] = (const uint8_t*)&keys[i];
113 | }
114 | std::vector out(keys.size());
115 |
116 | ASSERT_EQ(dict.batch_search(keys.size(), in.data(), out.data()), PIECE);
117 | for (unsigned i = 0; i < PIECE; i++) {
118 | ASSERT_NE(out[i*2], nullptr);
119 | ASSERT_EQ(out[i*2+1], nullptr);
120 | }
121 |
122 | ASSERT_EQ(dict.batch_fetch(keys.size(), (const uint8_t*)keys.data(), (uint8_t*)out.data()), 0);
123 | }
124 |
125 | TEST(SHD, SmallSet) {
126 | shd::DataReaders input(1);
127 | const uint64_t shift = 9999;
128 | const unsigned limit = 16;
129 | std::vector keys(limit);
130 | for (unsigned i = 0; i < limit; i++) {
131 | keys[i] = shift + i;
132 | }
133 | std::vector in(keys.size());
134 | for (unsigned i = 0; i < keys.size(); i++) {
135 | in[i] = (const uint8_t*)&keys[i];
136 | }
137 | std::vector out(keys.size());
138 | const std::string filename = "small.shd";
139 | for (unsigned i = 1; i < limit; i++) {
140 | input[0] = std::make_unique(shift, i);
141 | {
142 | shd::FileWriter output(filename.c_str());
143 | ASSERT_EQ(shd::BuildSet(input, output), shd::BUILD_STATUS_OK);
144 | }
145 | {
146 | shd::PerfectHashtable dict(filename);
147 | ASSERT_FALSE(!dict);
148 | for (auto& p : out) {
149 | p = nullptr;
150 | }
151 | ASSERT_EQ(dict.batch_search(keys.size(), in.data(), out.data()), i);
152 | for (unsigned j = 0; j < i; j++) {
153 | ASSERT_NE(out[j], nullptr);
154 | }
155 | for (unsigned j = i; j < limit; j++) {
156 | ASSERT_EQ(out[j], nullptr);
157 | }
158 | }
159 | }
160 | }
161 |
162 | TEST(SHD, InlinedDict) {
163 | const std::string filename = "dict.shd";
164 | {
165 | shd::FileWriter output(filename.c_str());
166 | auto input = CreateReaders(2, EmbeddingGenerator::MASK0);
167 | ASSERT_EQ(shd::BuildDict(input, output), shd::BUILD_STATUS_OK);
168 | }
169 | shd::PerfectHashtable dict(filename);
170 | ASSERT_FALSE(!dict);
171 | ASSERT_EQ(dict.type(), shd::PerfectHashtable::KV_INLINE);
172 | ASSERT_EQ(dict.key_len(), sizeof(uint64_t));
173 | ASSERT_EQ(dict.val_len(), EmbeddingGenerator::VALUE_SIZE);
174 | ASSERT_EQ(dict.item(), PIECE*2);
175 |
176 | EmbeddingGenerator checker(PIECE, PIECE*2);
177 |
178 | std::vector keys(PIECE*2);
179 |
180 | for (unsigned i = 0; i < PIECE; i++) {
181 | auto rec = checker.read(false);
182 | auto val = dict.search(rec.key.ptr);
183 | ASSERT_NE(val.ptr, nullptr);
184 | ASSERT_NE(val.ptr, rec.val.ptr);
185 | ASSERT_EQ(val.len, rec.val.len);
186 | ASSERT_EQ(memcmp(val.ptr, rec.val.ptr, rec.val.len), 0);
187 | auto key = *(const uint64_t*)rec.key.ptr;
188 | keys[i*2] = key;
189 | keys[i*2+1] = ~key;
190 | }
191 | for (unsigned i = 0; i < PIECE; i++) {
192 | auto rec = checker.read(false);
193 | auto val = dict.search(rec.key.ptr);
194 | ASSERT_EQ(val.ptr, nullptr);
195 | ASSERT_EQ(val.len, 0);
196 | }
197 |
198 | std::vector in(keys.size());
199 | for (unsigned i = 0; i < keys.size(); i++) {
200 | in[i] = (const uint8_t*)&keys[i];
201 | }
202 | std::vector