swiss_hash 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +33 -31
- data/ext/swiss_hash/swiss_hash.c +521 -204
- data/lib/swiss_hash/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b9d4f872535c0a7e7558f6c7950a2fb113263ab9b75cd2397bff72ee4185c295
|
|
4
|
+
data.tar.gz: ad110fb4e54d662181d3e49546eec833a953be7b87debae477837ad8e50408a5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85cecddcb36ff970d62bd053bb78c7b42f0f15290276f64326ecc053236ca6ff473ed016933dd848ce9d9f4e5b63efdebb014140a84b22436e9bb9614567b4cf
|
|
7
|
+
data.tar.gz: 2cf36fed40554508684932d155bad203b4da18d9b808d2a8317b5f0e46b5a2e3dee9ebe3ab963ecdbd75406e99075d816d2dcf9b1277b562c6b421a845b1d8ec
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3 - 2026-08-18
|
|
4
|
+
|
|
5
|
+
- NEON group matching on ARM64 (SSE2 on x86_64, SWAR elsewhere)
|
|
6
|
+
- Single allocation for control bytes and slots
|
|
7
|
+
- Faster Fixnum `[]` / `[]=`
|
|
8
|
+
- 7-bit strings of different encodings hash like Ruby Hash (`"abc"` and `"abc".b` are the same key)
|
|
9
|
+
- Rehash is GC-safe; `#hash` / `#eql?` that mutate the same table raise instead of corrupting it
|
|
10
|
+
- `initialize` no longer leaks on reuse; OOM no longer leaves a half-built table
|
|
11
|
+
|
|
3
12
|
## 0.1.2 - Unreleased
|
|
4
13
|
|
|
5
14
|
Documentation and API polish for the first public release candidate.
|
data/README.md
CHANGED
|
@@ -27,52 +27,52 @@ h.stats # => { capacity: 16, size: 0, ... }
|
|
|
27
27
|
|
|
28
28
|
Benchmarks below were produced by `benchmark.rb` on Ruby 3.4.3 / arm64-darwin24.
|
|
29
29
|
|
|
30
|
-
Methodology:
|
|
30
|
+
Methodology: 6 runs × 17 measured iterations, 4 warmup iterations per run, IQR-filtered mean per run, interleaved Ruby/SwissHash measurements with alternating start order, and per-side coefficient of variation (`±X.X%`) reported to make noise visible.
|
|
31
31
|
|
|
32
32
|
### N = 100,000
|
|
33
33
|
|
|
34
34
|
| Operation | Ruby Hash | SwissHash | Delta |
|
|
35
35
|
|---|---:|---:|---:|
|
|
36
|
-
| Insert (sequential int) |
|
|
37
|
-
| Insert (string keys) |
|
|
38
|
-
| Insert (random int) |
|
|
39
|
-
| Lookup (sequential int, 3x) |
|
|
40
|
-
| Lookup (string keys, 3x) |
|
|
41
|
-
| Delete + reinsert 25% |
|
|
42
|
-
| Mixed (70% read / 20% write / 10% delete) |
|
|
36
|
+
| Insert (sequential int) | 7.004 ms (±2.4%) | 5.245 ms (±1.2%) | **−25.12%** ⚡ |
|
|
37
|
+
| Insert (string keys) | 17.751 ms (±1.0%) | 11.207 ms (±1.1%) | **−36.86%** ⚡ |
|
|
38
|
+
| Insert (random int) | 6.148 ms (±1.8%) | 4.845 ms (±0.2%) | **−21.20%** ⚡ |
|
|
39
|
+
| Lookup (sequential int, 3x) | 12.286 ms (±1.1%) | 11.614 ms (±1.1%) | **−5.47%** ⚡ |
|
|
40
|
+
| Lookup (string keys, 3x) | 22.558 ms (±0.6%) | 22.650 ms (±0.9%) | +0.41% |
|
|
41
|
+
| Delete + reinsert 25% | 9.517 ms (±0.8%) | 7.392 ms (±1.8%) | **−22.33%** ⚡ |
|
|
42
|
+
| Mixed (70% read / 20% write / 10% delete) | 25.333 ms (±2.0%) | 21.860 ms (±3.4%) | **−13.71%** ⚡ |
|
|
43
43
|
|
|
44
44
|
### N = 10,000
|
|
45
45
|
|
|
46
46
|
| Operation | Ruby Hash | SwissHash | Delta |
|
|
47
47
|
|---|---:|---:|---:|
|
|
48
|
-
| Insert (sequential int) | 0.
|
|
49
|
-
| Insert (string keys) | 1.
|
|
50
|
-
| Insert (random int) | 0.
|
|
51
|
-
| Lookup (sequential int, 3x) | 1.
|
|
52
|
-
| Lookup (string keys, 3x) | 1.
|
|
53
|
-
| Delete + reinsert 25% | 0.
|
|
54
|
-
| Mixed (70% read / 20% write / 10% delete) |
|
|
48
|
+
| Insert (sequential int) | 0.606 ms (±2.8%) | 0.518 ms (±1.3%) | **−14.57%** ⚡ |
|
|
49
|
+
| Insert (string keys) | 1.638 ms (±1.9%) | 1.057 ms (±1.2%) | **−35.49%** ⚡ |
|
|
50
|
+
| Insert (random int) | 0.583 ms (±1.5%) | 0.507 ms (±1.2%) | **−13.07%** ⚡ |
|
|
51
|
+
| Lookup (sequential int, 3x) | 1.108 ms (±1.3%) | 1.063 ms (±1.4%) | **−4.02%** ⚡ |
|
|
52
|
+
| Lookup (string keys, 3x) | 1.748 ms (±1.5%) | 1.623 ms (±1.3%) | **−7.12%** ⚡ |
|
|
53
|
+
| Delete + reinsert 25% | 0.859 ms (±2.2%) | 0.740 ms (±2.2%) | **−13.85%** ⚡ |
|
|
54
|
+
| Mixed (70% read / 20% write / 10% delete) | 2.123 ms (±1.1%) | 1.928 ms (±1.2%) | **−9.21%** ⚡ |
|
|
55
55
|
|
|
56
56
|
### N = 1,000
|
|
57
57
|
|
|
58
|
-
Ruby Hash uses an AR-table for small hashes, so very small integer-keyed workloads can still
|
|
58
|
+
Ruby Hash uses an AR-table for small hashes, so very small integer-keyed workloads can still be close. String-heavy workloads remain the strongest SwissHash case.
|
|
59
59
|
|
|
60
60
|
| Operation | Ruby Hash | SwissHash | Delta |
|
|
61
61
|
|---|---:|---:|---:|
|
|
62
|
-
| Insert (sequential int) | 0.
|
|
63
|
-
| Insert (string keys) | 0.
|
|
64
|
-
| Insert (random int) | 0.
|
|
65
|
-
| Lookup (sequential int, 3x) | 0.
|
|
66
|
-
| Lookup (string keys, 3x) | 0.
|
|
67
|
-
| Delete + reinsert 25% | 0.
|
|
68
|
-
| Mixed (70% read / 20% write / 10% delete) | 0.
|
|
62
|
+
| Insert (sequential int) | 0.059 ms (±3.2%) | 0.057 ms (±4.6%) | **−4.32%** ⚡ |
|
|
63
|
+
| Insert (string keys) | 0.167 ms (±3.3%) | 0.108 ms (±1.8%) | **−35.17%** ⚡ |
|
|
64
|
+
| Insert (random int) | 0.055 ms (±2.1%) | 0.052 ms (±2.2%) | **−6.03%** ⚡ |
|
|
65
|
+
| Lookup (sequential int, 3x) | 0.114 ms (±1.8%) | 0.111 ms (±2.3%) | **−2.15%** ⚡ |
|
|
66
|
+
| Lookup (string keys, 3x) | 0.184 ms (±1.3%) | 0.155 ms (±0.9%) | **−15.68%** ⚡ |
|
|
67
|
+
| Delete + reinsert 25% | 0.086 ms (±2.2%) | 0.080 ms (±2.5%) | **−6.91%** ⚡ |
|
|
68
|
+
| Mixed (70% read / 20% write / 10% delete) | 0.211 ms (±1.4%) | 0.202 ms (±2.0%) | **−4.66%** ⚡ |
|
|
69
69
|
|
|
70
70
|
### Summary
|
|
71
71
|
|
|
72
|
-
- SwissHash is faster on **6 of 7 operations** at N=100k
|
|
73
|
-
- The strongest win is still string-key insertion: **−
|
|
74
|
-
-
|
|
75
|
-
-
|
|
72
|
+
- SwissHash is faster on **6 of 7 operations** at N=100k; string lookup at that size is within 1% of Ruby Hash.
|
|
73
|
+
- The strongest win is still string-key insertion: **−35% to −37%** across tested sizes.
|
|
74
|
+
- Sequential integer insert at N=1k now beats Ruby Hash (it lost slightly in 0.1.2).
|
|
75
|
+
- ARM64 uses the NEON group-matching path (`stats[:simd] => "NEON"`).
|
|
76
76
|
- Ruby's built-in `Hash` remains excellent, especially for very small maps and cases that benefit from VM-level Hash specialization.
|
|
77
77
|
|
|
78
78
|
### Memory Usage
|
|
@@ -84,12 +84,13 @@ For 100,000 integer keys in the current benchmark:
|
|
|
84
84
|
| SwissHash | 2,176 KB native + 4 GC slots |
|
|
85
85
|
| Ruby Hash | 3 GC slots; native memory not directly measurable from this benchmark |
|
|
86
86
|
|
|
87
|
-
Additional stats: load factor 76.3%, max load factor 87.5%, SIMD path reported as
|
|
87
|
+
Additional stats: load factor 76.3%, max load factor 87.5%, SIMD path reported as NEON on the benchmarked Apple Silicon machine.
|
|
88
88
|
|
|
89
89
|
## Features
|
|
90
90
|
|
|
91
91
|
- **Swiss Table probing**: 7-bit `H2` metadata, group probing, triangular probe sequence, and 87.5% max load factor.
|
|
92
|
-
- **
|
|
92
|
+
- **SIMD group matching**: SSE2 on x86_64, NEON on aarch64, SWAR fallback elsewhere.
|
|
93
|
+
- **Fast string-key path**: wyhash for string keys, frozen string key preparation, 7-bit strings of different encodings hash/compare like Ruby Hash, and direct `memcmp` when encodings are compatible.
|
|
93
94
|
- **Low GC pressure**: keys and values are Ruby objects, while control bytes and slots live in contiguous native arrays.
|
|
94
95
|
- **Delete/reinsert friendly**: tombstones are tracked and compacted to avoid pathological slowdown.
|
|
95
96
|
- **Hash-like API**: basic accessors, enumeration, fetch helpers, merge/update/replace, filtering, transforming, slicing, inversion, and conversion helpers.
|
|
@@ -200,7 +201,7 @@ Stick with Ruby's built-in `Hash` when:
|
|
|
200
201
|
### Swiss Table core
|
|
201
202
|
|
|
202
203
|
- **Open addressing** with 7-bit `H2` metadata byte per slot; group matching rejects non-matching slots in batches.
|
|
203
|
-
- **Group size 16 on SSE2
|
|
204
|
+
- **Group size 16 on SSE2 (x86_64) and NEON (aarch64)**, **group size 8 on portable SWAR**. The active path is printed by `stats[:simd]` / the benchmark memory section (`NEON` on Apple Silicon).
|
|
204
205
|
- **Triangular probing** — `i(i+1)/2` — over power-of-two group counts.
|
|
205
206
|
- **Max load factor 87.5%** (7/8).
|
|
206
207
|
|
|
@@ -215,7 +216,7 @@ Stick with Ruby's built-in `Hash` when:
|
|
|
215
216
|
|
|
216
217
|
### Memory layout
|
|
217
218
|
|
|
218
|
-
-
|
|
219
|
+
- One native allocation for control bytes followed by slots (16-byte aligned). `stats[:memory_bytes]` still counts `capacity * (1 + sizeof(Slot))`.
|
|
219
220
|
- Native arrays are allocated outside Ruby's object heap; keys and values are still marked for GC.
|
|
220
221
|
- Slot memory is not zero-initialized on allocation; slots are read only after their control byte marks them live.
|
|
221
222
|
|
|
@@ -231,6 +232,7 @@ bundle exec rake compile
|
|
|
231
232
|
```bash
|
|
232
233
|
bundle exec ruby test/hash_api_test.rb
|
|
233
234
|
bundle exec ruby test/string_key_mutation_test.rb
|
|
235
|
+
bundle exec ruby test/safety_and_encoding_test.rb
|
|
234
236
|
```
|
|
235
237
|
|
|
236
238
|
## Benchmarking
|
data/ext/swiss_hash/swiss_hash.c
CHANGED
|
@@ -146,36 +146,77 @@ static inline uint64_t wyhash(const void *data, size_t len, uint64_t seed) {
|
|
|
146
146
|
#if defined(__x86_64__) || defined(_M_X64)
|
|
147
147
|
#define SWISS_USE_SSE2 1
|
|
148
148
|
#include <emmintrin.h>
|
|
149
|
-
|
|
150
149
|
#define GROUP_SIZE 16
|
|
151
|
-
#
|
|
150
|
+
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
151
|
+
#define SWISS_USE_NEON 1
|
|
152
|
+
#include <arm_neon.h>
|
|
153
|
+
#define GROUP_SIZE 16
|
|
152
154
|
#else
|
|
153
155
|
#define SWISS_USE_SWAR 1
|
|
154
156
|
#define GROUP_SIZE 8
|
|
155
|
-
#
|
|
157
|
+
#endif
|
|
158
|
+
|
|
159
|
+
#if defined(SWISS_USE_SSE2) || defined(SWISS_USE_NEON)
|
|
160
|
+
#define SWISS_USE_VEC 1
|
|
156
161
|
#endif
|
|
157
162
|
|
|
158
163
|
#ifdef SWISS_USE_SSE2
|
|
159
164
|
|
|
160
|
-
|
|
165
|
+
typedef __m128i ctrl_vec_t;
|
|
166
|
+
|
|
167
|
+
SH_ALWAYS_INLINE ctrl_vec_t ctrl_load(const uint8_t *ctrl) {
|
|
161
168
|
return _mm_loadu_si128((const __m128i *)ctrl);
|
|
162
169
|
}
|
|
163
170
|
|
|
164
|
-
|
|
171
|
+
SH_ALWAYS_INLINE uint32_t ctrl_match_h2_vec(ctrl_vec_t cv, uint8_t h2) {
|
|
165
172
|
__m128i cmp = _mm_cmpeq_epi8(cv, _mm_set1_epi8((char)h2));
|
|
166
173
|
return (uint32_t)_mm_movemask_epi8(cmp);
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
|
|
176
|
+
SH_ALWAYS_INLINE uint32_t ctrl_match_empty_vec(ctrl_vec_t cv) {
|
|
170
177
|
__m128i cmp = _mm_cmpeq_epi8(cv, _mm_set1_epi8((char)CTRL_EMPTY));
|
|
171
178
|
return (uint32_t)_mm_movemask_epi8(cmp);
|
|
172
179
|
}
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
SH_ALWAYS_INLINE uint32_t ctrl_match_empty_or_deleted_vec(ctrl_vec_t cv) {
|
|
175
182
|
return (uint32_t)_mm_movemask_epi8(cv);
|
|
176
183
|
}
|
|
177
184
|
|
|
178
|
-
|
|
185
|
+
SH_ALWAYS_INLINE uint32_t ctrl_match_empty(const uint8_t *ctrl) {
|
|
186
|
+
return ctrl_match_empty_vec(ctrl_load(ctrl));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#elif defined(SWISS_USE_NEON)
|
|
190
|
+
|
|
191
|
+
typedef uint8x16_t ctrl_vec_t;
|
|
192
|
+
typedef uint64_t ctrl_mask_t;
|
|
193
|
+
|
|
194
|
+
#define CTRL_MASK_SLOT(m) ((int)(ctz64(m) >> 2))
|
|
195
|
+
#define NEON_NIBBLE_MASK 0x1111111111111111ULL
|
|
196
|
+
|
|
197
|
+
SH_ALWAYS_INLINE ctrl_mask_t neon_mask64(uint8x16_t cmp) {
|
|
198
|
+
/* 4 bits per slot (0x0 / 0xF); keep one bit per nibble so mask &= mask-1 works. */
|
|
199
|
+
uint64_t packed = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(cmp), 4)), 0);
|
|
200
|
+
return packed & NEON_NIBBLE_MASK;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
SH_ALWAYS_INLINE ctrl_vec_t ctrl_load(const uint8_t *ctrl) {
|
|
204
|
+
return vld1q_u8(ctrl);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
SH_ALWAYS_INLINE ctrl_mask_t ctrl_match_h2_vec(ctrl_vec_t cv, uint8_t h2) {
|
|
208
|
+
return neon_mask64(vceqq_u8(cv, vdupq_n_u8(h2)));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
SH_ALWAYS_INLINE ctrl_mask_t ctrl_match_empty_vec(ctrl_vec_t cv) {
|
|
212
|
+
return neon_mask64(vceqq_u8(cv, vdupq_n_u8((uint8_t)CTRL_EMPTY)));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
SH_ALWAYS_INLINE ctrl_mask_t ctrl_match_empty_or_deleted_vec(ctrl_vec_t cv) {
|
|
216
|
+
return neon_mask64(vcltzq_s8(vreinterpretq_s8_u8(cv)));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
SH_ALWAYS_INLINE ctrl_mask_t ctrl_match_empty(const uint8_t *ctrl) {
|
|
179
220
|
return ctrl_match_empty_vec(ctrl_load(ctrl));
|
|
180
221
|
}
|
|
181
222
|
|
|
@@ -216,6 +257,14 @@ SH_ALWAYS_INLINE uint32_t ctrl_match_empty(const uint8_t *ctrl) {
|
|
|
216
257
|
|
|
217
258
|
#endif /* control-byte matching selection */
|
|
218
259
|
|
|
260
|
+
#ifndef CTRL_MASK_SLOT
|
|
261
|
+
typedef uint32_t ctrl_mask_t;
|
|
262
|
+
#define CTRL_MASK_SLOT(m) ctz32(m)
|
|
263
|
+
#endif
|
|
264
|
+
|
|
265
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
266
|
+
__attribute__((unused))
|
|
267
|
+
#endif
|
|
219
268
|
SH_ALWAYS_INLINE int ctz32(uint32_t v) {
|
|
220
269
|
#if defined(__GNUC__) || defined(__clang__)
|
|
221
270
|
return __builtin_ctz(v);
|
|
@@ -233,6 +282,21 @@ SH_ALWAYS_INLINE int ctz32(uint32_t v) {
|
|
|
233
282
|
#endif
|
|
234
283
|
}
|
|
235
284
|
|
|
285
|
+
SH_ALWAYS_INLINE int ctz64(uint64_t v) {
|
|
286
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
287
|
+
return (int)__builtin_ctzll(v);
|
|
288
|
+
#elif defined(_MSC_VER)
|
|
289
|
+
unsigned long idx;
|
|
290
|
+
_BitScanForward64(&idx, v);
|
|
291
|
+
return (int)idx;
|
|
292
|
+
#else
|
|
293
|
+
if ((uint32_t)v) {
|
|
294
|
+
return ctz32((uint32_t)v);
|
|
295
|
+
}
|
|
296
|
+
return 32 + ctz32((uint32_t)(v >> 32));
|
|
297
|
+
#endif
|
|
298
|
+
}
|
|
299
|
+
|
|
236
300
|
typedef struct {
|
|
237
301
|
VALUE key;
|
|
238
302
|
VALUE value;
|
|
@@ -250,44 +314,49 @@ typedef struct {
|
|
|
250
314
|
uint8_t mutating;
|
|
251
315
|
} SwissHash;
|
|
252
316
|
|
|
253
|
-
#define MUTATE_GUARD_BEGIN(sh) \
|
|
254
|
-
do { \
|
|
255
|
-
if ((sh)->mutating) { \
|
|
256
|
-
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected " \
|
|
257
|
-
"(#hash or #eql? callback modified the same table)"); \
|
|
258
|
-
} \
|
|
259
|
-
(sh)->mutating = 1; \
|
|
260
|
-
} while (0)
|
|
261
|
-
|
|
262
|
-
#define MUTATE_GUARD_END(sh) \
|
|
263
|
-
do { \
|
|
264
|
-
(sh)->mutating = 0; \
|
|
265
|
-
} while (0)
|
|
266
|
-
|
|
267
317
|
#define FIBONACCI_HASH_C 0x9E3779B97F4A7C15ULL
|
|
268
318
|
|
|
269
|
-
SH_ALWAYS_INLINE uint64_t
|
|
270
|
-
uint64_t
|
|
319
|
+
SH_ALWAYS_INLINE uint64_t hash_fixnum(VALUE key) {
|
|
320
|
+
return ((uint64_t)FIX2LONG(key) ^ swiss_hash_seed0) * FIBONACCI_HASH_C;
|
|
321
|
+
}
|
|
271
322
|
|
|
323
|
+
SH_ALWAYS_INLINE uint64_t hash_immediate(VALUE key) {
|
|
272
324
|
if (FIXNUM_P(key)) {
|
|
273
|
-
|
|
274
|
-
return v * FIBONACCI_HASH_C;
|
|
325
|
+
return hash_fixnum(key);
|
|
275
326
|
}
|
|
327
|
+
return ((uint64_t)SYM2ID(key) ^ swiss_hash_seed0) * FIBONACCI_HASH_C;
|
|
328
|
+
}
|
|
276
329
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
330
|
+
SH_ALWAYS_INLINE uint64_t hash_string(VALUE key) {
|
|
331
|
+
const char *ptr = RSTRING_PTR(key);
|
|
332
|
+
long len = RSTRING_LEN(key);
|
|
333
|
+
uint64_t seed = swiss_hash_seed0;
|
|
334
|
+
|
|
335
|
+
if (len > 0) {
|
|
336
|
+
int enc = ENCODING_GET(key);
|
|
337
|
+
if (enc) {
|
|
338
|
+
int cr = ENC_CODERANGE(key);
|
|
339
|
+
if (cr == ENC_CODERANGE_UNKNOWN) {
|
|
340
|
+
cr = rb_enc_str_coderange(key);
|
|
341
|
+
}
|
|
342
|
+
if (cr != ENC_CODERANGE_7BIT) {
|
|
343
|
+
seed ^= (uint64_t)enc;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return wyhash(ptr ? ptr : (const char *)"", (size_t)len, seed);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
SH_ALWAYS_INLINE uint64_t compute_hash(VALUE key) {
|
|
351
|
+
if (FIXNUM_P(key) || SYMBOL_P(key)) {
|
|
352
|
+
return hash_immediate(key);
|
|
280
353
|
}
|
|
281
354
|
|
|
282
355
|
if (RB_TYPE_P(key, T_STRING)) {
|
|
283
|
-
|
|
284
|
-
long len = RSTRING_LEN(key);
|
|
285
|
-
int enc_idx = ENCODING_GET(key);
|
|
286
|
-
uint64_t str_seed = swiss_hash_seed0 ^ (uint64_t)enc_idx;
|
|
287
|
-
return wyhash(ptr ? ptr : (const char *)"", (size_t)len, str_seed);
|
|
356
|
+
return hash_string(key);
|
|
288
357
|
}
|
|
289
358
|
|
|
290
|
-
v = (uint64_t)NUM2LONG(rb_hash(key));
|
|
359
|
+
uint64_t v = (uint64_t)NUM2LONG(rb_hash(key));
|
|
291
360
|
v ^= swiss_hash_seed1;
|
|
292
361
|
v ^= v >> 33;
|
|
293
362
|
v *= 0xff51afd7ed558ccdULL;
|
|
@@ -300,6 +369,32 @@ SH_ALWAYS_INLINE uint64_t compute_hash(VALUE key) {
|
|
|
300
369
|
#define H1(hash) ((hash) >> 7)
|
|
301
370
|
#define H2(hash) ((uint8_t)((hash) & H2_MASK))
|
|
302
371
|
|
|
372
|
+
SH_ALWAYS_INLINE int strings_equal(VALUE a, VALUE b) {
|
|
373
|
+
long la = RSTRING_LEN(a);
|
|
374
|
+
if (la != RSTRING_LEN(b))
|
|
375
|
+
return 0;
|
|
376
|
+
|
|
377
|
+
const char *pa = RSTRING_PTR(a);
|
|
378
|
+
const char *pb = RSTRING_PTR(b);
|
|
379
|
+
if (pa == pb)
|
|
380
|
+
return 1;
|
|
381
|
+
|
|
382
|
+
int ea = ENCODING_GET(a);
|
|
383
|
+
int eb = ENCODING_GET(b);
|
|
384
|
+
if (ea == eb) {
|
|
385
|
+
return memcmp(pa, pb, (size_t)la) == 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (ENC_CODERANGE(a) == ENC_CODERANGE_7BIT && ENC_CODERANGE(b) == ENC_CODERANGE_7BIT) {
|
|
389
|
+
return memcmp(pa, pb, (size_t)la) == 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (rb_enc_compatible(a, b)) {
|
|
393
|
+
return memcmp(pa, pb, (size_t)la) == 0;
|
|
394
|
+
}
|
|
395
|
+
return rb_eql(a, b);
|
|
396
|
+
}
|
|
397
|
+
|
|
303
398
|
SH_ALWAYS_INLINE int keys_equal(VALUE a, VALUE b) {
|
|
304
399
|
if (a == b)
|
|
305
400
|
return 1;
|
|
@@ -307,28 +402,7 @@ SH_ALWAYS_INLINE int keys_equal(VALUE a, VALUE b) {
|
|
|
307
402
|
return 0;
|
|
308
403
|
|
|
309
404
|
if (RB_TYPE_P(a, T_STRING) && RB_TYPE_P(b, T_STRING)) {
|
|
310
|
-
|
|
311
|
-
if (la != RSTRING_LEN(b))
|
|
312
|
-
return 0;
|
|
313
|
-
const char *pa = RSTRING_PTR(a);
|
|
314
|
-
const char *pb = RSTRING_PTR(b);
|
|
315
|
-
if (pa == pb)
|
|
316
|
-
return 1;
|
|
317
|
-
|
|
318
|
-
int ea = ENCODING_GET(a);
|
|
319
|
-
int eb = ENCODING_GET(b);
|
|
320
|
-
if (ea == eb) {
|
|
321
|
-
return memcmp(pa, pb, (size_t)la) == 0;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (ENC_CODERANGE(a) == ENC_CODERANGE_7BIT && ENC_CODERANGE(b) == ENC_CODERANGE_7BIT) {
|
|
325
|
-
return memcmp(pa, pb, (size_t)la) == 0;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (rb_enc_compatible(a, b)) {
|
|
329
|
-
return memcmp(pa, pb, (size_t)la) == 0;
|
|
330
|
-
}
|
|
331
|
-
return rb_eql(a, b);
|
|
405
|
+
return strings_equal(a, b);
|
|
332
406
|
}
|
|
333
407
|
|
|
334
408
|
return rb_eql(a, b);
|
|
@@ -345,19 +419,42 @@ SH_ALWAYS_INLINE VALUE prepare_key(VALUE key) {
|
|
|
345
419
|
key = rb_str_new_frozen(key);
|
|
346
420
|
}
|
|
347
421
|
#endif
|
|
348
|
-
rb_enc_str_coderange(key);
|
|
349
422
|
}
|
|
350
423
|
return key;
|
|
351
424
|
}
|
|
352
425
|
|
|
426
|
+
SH_ALWAYS_INLINE size_t swiss_ctrl_bytes(size_t capacity) {
|
|
427
|
+
return (capacity + 15u) & ~(size_t)15u;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static int swiss_alloc_block(size_t capacity, uint8_t **ctrl, Slot **slots) {
|
|
431
|
+
if (capacity == 0 || capacity > (SIZE_MAX / (sizeof(Slot) + 16))) {
|
|
432
|
+
return 0;
|
|
433
|
+
}
|
|
434
|
+
size_t ctrl_bytes = swiss_ctrl_bytes(capacity);
|
|
435
|
+
if (capacity > (SIZE_MAX - ctrl_bytes) / sizeof(Slot)) {
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
uint8_t *block = (uint8_t *)malloc(ctrl_bytes + capacity * sizeof(Slot));
|
|
439
|
+
if (!block) {
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
442
|
+
*ctrl = block;
|
|
443
|
+
*slots = (Slot *)(block + ctrl_bytes);
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
|
|
353
447
|
static void swiss_free_arrays(SwissHash *sh) {
|
|
354
448
|
free(sh->ctrl);
|
|
355
449
|
sh->ctrl = NULL;
|
|
356
|
-
free(sh->slots);
|
|
357
450
|
sh->slots = NULL;
|
|
358
451
|
}
|
|
359
452
|
|
|
360
453
|
static void swiss_init(SwissHash *sh, size_t min_capacity) {
|
|
454
|
+
if (sh->ctrl) {
|
|
455
|
+
swiss_free_arrays(sh);
|
|
456
|
+
}
|
|
457
|
+
|
|
361
458
|
size_t min_groups = (min_capacity + GROUP_SIZE - 1) / GROUP_SIZE;
|
|
362
459
|
size_t num_groups = 1;
|
|
363
460
|
while (num_groups < min_groups)
|
|
@@ -366,7 +463,22 @@ static void swiss_init(SwissHash *sh, size_t min_capacity) {
|
|
|
366
463
|
num_groups = 2;
|
|
367
464
|
|
|
368
465
|
size_t capacity = num_groups * GROUP_SIZE;
|
|
466
|
+
uint8_t *ctrl = NULL;
|
|
467
|
+
Slot *slots = NULL;
|
|
468
|
+
|
|
469
|
+
if (!swiss_alloc_block(capacity, &ctrl, &slots)) {
|
|
470
|
+
sh->capacity = 0;
|
|
471
|
+
sh->num_groups = 0;
|
|
472
|
+
sh->group_mask = 0;
|
|
473
|
+
sh->size = 0;
|
|
474
|
+
sh->growth_left = 0;
|
|
475
|
+
sh->tombstone_count = 0;
|
|
476
|
+
sh->mutating = 0;
|
|
477
|
+
rb_raise(rb_eNoMemError, "failed to allocate SwissHash");
|
|
478
|
+
}
|
|
369
479
|
|
|
480
|
+
sh->ctrl = ctrl;
|
|
481
|
+
sh->slots = slots;
|
|
370
482
|
sh->num_groups = num_groups;
|
|
371
483
|
sh->group_mask = num_groups - 1;
|
|
372
484
|
sh->capacity = capacity;
|
|
@@ -374,18 +486,6 @@ static void swiss_init(SwissHash *sh, size_t min_capacity) {
|
|
|
374
486
|
sh->tombstone_count = 0;
|
|
375
487
|
sh->mutating = 0;
|
|
376
488
|
sh->growth_left = capacity * MAX_LOAD_NUM / MAX_LOAD_DEN;
|
|
377
|
-
|
|
378
|
-
sh->ctrl = (uint8_t *)malloc(capacity);
|
|
379
|
-
sh->slots = (Slot *)malloc(capacity * sizeof(Slot));
|
|
380
|
-
|
|
381
|
-
if (!sh->ctrl || !sh->slots) {
|
|
382
|
-
free(sh->ctrl);
|
|
383
|
-
free(sh->slots);
|
|
384
|
-
sh->ctrl = NULL;
|
|
385
|
-
sh->slots = NULL;
|
|
386
|
-
rb_raise(rb_eNoMemError, "failed to allocate SwissHash");
|
|
387
|
-
}
|
|
388
|
-
|
|
389
489
|
memset(sh->ctrl, CTRL_EMPTY, capacity);
|
|
390
490
|
}
|
|
391
491
|
|
|
@@ -410,28 +510,51 @@ SH_ALWAYS_INLINE void probe_next(ProbeSeq *ps) {
|
|
|
410
510
|
|
|
411
511
|
#define GROUP_OFF(gi) ((gi) * GROUP_SIZE)
|
|
412
512
|
|
|
413
|
-
|
|
414
|
-
|
|
513
|
+
#ifdef SWISS_USE_VEC
|
|
514
|
+
#define SH_SCAN_GROUP(sh, off, h2, match, empty) \
|
|
515
|
+
do { \
|
|
516
|
+
ctrl_vec_t cv = ctrl_load((sh)->ctrl + (off)); \
|
|
517
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
518
|
+
(match) = ctrl_match_h2_vec(cv, (h2)); \
|
|
519
|
+
(empty) = ctrl_match_empty_vec(cv); \
|
|
520
|
+
} while (0)
|
|
521
|
+
#define SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail) \
|
|
522
|
+
do { \
|
|
523
|
+
ctrl_vec_t cv = ctrl_load((sh)->ctrl + (off)); \
|
|
524
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
525
|
+
(match) = ctrl_match_h2_vec(cv, (h2)); \
|
|
526
|
+
(empty) = ctrl_match_empty_vec(cv); \
|
|
527
|
+
(avail) = ctrl_match_empty_or_deleted_vec(cv); \
|
|
528
|
+
} while (0)
|
|
529
|
+
#else
|
|
530
|
+
#define SH_SCAN_GROUP(sh, off, h2, match, empty) \
|
|
531
|
+
do { \
|
|
532
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
533
|
+
(match) = ctrl_match_h2_raw((sh)->ctrl + (off), (h2)); \
|
|
534
|
+
(empty) = ctrl_match_empty_raw((sh)->ctrl + (off)); \
|
|
535
|
+
} while (0)
|
|
536
|
+
#define SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail) \
|
|
537
|
+
do { \
|
|
538
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
539
|
+
(match) = ctrl_match_h2_raw((sh)->ctrl + (off), (h2)); \
|
|
540
|
+
(empty) = ctrl_match_empty_raw((sh)->ctrl + (off)); \
|
|
541
|
+
(avail) = ctrl_match_empty_or_deleted_raw((sh)->ctrl + (off)); \
|
|
542
|
+
} while (0)
|
|
543
|
+
#endif
|
|
544
|
+
|
|
545
|
+
static inline VALUE *swiss_lookup_hashed(SwissHash *sh, VALUE key, uint64_t hash, int identity_only) {
|
|
415
546
|
uint8_t h2 = H2(hash);
|
|
416
547
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
417
548
|
|
|
418
549
|
for (;;) {
|
|
419
550
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
551
|
+
ctrl_mask_t match, empty;
|
|
552
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
420
553
|
|
|
421
|
-
#if defined(SWISS_USE_SSE2)
|
|
422
|
-
__m128i cv = ctrl_load(sh->ctrl + off);
|
|
423
|
-
SH_PREFETCH(&sh->slots[off]);
|
|
424
|
-
uint32_t match = ctrl_match_h2_vec(cv, h2);
|
|
425
|
-
uint32_t empty = ctrl_match_empty_vec(cv);
|
|
426
|
-
#else
|
|
427
|
-
SH_PREFETCH(&sh->slots[off]);
|
|
428
|
-
uint32_t match = ctrl_match_h2_raw(sh->ctrl + off, h2);
|
|
429
|
-
uint32_t empty = ctrl_match_empty_raw(sh->ctrl + off);
|
|
430
|
-
#endif
|
|
431
554
|
while (match) {
|
|
432
|
-
int slot =
|
|
555
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
433
556
|
Slot *s = &sh->slots[off + slot];
|
|
434
|
-
if (RB_LIKELY(s->key == key) || keys_equal(s->key, key)) {
|
|
557
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
435
558
|
return &s->value;
|
|
436
559
|
}
|
|
437
560
|
match &= match - 1;
|
|
@@ -443,23 +566,99 @@ static VALUE *swiss_lookup(SwissHash *sh, VALUE key) {
|
|
|
443
566
|
}
|
|
444
567
|
}
|
|
445
568
|
|
|
569
|
+
static inline VALUE *swiss_lookup_string(SwissHash *sh, VALUE key, uint64_t hash) {
|
|
570
|
+
uint8_t h2 = H2(hash);
|
|
571
|
+
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
572
|
+
|
|
573
|
+
for (;;) {
|
|
574
|
+
size_t off = GROUP_OFF(ps.group_idx);
|
|
575
|
+
ctrl_mask_t match, empty;
|
|
576
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
577
|
+
|
|
578
|
+
while (match) {
|
|
579
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
580
|
+
Slot *s = &sh->slots[off + slot];
|
|
581
|
+
if (RB_LIKELY(s->key == key) || (RB_TYPE_P(s->key, T_STRING) && strings_equal(s->key, key))) {
|
|
582
|
+
return &s->value;
|
|
583
|
+
}
|
|
584
|
+
match &= match - 1;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (empty)
|
|
588
|
+
return NULL;
|
|
589
|
+
probe_next(&ps);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
typedef struct {
|
|
594
|
+
SwissHash *sh;
|
|
595
|
+
VALUE key;
|
|
596
|
+
VALUE value;
|
|
597
|
+
VALUE result;
|
|
598
|
+
VALUE *found;
|
|
599
|
+
} CallbackCtx;
|
|
600
|
+
|
|
601
|
+
SH_ALWAYS_INLINE int key_may_callback(VALUE key) {
|
|
602
|
+
return !(FIXNUM_P(key) || SYMBOL_P(key) || RB_TYPE_P(key, T_STRING));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
static void swiss_lock(SwissHash *sh) {
|
|
606
|
+
if (sh->mutating) {
|
|
607
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
608
|
+
"(#hash or #eql? callback modified the same table)");
|
|
609
|
+
}
|
|
610
|
+
sh->mutating = 1;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
static VALUE swiss_unlock_ensure(VALUE shv) {
|
|
614
|
+
((SwissHash *)shv)->mutating = 0;
|
|
615
|
+
return Qnil;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
static VALUE swiss_lookup_custom(VALUE arg) {
|
|
619
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
620
|
+
c->found = swiss_lookup_hashed(c->sh, c->key, compute_hash(c->key), 0);
|
|
621
|
+
return Qnil;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
static inline VALUE *swiss_lookup(SwissHash *sh, VALUE key) {
|
|
625
|
+
if (FIXNUM_P(key)) {
|
|
626
|
+
return swiss_lookup_hashed(sh, key, hash_fixnum(key), 1);
|
|
627
|
+
}
|
|
628
|
+
if (SYMBOL_P(key)) {
|
|
629
|
+
return swiss_lookup_hashed(sh, key, hash_immediate(key), 1);
|
|
630
|
+
}
|
|
631
|
+
if (RB_TYPE_P(key, T_STRING)) {
|
|
632
|
+
return swiss_lookup_string(sh, key, hash_string(key));
|
|
633
|
+
}
|
|
634
|
+
if (sh->mutating) {
|
|
635
|
+
return swiss_lookup_hashed(sh, key, compute_hash(key), 0);
|
|
636
|
+
}
|
|
637
|
+
CallbackCtx ctx = {sh, key, Qnil, Qnil, NULL};
|
|
638
|
+
swiss_lock(sh);
|
|
639
|
+
rb_ensure(swiss_lookup_custom, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
640
|
+
return ctx.found;
|
|
641
|
+
}
|
|
642
|
+
|
|
446
643
|
static void swiss_grow(SwissHash *sh);
|
|
447
644
|
static void swiss_compact(SwissHash *sh);
|
|
448
645
|
|
|
449
|
-
SH_ALWAYS_INLINE void
|
|
646
|
+
SH_ALWAYS_INLINE void swiss_insert_rehash_into(uint8_t *ctrl, Slot *slots, size_t group_mask,
|
|
647
|
+
size_t *size, size_t *growth_left,
|
|
648
|
+
uint64_t hash, VALUE key, VALUE value) {
|
|
450
649
|
uint8_t h2 = H2(hash);
|
|
451
|
-
ProbeSeq ps = probe_start(H1(hash),
|
|
650
|
+
ProbeSeq ps = probe_start(H1(hash), group_mask);
|
|
452
651
|
|
|
453
652
|
for (;;) {
|
|
454
653
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
455
|
-
|
|
654
|
+
ctrl_mask_t empty_mask = ctrl_match_empty(ctrl + off);
|
|
456
655
|
if (empty_mask) {
|
|
457
|
-
size_t idx = off +
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
656
|
+
size_t idx = off + (size_t)CTRL_MASK_SLOT(empty_mask);
|
|
657
|
+
ctrl[idx] = h2;
|
|
658
|
+
slots[idx].key = key;
|
|
659
|
+
slots[idx].value = value;
|
|
660
|
+
(*size)++;
|
|
661
|
+
(*growth_left)--;
|
|
463
662
|
return;
|
|
464
663
|
}
|
|
465
664
|
probe_next(&ps);
|
|
@@ -470,44 +669,87 @@ static inline int should_compact(SwissHash *sh) {
|
|
|
470
669
|
return sh->tombstone_count >= sh->capacity / TOMBSTONE_COMPACT_DIVISOR;
|
|
471
670
|
}
|
|
472
671
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
uint8_t *old_ctrl
|
|
478
|
-
Slot *old_slots
|
|
672
|
+
typedef struct {
|
|
673
|
+
SwissHash *sh;
|
|
674
|
+
uint8_t *new_ctrl;
|
|
675
|
+
Slot *new_slots;
|
|
676
|
+
const uint8_t *old_ctrl;
|
|
677
|
+
const Slot *old_slots;
|
|
678
|
+
size_t old_cap;
|
|
679
|
+
size_t new_cap;
|
|
680
|
+
size_t new_num_groups;
|
|
681
|
+
} RebuildCtx;
|
|
682
|
+
|
|
683
|
+
static VALUE swiss_rebuild_body(VALUE arg) {
|
|
684
|
+
RebuildCtx *ctx = (RebuildCtx *)arg;
|
|
685
|
+
size_t group_mask = ctx->new_num_groups - 1;
|
|
686
|
+
size_t size = 0;
|
|
687
|
+
size_t growth_left = ctx->new_cap * MAX_LOAD_NUM / MAX_LOAD_DEN;
|
|
688
|
+
|
|
689
|
+
for (size_t i = 0; i < ctx->old_cap; i++) {
|
|
690
|
+
uint8_t c = ctx->old_ctrl[i];
|
|
691
|
+
if (c != CTRL_EMPTY && c != CTRL_DELETED) {
|
|
692
|
+
VALUE key = ctx->old_slots[i].key;
|
|
693
|
+
uint64_t hash = FIXNUM_P(key) ? hash_fixnum(key) : compute_hash(key);
|
|
694
|
+
swiss_insert_rehash_into(ctx->new_ctrl, ctx->new_slots, group_mask,
|
|
695
|
+
&size, &growth_left, hash, key, ctx->old_slots[i].value);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
479
698
|
|
|
480
|
-
sh->ctrl
|
|
481
|
-
sh->
|
|
699
|
+
free(ctx->sh->ctrl);
|
|
700
|
+
ctx->sh->ctrl = ctx->new_ctrl;
|
|
701
|
+
ctx->sh->slots = ctx->new_slots;
|
|
702
|
+
ctx->sh->num_groups = ctx->new_num_groups;
|
|
703
|
+
ctx->sh->group_mask = group_mask;
|
|
704
|
+
ctx->sh->capacity = ctx->new_cap;
|
|
705
|
+
ctx->sh->size = size;
|
|
706
|
+
ctx->sh->growth_left = growth_left;
|
|
707
|
+
ctx->sh->tombstone_count = 0;
|
|
708
|
+
ctx->new_ctrl = NULL;
|
|
709
|
+
ctx->new_slots = NULL;
|
|
710
|
+
return Qnil;
|
|
711
|
+
}
|
|
482
712
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
sh->growth_left = 0;
|
|
490
|
-
rb_raise(rb_eNoMemError, "failed to compact SwissHash");
|
|
713
|
+
static VALUE swiss_rebuild_ensure(VALUE arg) {
|
|
714
|
+
RebuildCtx *ctx = (RebuildCtx *)arg;
|
|
715
|
+
if (ctx->new_ctrl) {
|
|
716
|
+
free(ctx->new_ctrl);
|
|
717
|
+
ctx->new_ctrl = NULL;
|
|
718
|
+
ctx->new_slots = NULL;
|
|
491
719
|
}
|
|
720
|
+
return Qnil;
|
|
721
|
+
}
|
|
492
722
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
sh
|
|
723
|
+
static void swiss_rebuild(SwissHash *sh, size_t new_num_groups) {
|
|
724
|
+
RebuildCtx ctx;
|
|
725
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
726
|
+
ctx.sh = sh;
|
|
727
|
+
ctx.old_ctrl = sh->ctrl;
|
|
728
|
+
ctx.old_slots = sh->slots;
|
|
729
|
+
ctx.old_cap = sh->capacity;
|
|
730
|
+
ctx.new_num_groups = new_num_groups;
|
|
731
|
+
ctx.new_cap = new_num_groups * GROUP_SIZE;
|
|
497
732
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if (c != CTRL_EMPTY && c != CTRL_DELETED) {
|
|
501
|
-
uint64_t hash = compute_hash(old_slots[i].key);
|
|
502
|
-
swiss_insert_rehash(sh, hash, old_slots[i].key, old_slots[i].value);
|
|
503
|
-
}
|
|
733
|
+
if (!swiss_alloc_block(ctx.new_cap, &ctx.new_ctrl, &ctx.new_slots)) {
|
|
734
|
+
rb_raise(rb_eNoMemError, "failed to rebuild SwissHash");
|
|
504
735
|
}
|
|
736
|
+
memset(ctx.new_ctrl, CTRL_EMPTY, ctx.new_cap);
|
|
737
|
+
rb_ensure(swiss_rebuild_body, (VALUE)&ctx, swiss_rebuild_ensure, (VALUE)&ctx);
|
|
738
|
+
}
|
|
505
739
|
|
|
506
|
-
|
|
507
|
-
|
|
740
|
+
SH_ALWAYS_INLINE void swiss_place_new(SwissHash *sh, size_t insert_idx, uint8_t h2, VALUE key, VALUE value) {
|
|
741
|
+
if (sh->ctrl[insert_idx] == CTRL_EMPTY) {
|
|
742
|
+
sh->growth_left--;
|
|
743
|
+
} else {
|
|
744
|
+
sh->tombstone_count--;
|
|
745
|
+
}
|
|
746
|
+
sh->ctrl[insert_idx] = h2;
|
|
747
|
+
sh->slots[insert_idx].key = key;
|
|
748
|
+
sh->slots[insert_idx].value = value;
|
|
749
|
+
sh->size++;
|
|
508
750
|
}
|
|
509
751
|
|
|
510
|
-
static VALUE
|
|
752
|
+
static VALUE swiss_insert_hashed(SwissHash *sh, VALUE key, VALUE value, uint64_t hash, int identity_only, int may_callback) {
|
|
511
753
|
if (sh->growth_left == 0) {
|
|
512
754
|
if (should_compact(sh)) {
|
|
513
755
|
swiss_compact(sh);
|
|
@@ -516,32 +758,33 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
516
758
|
}
|
|
517
759
|
}
|
|
518
760
|
|
|
519
|
-
uint64_t hash = compute_hash(key);
|
|
520
761
|
uint8_t h2 = H2(hash);
|
|
521
762
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
522
|
-
|
|
523
763
|
size_t insert_idx = (size_t)-1;
|
|
524
764
|
|
|
525
765
|
for (;;) {
|
|
526
766
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
527
|
-
|
|
528
|
-
#
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
767
|
+
ctrl_mask_t match, empty, avail;
|
|
768
|
+
#ifdef SWISS_USE_VEC
|
|
769
|
+
{
|
|
770
|
+
ctrl_vec_t cv = ctrl_load(sh->ctrl + off);
|
|
771
|
+
SH_PREFETCH(&sh->slots[off]);
|
|
772
|
+
match = ctrl_match_h2_vec(cv, h2);
|
|
773
|
+
empty = ctrl_match_empty_vec(cv);
|
|
774
|
+
avail = (sh->tombstone_count == 0) ? empty : ctrl_match_empty_or_deleted_vec(cv);
|
|
775
|
+
}
|
|
534
776
|
#else
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
777
|
+
SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail);
|
|
778
|
+
if (sh->tombstone_count == 0) {
|
|
779
|
+
avail = empty;
|
|
780
|
+
}
|
|
539
781
|
#endif
|
|
782
|
+
|
|
540
783
|
while (match) {
|
|
541
|
-
int slot =
|
|
784
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
542
785
|
size_t idx = off + slot;
|
|
543
786
|
Slot *s = &sh->slots[idx];
|
|
544
|
-
if (RB_LIKELY(s->key == key) || keys_equal(s->key, key)) {
|
|
787
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
545
788
|
s->value = value;
|
|
546
789
|
return value;
|
|
547
790
|
}
|
|
@@ -549,7 +792,7 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
549
792
|
}
|
|
550
793
|
|
|
551
794
|
if (insert_idx == (size_t)-1 && avail) {
|
|
552
|
-
insert_idx = off +
|
|
795
|
+
insert_idx = off + (size_t)CTRL_MASK_SLOT(avail);
|
|
553
796
|
}
|
|
554
797
|
|
|
555
798
|
if (empty)
|
|
@@ -557,108 +800,172 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
557
800
|
probe_next(&ps);
|
|
558
801
|
}
|
|
559
802
|
|
|
560
|
-
|
|
803
|
+
swiss_place_new(sh, insert_idx, h2, key, value);
|
|
804
|
+
return value;
|
|
805
|
+
}
|
|
561
806
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
807
|
+
SH_ALWAYS_INLINE VALUE swiss_insert_fixnum(SwissHash *sh, VALUE key, VALUE value) {
|
|
808
|
+
if (RB_UNLIKELY(sh->growth_left == 0)) {
|
|
809
|
+
if (should_compact(sh)) {
|
|
810
|
+
swiss_compact(sh);
|
|
811
|
+
} else {
|
|
812
|
+
swiss_grow(sh);
|
|
813
|
+
}
|
|
566
814
|
}
|
|
567
|
-
sh->ctrl[insert_idx] = h2;
|
|
568
|
-
sh->slots[insert_idx].key = key;
|
|
569
|
-
sh->slots[insert_idx].value = value;
|
|
570
|
-
sh->size++;
|
|
571
815
|
|
|
572
|
-
|
|
816
|
+
uint64_t hash = hash_fixnum(key);
|
|
817
|
+
uint8_t h2 = H2(hash);
|
|
818
|
+
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
819
|
+
size_t insert_idx = (size_t)-1;
|
|
820
|
+
|
|
821
|
+
for (;;) {
|
|
822
|
+
size_t off = GROUP_OFF(ps.group_idx);
|
|
823
|
+
ctrl_mask_t match, empty, avail;
|
|
824
|
+
#ifdef SWISS_USE_VEC
|
|
825
|
+
ctrl_vec_t cv = ctrl_load(sh->ctrl + off);
|
|
826
|
+
SH_PREFETCH(&sh->slots[off]);
|
|
827
|
+
match = ctrl_match_h2_vec(cv, h2);
|
|
828
|
+
empty = ctrl_match_empty_vec(cv);
|
|
829
|
+
avail = (sh->tombstone_count == 0) ? empty : ctrl_match_empty_or_deleted_vec(cv);
|
|
830
|
+
#else
|
|
831
|
+
SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail);
|
|
832
|
+
if (sh->tombstone_count == 0) {
|
|
833
|
+
avail = empty;
|
|
834
|
+
}
|
|
835
|
+
#endif
|
|
836
|
+
while (match) {
|
|
837
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
838
|
+
if (RB_LIKELY(sh->slots[off + slot].key == key)) {
|
|
839
|
+
sh->slots[off + slot].value = value;
|
|
840
|
+
return value;
|
|
841
|
+
}
|
|
842
|
+
match &= match - 1;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (insert_idx == (size_t)-1 && avail) {
|
|
846
|
+
insert_idx = off + (size_t)CTRL_MASK_SLOT(avail);
|
|
847
|
+
}
|
|
848
|
+
if (RB_LIKELY(empty))
|
|
849
|
+
break;
|
|
850
|
+
probe_next(&ps);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
swiss_place_new(sh, insert_idx, h2, key, value);
|
|
573
854
|
return value;
|
|
574
855
|
}
|
|
575
856
|
|
|
576
|
-
static VALUE
|
|
857
|
+
static VALUE swiss_insert_unlocked(VALUE arg) {
|
|
858
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
859
|
+
if (FIXNUM_P(c->key)) {
|
|
860
|
+
c->result = swiss_insert_fixnum(c->sh, c->key, c->value);
|
|
861
|
+
} else if (SYMBOL_P(c->key)) {
|
|
862
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, hash_immediate(c->key), 1, 0);
|
|
863
|
+
} else if (RB_TYPE_P(c->key, T_STRING)) {
|
|
864
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, hash_string(c->key), 0, 0);
|
|
865
|
+
} else {
|
|
866
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, compute_hash(c->key), 0, 0);
|
|
867
|
+
}
|
|
868
|
+
return c->result;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
872
|
+
if (sh->mutating) {
|
|
873
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
874
|
+
"(#hash or #eql? callback modified the same table)");
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
CallbackCtx ctx = {sh, key, value, Qnil, NULL};
|
|
878
|
+
if (!key_may_callback(key) && sh->growth_left != 0) {
|
|
879
|
+
return swiss_insert_unlocked((VALUE)&ctx);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
swiss_lock(sh);
|
|
883
|
+
rb_ensure(swiss_insert_unlocked, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
884
|
+
return ctx.result;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
static VALUE swiss_delete_unlocked(VALUE arg) {
|
|
888
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
889
|
+
SwissHash *sh = c->sh;
|
|
890
|
+
VALUE key = c->key;
|
|
577
891
|
uint64_t hash = compute_hash(key);
|
|
892
|
+
int identity_only = FIXNUM_P(key) || SYMBOL_P(key);
|
|
578
893
|
uint8_t h2 = H2(hash);
|
|
579
894
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
580
895
|
|
|
581
896
|
for (;;) {
|
|
582
897
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
898
|
+
ctrl_mask_t match, empty;
|
|
899
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
583
900
|
|
|
584
|
-
#if defined(SWISS_USE_SSE2)
|
|
585
|
-
__m128i cv = ctrl_load(sh->ctrl + off);
|
|
586
|
-
SH_PREFETCH(&sh->slots[off]);
|
|
587
|
-
uint32_t match = ctrl_match_h2_vec(cv, h2);
|
|
588
|
-
uint32_t empty = ctrl_match_empty_vec(cv);
|
|
589
|
-
#else
|
|
590
|
-
SH_PREFETCH(&sh->slots[off]);
|
|
591
|
-
uint32_t match = ctrl_match_h2_raw(sh->ctrl + off, h2);
|
|
592
|
-
uint32_t empty = ctrl_match_empty_raw(sh->ctrl + off);
|
|
593
|
-
#endif
|
|
594
901
|
while (match) {
|
|
595
|
-
int slot =
|
|
902
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
596
903
|
size_t idx = off + slot;
|
|
597
904
|
Slot *s = &sh->slots[idx];
|
|
598
|
-
if (RB_LIKELY(s->key == key) || keys_equal(s->key, key)) {
|
|
905
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
599
906
|
VALUE old_value = s->value;
|
|
600
|
-
|
|
601
|
-
MUTATE_GUARD_BEGIN(sh);
|
|
602
907
|
sh->ctrl[idx] = CTRL_DELETED;
|
|
603
908
|
sh->slots[idx].key = Qnil;
|
|
604
909
|
sh->slots[idx].value = Qnil;
|
|
605
910
|
sh->size--;
|
|
606
911
|
sh->tombstone_count++;
|
|
607
|
-
|
|
608
|
-
|
|
912
|
+
c->result = old_value;
|
|
609
913
|
return old_value;
|
|
610
914
|
}
|
|
611
915
|
match &= match - 1;
|
|
612
916
|
}
|
|
613
917
|
|
|
614
|
-
if (empty)
|
|
918
|
+
if (empty) {
|
|
919
|
+
c->result = Qnil;
|
|
615
920
|
return Qnil;
|
|
921
|
+
}
|
|
616
922
|
probe_next(&ps);
|
|
617
923
|
}
|
|
618
924
|
}
|
|
619
925
|
|
|
620
|
-
static
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
Slot *old_slots = sh->slots;
|
|
626
|
-
|
|
627
|
-
size_t new_num_groups = sh->num_groups * 2;
|
|
628
|
-
size_t new_cap = new_num_groups * GROUP_SIZE;
|
|
629
|
-
|
|
630
|
-
sh->ctrl = (uint8_t *)malloc(new_cap);
|
|
631
|
-
sh->slots = (Slot *)malloc(new_cap * sizeof(Slot));
|
|
926
|
+
static VALUE swiss_delete(SwissHash *sh, VALUE key) {
|
|
927
|
+
if (sh->mutating) {
|
|
928
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
929
|
+
"(#hash or #eql? callback modified the same table)");
|
|
930
|
+
}
|
|
632
931
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
sh->ctrl = old_ctrl;
|
|
637
|
-
sh->slots = old_slots;
|
|
638
|
-
sh->size = old_size;
|
|
639
|
-
sh->growth_left = 0;
|
|
640
|
-
rb_raise(rb_eNoMemError, "failed to grow SwissHash");
|
|
932
|
+
CallbackCtx ctx = {sh, key, Qnil, Qnil, NULL};
|
|
933
|
+
if (!key_may_callback(key)) {
|
|
934
|
+
return swiss_delete_unlocked((VALUE)&ctx);
|
|
641
935
|
}
|
|
642
936
|
|
|
643
|
-
|
|
937
|
+
swiss_lock(sh);
|
|
938
|
+
rb_ensure(swiss_delete_unlocked, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
939
|
+
return ctx.result;
|
|
940
|
+
}
|
|
644
941
|
|
|
645
|
-
|
|
646
|
-
sh
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
sh->growth_left = new_cap * MAX_LOAD_NUM / MAX_LOAD_DEN;
|
|
650
|
-
sh->tombstone_count = 0;
|
|
942
|
+
typedef struct {
|
|
943
|
+
SwissHash *sh;
|
|
944
|
+
size_t new_num_groups;
|
|
945
|
+
} RebuildCall;
|
|
651
946
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
947
|
+
static VALUE swiss_rebuild_call(VALUE arg) {
|
|
948
|
+
RebuildCall *c = (RebuildCall *)arg;
|
|
949
|
+
swiss_rebuild(c->sh, c->new_num_groups);
|
|
950
|
+
return Qnil;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
static void swiss_rebuild_locked(SwissHash *sh, size_t new_num_groups) {
|
|
954
|
+
RebuildCall call = {sh, new_num_groups};
|
|
955
|
+
if (sh->mutating) {
|
|
956
|
+
swiss_rebuild(sh, new_num_groups);
|
|
957
|
+
return;
|
|
658
958
|
}
|
|
959
|
+
swiss_lock(sh);
|
|
960
|
+
rb_ensure(swiss_rebuild_call, (VALUE)&call, swiss_unlock_ensure, (VALUE)sh);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
static void swiss_grow(SwissHash *sh) {
|
|
964
|
+
swiss_rebuild_locked(sh, sh->num_groups * 2);
|
|
965
|
+
}
|
|
659
966
|
|
|
660
|
-
|
|
661
|
-
|
|
967
|
+
static void swiss_compact(SwissHash *sh) {
|
|
968
|
+
swiss_rebuild_locked(sh, sh->num_groups);
|
|
662
969
|
}
|
|
663
970
|
|
|
664
971
|
static void swiss_hash_mark(void *ptr) {
|
|
@@ -750,7 +1057,10 @@ static VALUE swiss_hash_initialize_copy(VALUE self, VALUE original) {
|
|
|
750
1057
|
|
|
751
1058
|
static VALUE swiss_hash_aset(VALUE self, VALUE key, VALUE value) {
|
|
752
1059
|
SwissHash *sh = (SwissHash *)RTYPEDDATA_DATA(self);
|
|
753
|
-
if (
|
|
1060
|
+
if (FIXNUM_P(key)) {
|
|
1061
|
+
return swiss_insert_fixnum(sh, key, value);
|
|
1062
|
+
}
|
|
1063
|
+
if (RB_UNLIKELY(!SYMBOL_P(key))) {
|
|
754
1064
|
key = prepare_key(key);
|
|
755
1065
|
}
|
|
756
1066
|
return swiss_insert(sh, key, value);
|
|
@@ -758,7 +1068,12 @@ static VALUE swiss_hash_aset(VALUE self, VALUE key, VALUE value) {
|
|
|
758
1068
|
|
|
759
1069
|
static VALUE swiss_hash_aref(VALUE self, VALUE key) {
|
|
760
1070
|
SwissHash *sh = (SwissHash *)RTYPEDDATA_DATA(self);
|
|
761
|
-
VALUE *val
|
|
1071
|
+
VALUE *val;
|
|
1072
|
+
if (FIXNUM_P(key)) {
|
|
1073
|
+
val = swiss_lookup_hashed(sh, key, hash_fixnum(key), 1);
|
|
1074
|
+
} else {
|
|
1075
|
+
val = swiss_lookup(sh, key);
|
|
1076
|
+
}
|
|
762
1077
|
return val ? *val : Qnil;
|
|
763
1078
|
}
|
|
764
1079
|
|
|
@@ -1401,6 +1716,8 @@ static VALUE swiss_hash_stats(VALUE self) {
|
|
|
1401
1716
|
|
|
1402
1717
|
#ifdef SWISS_USE_SSE2
|
|
1403
1718
|
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("SSE2"));
|
|
1719
|
+
#elif defined(SWISS_USE_NEON)
|
|
1720
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("NEON"));
|
|
1404
1721
|
#else
|
|
1405
1722
|
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("SWAR"));
|
|
1406
1723
|
#endif
|
data/lib/swiss_hash/version.rb
CHANGED