swiss_hash 0.1.2 → 0.1.4
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 +17 -0
- data/README.md +37 -36
- data/ext/swiss_hash/swiss_hash.c +544 -212
- 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: 0c2f1e2b2c9e7a8ee0ba2b4ab30f7088eeeefdd1e56abc6fb92758bd5a667a64
|
|
4
|
+
data.tar.gz: 92904e400f7f16702a48c2966afc8c41b4e05aacd769fbd3ff9abd54d8cd5b53
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4a90125a2c8d4792299a7899331f14a0f285cb15fe497a5c53258641846f5811d8f07127cee8a3c0f698f35011a6a45190deeaa9149642fa36856ca04fc12fd
|
|
7
|
+
data.tar.gz: d93013782fc2df669c537d4f0907c2c398781ddf077afd79dc0ddce455f18476ba2e0dc798dd09637c21cb866eb4ceed06c63669b8e625c5f26c5ae48633ab55
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.4 - 2026-10-28
|
|
4
|
+
|
|
5
|
+
- Prevented reentrant Fixnum insertion and `clear`/`initialize` from modifying a table during hash callbacks.
|
|
6
|
+
- Made reinitialization preserve the existing table if capacity validation or allocation fails; rejected overflowing capacities.
|
|
7
|
+
- Made `replace` with itself a no-op and kept contents intact when the replacement cannot convert to a Hash.
|
|
8
|
+
- Updated benchmark memory reporting to compare container storage and directly retained keys/values for Ruby Hash and SwissHash.
|
|
9
|
+
- Refreshed README timings and memory results for Ruby 3.4.3 / arm64-darwin24.
|
|
10
|
+
|
|
11
|
+
## 0.1.3 - 2026-08-18
|
|
12
|
+
|
|
13
|
+
- NEON group matching on ARM64 (SSE2 on x86_64, SWAR elsewhere)
|
|
14
|
+
- Single allocation for control bytes and slots
|
|
15
|
+
- Faster Fixnum `[]` / `[]=`
|
|
16
|
+
- 7-bit strings of different encodings hash like Ruby Hash (`"abc"` and `"abc".b` are the same key)
|
|
17
|
+
- Rehash is GC-safe; `#hash` / `#eql?` that mutate the same table raise instead of corrupting it
|
|
18
|
+
- `initialize` no longer leaks on reuse; OOM no longer leaves a half-built table
|
|
19
|
+
|
|
3
20
|
## 0.1.2 - Unreleased
|
|
4
21
|
|
|
5
22
|
Documentation and API polish for the first public release candidate.
|
data/README.md
CHANGED
|
@@ -33,63 +33,63 @@ Methodology: 10 runs × 21 measured iterations, 5 warmup iterations per run, IQR
|
|
|
33
33
|
|
|
34
34
|
| Operation | Ruby Hash | SwissHash | Delta |
|
|
35
35
|
|---|---:|---:|---:|
|
|
36
|
-
| Insert (sequential int) | 6.
|
|
37
|
-
| Insert (string keys) | 16.
|
|
38
|
-
| Insert (random int) | 5.
|
|
39
|
-
| Lookup (sequential int, 3x) |
|
|
40
|
-
| Lookup (string keys, 3x) | 20.
|
|
41
|
-
| Delete + reinsert 25% | 8.
|
|
42
|
-
| Mixed (70
|
|
36
|
+
| Insert (sequential int) | 6.412 ms (±0.5%) | 4.973 ms (±0.7%) | **−22.44%** ⚡ |
|
|
37
|
+
| Insert (string keys) | 16.130 ms (±0.2%) | 10.470 ms (±0.2%) | **−35.09%** ⚡ |
|
|
38
|
+
| Insert (random int) | 5.994 ms (±0.6%) | 4.763 ms (±0.8%) | **−20.55%** ⚡ |
|
|
39
|
+
| Lookup (sequential int, 3x) | 12.204 ms (±0.1%) | 11.050 ms (±0.1%) | **−9.46%** ⚡ |
|
|
40
|
+
| Lookup (string keys, 3x) | 20.465 ms (±0.2%) | 20.322 ms (±0.2%) | **−0.70%** ⚡ |
|
|
41
|
+
| Delete + reinsert 25% | 8.968 ms (±0.3%) | 7.075 ms (±0.4%) | **−21.10%** ⚡ |
|
|
42
|
+
| Mixed (70/20/10 R/W/D) | 22.168 ms (±0.3%) | 18.818 ms (±0.1%) | **−15.11%** ⚡ |
|
|
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
|
|
48
|
+
| Insert (sequential int) | 0.577 ms (±1.2%) | 0.499 ms (±1.5%) | **−13.58%** ⚡ |
|
|
49
|
+
| Insert (string keys) | 1.479 ms (±1.6%) | 1.001 ms (±1.3%) | **−32.30%** ⚡ |
|
|
50
|
+
| Insert (random int) | 0.541 ms (±1.9%) | 0.470 ms (±2.2%) | **−13.10%** ⚡ |
|
|
51
|
+
| Lookup (sequential int, 3x) | 1.069 ms (±0.2%) | 1.035 ms (±0.2%) | **−3.22%** ⚡ |
|
|
52
|
+
| Lookup (string keys, 3x) | 1.692 ms (±0.4%) | 1.550 ms (±0.4%) | **−8.42%** ⚡ |
|
|
53
|
+
| Delete + reinsert 25% | 0.808 ms (±0.9%) | 0.701 ms (±0.7%) | **−13.33%** ⚡ |
|
|
54
|
+
| Mixed (70/20/10 R/W/D) | 1.979 ms (±0.6%) | 1.829 ms (±0.8%) | **−7.62%** ⚡ |
|
|
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 favour the built-in implementation. String-heavy workloads continue to be the strongest SwissHash case.
|
|
59
|
-
|
|
60
58
|
| Operation | Ruby Hash | SwissHash | Delta |
|
|
61
59
|
|---|---:|---:|---:|
|
|
62
|
-
| Insert (sequential int) | 0.
|
|
63
|
-
| Insert (string keys) | 0.
|
|
64
|
-
| Insert (random int) | 0.054 ms (±
|
|
65
|
-
| Lookup (sequential int, 3x) | 0.
|
|
66
|
-
| Lookup (string keys, 3x) | 0.
|
|
67
|
-
| Delete + reinsert 25% | 0.
|
|
68
|
-
| Mixed (70
|
|
60
|
+
| Insert (sequential int) | 0.057 ms (±0.8%) | 0.053 ms (±1.3%) | **−6.53%** ⚡ |
|
|
61
|
+
| Insert (string keys) | 0.154 ms (±2.7%) | 0.103 ms (±0.9%) | **−32.69%** ⚡ |
|
|
62
|
+
| Insert (random int) | 0.054 ms (±1.4%) | 0.049 ms (±1.1%) | **−9.27%** ⚡ |
|
|
63
|
+
| Lookup (sequential int, 3x) | 0.110 ms (±0.5%) | 0.106 ms (±0.4%) | **−3.73%** ⚡ |
|
|
64
|
+
| Lookup (string keys, 3x) | 0.177 ms (±0.5%) | 0.151 ms (±0.4%) | **−14.37%** ⚡ |
|
|
65
|
+
| Delete + reinsert 25% | 0.081 ms (±0.8%) | 0.074 ms (±1.1%) | **−8.42%** ⚡ |
|
|
66
|
+
| Mixed (70/20/10 R/W/D) | 0.203 ms (±0.2%) | 0.191 ms (±0.3%) | **−5.88%** ⚡ |
|
|
69
67
|
|
|
70
68
|
### Summary
|
|
71
69
|
|
|
72
|
-
- SwissHash is faster on
|
|
73
|
-
- The strongest win is
|
|
74
|
-
-
|
|
75
|
-
- String lookups are workload-sensitive: SwissHash wins at N=1k and N=10k, while the N=100k run is slightly slower than Ruby Hash within a noisier test band.
|
|
70
|
+
- SwissHash is faster on all seven listed operations at N=100k; string lookup is within 1% of Ruby Hash.
|
|
71
|
+
- The strongest win is string-key insertion: **−32% to −35%** across tested sizes.
|
|
72
|
+
- ARM64 uses the NEON group-matching path (`stats[:simd] => "NEON"`).
|
|
76
73
|
- Ruby's built-in `Hash` remains excellent, especially for very small maps and cases that benefit from VM-level Hash specialization.
|
|
77
74
|
|
|
78
75
|
### Memory Usage
|
|
79
76
|
|
|
80
|
-
For 100,000
|
|
77
|
+
For 100,000 entries, `ObjectSpace.memsize_of` reports the hash object/table and directly retained key/value objects. Integer keys and values are immediate values, so they add no separate object bytes. For string-key tests, stored string keys are counted; the shared input array and source strings are excluded from both totals.
|
|
81
78
|
|
|
82
|
-
| Implementation |
|
|
83
|
-
|
|
84
|
-
| SwissHash | 2,176
|
|
85
|
-
|
|
|
79
|
+
| Workload | Implementation | Container | Referenced keys/values | Total |
|
|
80
|
+
|---|---|---:|---:|---:|
|
|
81
|
+
| Integer keys and values | SwissHash | 2,176.1 KiB | 0.0 KiB | 2,176.1 KiB |
|
|
82
|
+
| Integer keys and values | Ruby Hash | 4,096.2 KiB | 0.0 KiB | 4,096.2 KiB |
|
|
83
|
+
| String keys and integer values | SwissHash | 2,176.1 KiB | 6,408.1 KiB | 8,584.2 KiB |
|
|
84
|
+
| String keys and integer values | Ruby Hash | 4,096.2 KiB | 6,408.1 KiB | 10,504.3 KiB |
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
SwissHash uses 46.87% less measured memory for integer entries and 18.28% less for string-key entries. The measurement excludes allocator fragmentation and objects reachable only through nested values. Load factor: 76.3%; max load factor: 87.5%; SIMD path: NEON.
|
|
88
87
|
|
|
89
88
|
## Features
|
|
90
89
|
|
|
91
90
|
- **Swiss Table probing**: 7-bit `H2` metadata, group probing, triangular probe sequence, and 87.5% max load factor.
|
|
92
|
-
- **
|
|
91
|
+
- **SIMD group matching**: SSE2 on x86_64, NEON on aarch64, SWAR fallback elsewhere.
|
|
92
|
+
- **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
93
|
- **Low GC pressure**: keys and values are Ruby objects, while control bytes and slots live in contiguous native arrays.
|
|
94
94
|
- **Delete/reinsert friendly**: tombstones are tracked and compacted to avoid pathological slowdown.
|
|
95
95
|
- **Hash-like API**: basic accessors, enumeration, fetch helpers, merge/update/replace, filtering, transforming, slicing, inversion, and conversion helpers.
|
|
@@ -200,7 +200,7 @@ Stick with Ruby's built-in `Hash` when:
|
|
|
200
200
|
### Swiss Table core
|
|
201
201
|
|
|
202
202
|
- **Open addressing** with 7-bit `H2` metadata byte per slot; group matching rejects non-matching slots in batches.
|
|
203
|
-
- **Group size 16 on SSE2
|
|
203
|
+
- **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
204
|
- **Triangular probing** — `i(i+1)/2` — over power-of-two group counts.
|
|
205
205
|
- **Max load factor 87.5%** (7/8).
|
|
206
206
|
|
|
@@ -215,7 +215,7 @@ Stick with Ruby's built-in `Hash` when:
|
|
|
215
215
|
|
|
216
216
|
### Memory layout
|
|
217
217
|
|
|
218
|
-
-
|
|
218
|
+
- One native allocation for control bytes followed by slots (16-byte aligned). `stats[:memory_bytes]` still counts `capacity * (1 + sizeof(Slot))`.
|
|
219
219
|
- Native arrays are allocated outside Ruby's object heap; keys and values are still marked for GC.
|
|
220
220
|
- Slot memory is not zero-initialized on allocation; slots are read only after their control byte marks them live.
|
|
221
221
|
|
|
@@ -231,6 +231,7 @@ bundle exec rake compile
|
|
|
231
231
|
```bash
|
|
232
232
|
bundle exec ruby test/hash_api_test.rb
|
|
233
233
|
bundle exec ruby test/string_key_mutation_test.rb
|
|
234
|
+
bundle exec ruby test/safety_and_encoding_test.rb
|
|
234
235
|
```
|
|
235
236
|
|
|
236
237
|
## 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,28 +419,64 @@ 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) {
|
|
361
|
-
size_t min_groups =
|
|
454
|
+
size_t min_groups = min_capacity / GROUP_SIZE + (min_capacity % GROUP_SIZE != 0);
|
|
362
455
|
size_t num_groups = 1;
|
|
363
|
-
while (num_groups < min_groups)
|
|
456
|
+
while (num_groups < min_groups) {
|
|
457
|
+
if (num_groups > SIZE_MAX / 2) {
|
|
458
|
+
rb_raise(rb_eArgError, "SwissHash capacity is too large");
|
|
459
|
+
}
|
|
364
460
|
num_groups <<= 1;
|
|
461
|
+
}
|
|
365
462
|
if (num_groups < 2)
|
|
366
463
|
num_groups = 2;
|
|
367
464
|
|
|
465
|
+
if (num_groups > SIZE_MAX / GROUP_SIZE) {
|
|
466
|
+
rb_raise(rb_eArgError, "SwissHash capacity is too large");
|
|
467
|
+
}
|
|
368
468
|
size_t capacity = num_groups * GROUP_SIZE;
|
|
469
|
+
uint8_t *ctrl = NULL;
|
|
470
|
+
Slot *slots = NULL;
|
|
369
471
|
|
|
472
|
+
if (!swiss_alloc_block(capacity, &ctrl, &slots)) {
|
|
473
|
+
rb_raise(rb_eNoMemError, "failed to allocate SwissHash");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
memset(ctrl, CTRL_EMPTY, capacity);
|
|
477
|
+
uint8_t *old_ctrl = sh->ctrl;
|
|
478
|
+
sh->ctrl = ctrl;
|
|
479
|
+
sh->slots = slots;
|
|
370
480
|
sh->num_groups = num_groups;
|
|
371
481
|
sh->group_mask = num_groups - 1;
|
|
372
482
|
sh->capacity = capacity;
|
|
@@ -374,19 +484,7 @@ static void swiss_init(SwissHash *sh, size_t min_capacity) {
|
|
|
374
484
|
sh->tombstone_count = 0;
|
|
375
485
|
sh->mutating = 0;
|
|
376
486
|
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
|
-
memset(sh->ctrl, CTRL_EMPTY, capacity);
|
|
487
|
+
free(old_ctrl);
|
|
390
488
|
}
|
|
391
489
|
|
|
392
490
|
typedef struct {
|
|
@@ -410,28 +508,75 @@ SH_ALWAYS_INLINE void probe_next(ProbeSeq *ps) {
|
|
|
410
508
|
|
|
411
509
|
#define GROUP_OFF(gi) ((gi) * GROUP_SIZE)
|
|
412
510
|
|
|
413
|
-
|
|
414
|
-
|
|
511
|
+
#ifdef SWISS_USE_VEC
|
|
512
|
+
#define SH_SCAN_GROUP(sh, off, h2, match, empty) \
|
|
513
|
+
do { \
|
|
514
|
+
ctrl_vec_t cv = ctrl_load((sh)->ctrl + (off)); \
|
|
515
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
516
|
+
(match) = ctrl_match_h2_vec(cv, (h2)); \
|
|
517
|
+
(empty) = ctrl_match_empty_vec(cv); \
|
|
518
|
+
} while (0)
|
|
519
|
+
#define SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail) \
|
|
520
|
+
do { \
|
|
521
|
+
ctrl_vec_t cv = ctrl_load((sh)->ctrl + (off)); \
|
|
522
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
523
|
+
(match) = ctrl_match_h2_vec(cv, (h2)); \
|
|
524
|
+
(empty) = ctrl_match_empty_vec(cv); \
|
|
525
|
+
(avail) = ctrl_match_empty_or_deleted_vec(cv); \
|
|
526
|
+
} while (0)
|
|
527
|
+
#else
|
|
528
|
+
#define SH_SCAN_GROUP(sh, off, h2, match, empty) \
|
|
529
|
+
do { \
|
|
530
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
531
|
+
(match) = ctrl_match_h2_raw((sh)->ctrl + (off), (h2)); \
|
|
532
|
+
(empty) = ctrl_match_empty_raw((sh)->ctrl + (off)); \
|
|
533
|
+
} while (0)
|
|
534
|
+
#define SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail) \
|
|
535
|
+
do { \
|
|
536
|
+
SH_PREFETCH(&(sh)->slots[off]); \
|
|
537
|
+
(match) = ctrl_match_h2_raw((sh)->ctrl + (off), (h2)); \
|
|
538
|
+
(empty) = ctrl_match_empty_raw((sh)->ctrl + (off)); \
|
|
539
|
+
(avail) = ctrl_match_empty_or_deleted_raw((sh)->ctrl + (off)); \
|
|
540
|
+
} while (0)
|
|
541
|
+
#endif
|
|
542
|
+
|
|
543
|
+
static inline VALUE *swiss_lookup_hashed(SwissHash *sh, VALUE key, uint64_t hash, int identity_only) {
|
|
415
544
|
uint8_t h2 = H2(hash);
|
|
416
545
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
417
546
|
|
|
418
547
|
for (;;) {
|
|
419
548
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
549
|
+
ctrl_mask_t match, empty;
|
|
550
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
551
|
+
|
|
552
|
+
while (match) {
|
|
553
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
554
|
+
Slot *s = &sh->slots[off + slot];
|
|
555
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
556
|
+
return &s->value;
|
|
557
|
+
}
|
|
558
|
+
match &= match - 1;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (empty)
|
|
562
|
+
return NULL;
|
|
563
|
+
probe_next(&ps);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
static inline VALUE *swiss_lookup_string(SwissHash *sh, VALUE key, uint64_t hash) {
|
|
568
|
+
uint8_t h2 = H2(hash);
|
|
569
|
+
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
570
|
+
|
|
571
|
+
for (;;) {
|
|
572
|
+
size_t off = GROUP_OFF(ps.group_idx);
|
|
573
|
+
ctrl_mask_t match, empty;
|
|
574
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
420
575
|
|
|
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
576
|
while (match) {
|
|
432
|
-
int slot =
|
|
577
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
433
578
|
Slot *s = &sh->slots[off + slot];
|
|
434
|
-
if (RB_LIKELY(s->key == key) ||
|
|
579
|
+
if (RB_LIKELY(s->key == key) || (RB_TYPE_P(s->key, T_STRING) && strings_equal(s->key, key))) {
|
|
435
580
|
return &s->value;
|
|
436
581
|
}
|
|
437
582
|
match &= match - 1;
|
|
@@ -443,23 +588,75 @@ static VALUE *swiss_lookup(SwissHash *sh, VALUE key) {
|
|
|
443
588
|
}
|
|
444
589
|
}
|
|
445
590
|
|
|
591
|
+
typedef struct {
|
|
592
|
+
SwissHash *sh;
|
|
593
|
+
VALUE key;
|
|
594
|
+
VALUE value;
|
|
595
|
+
VALUE result;
|
|
596
|
+
VALUE *found;
|
|
597
|
+
} CallbackCtx;
|
|
598
|
+
|
|
599
|
+
SH_ALWAYS_INLINE int key_may_callback(VALUE key) {
|
|
600
|
+
return !(FIXNUM_P(key) || SYMBOL_P(key) || RB_TYPE_P(key, T_STRING));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
static void swiss_lock(SwissHash *sh) {
|
|
604
|
+
if (sh->mutating) {
|
|
605
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
606
|
+
"(#hash or #eql? callback modified the same table)");
|
|
607
|
+
}
|
|
608
|
+
sh->mutating = 1;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
static VALUE swiss_unlock_ensure(VALUE shv) {
|
|
612
|
+
((SwissHash *)shv)->mutating = 0;
|
|
613
|
+
return Qnil;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
static VALUE swiss_lookup_custom(VALUE arg) {
|
|
617
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
618
|
+
c->found = swiss_lookup_hashed(c->sh, c->key, compute_hash(c->key), 0);
|
|
619
|
+
return Qnil;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static inline VALUE *swiss_lookup(SwissHash *sh, VALUE key) {
|
|
623
|
+
if (FIXNUM_P(key)) {
|
|
624
|
+
return swiss_lookup_hashed(sh, key, hash_fixnum(key), 1);
|
|
625
|
+
}
|
|
626
|
+
if (SYMBOL_P(key)) {
|
|
627
|
+
return swiss_lookup_hashed(sh, key, hash_immediate(key), 1);
|
|
628
|
+
}
|
|
629
|
+
if (RB_TYPE_P(key, T_STRING)) {
|
|
630
|
+
return swiss_lookup_string(sh, key, hash_string(key));
|
|
631
|
+
}
|
|
632
|
+
if (sh->mutating) {
|
|
633
|
+
return swiss_lookup_hashed(sh, key, compute_hash(key), 0);
|
|
634
|
+
}
|
|
635
|
+
CallbackCtx ctx = {sh, key, Qnil, Qnil, NULL};
|
|
636
|
+
swiss_lock(sh);
|
|
637
|
+
rb_ensure(swiss_lookup_custom, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
638
|
+
return ctx.found;
|
|
639
|
+
}
|
|
640
|
+
|
|
446
641
|
static void swiss_grow(SwissHash *sh);
|
|
447
642
|
static void swiss_compact(SwissHash *sh);
|
|
448
643
|
|
|
449
|
-
SH_ALWAYS_INLINE void
|
|
644
|
+
SH_ALWAYS_INLINE void swiss_insert_rehash_into(uint8_t *ctrl, Slot *slots, size_t group_mask,
|
|
645
|
+
size_t *size, size_t *growth_left,
|
|
646
|
+
uint64_t hash, VALUE key, VALUE value) {
|
|
450
647
|
uint8_t h2 = H2(hash);
|
|
451
|
-
ProbeSeq ps = probe_start(H1(hash),
|
|
648
|
+
ProbeSeq ps = probe_start(H1(hash), group_mask);
|
|
452
649
|
|
|
453
650
|
for (;;) {
|
|
454
651
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
455
|
-
|
|
652
|
+
ctrl_mask_t empty_mask = ctrl_match_empty(ctrl + off);
|
|
456
653
|
if (empty_mask) {
|
|
457
|
-
size_t idx = off +
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
654
|
+
size_t idx = off + (size_t)CTRL_MASK_SLOT(empty_mask);
|
|
655
|
+
ctrl[idx] = h2;
|
|
656
|
+
slots[idx].key = key;
|
|
657
|
+
slots[idx].value = value;
|
|
658
|
+
(*size)++;
|
|
659
|
+
(*growth_left)--;
|
|
463
660
|
return;
|
|
464
661
|
}
|
|
465
662
|
probe_next(&ps);
|
|
@@ -470,44 +667,87 @@ static inline int should_compact(SwissHash *sh) {
|
|
|
470
667
|
return sh->tombstone_count >= sh->capacity / TOMBSTONE_COMPACT_DIVISOR;
|
|
471
668
|
}
|
|
472
669
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
uint8_t *old_ctrl
|
|
478
|
-
Slot *old_slots
|
|
670
|
+
typedef struct {
|
|
671
|
+
SwissHash *sh;
|
|
672
|
+
uint8_t *new_ctrl;
|
|
673
|
+
Slot *new_slots;
|
|
674
|
+
const uint8_t *old_ctrl;
|
|
675
|
+
const Slot *old_slots;
|
|
676
|
+
size_t old_cap;
|
|
677
|
+
size_t new_cap;
|
|
678
|
+
size_t new_num_groups;
|
|
679
|
+
} RebuildCtx;
|
|
680
|
+
|
|
681
|
+
static VALUE swiss_rebuild_body(VALUE arg) {
|
|
682
|
+
RebuildCtx *ctx = (RebuildCtx *)arg;
|
|
683
|
+
size_t group_mask = ctx->new_num_groups - 1;
|
|
684
|
+
size_t size = 0;
|
|
685
|
+
size_t growth_left = ctx->new_cap * MAX_LOAD_NUM / MAX_LOAD_DEN;
|
|
686
|
+
|
|
687
|
+
for (size_t i = 0; i < ctx->old_cap; i++) {
|
|
688
|
+
uint8_t c = ctx->old_ctrl[i];
|
|
689
|
+
if (c != CTRL_EMPTY && c != CTRL_DELETED) {
|
|
690
|
+
VALUE key = ctx->old_slots[i].key;
|
|
691
|
+
uint64_t hash = FIXNUM_P(key) ? hash_fixnum(key) : compute_hash(key);
|
|
692
|
+
swiss_insert_rehash_into(ctx->new_ctrl, ctx->new_slots, group_mask,
|
|
693
|
+
&size, &growth_left, hash, key, ctx->old_slots[i].value);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
479
696
|
|
|
480
|
-
sh->ctrl
|
|
481
|
-
sh->
|
|
697
|
+
free(ctx->sh->ctrl);
|
|
698
|
+
ctx->sh->ctrl = ctx->new_ctrl;
|
|
699
|
+
ctx->sh->slots = ctx->new_slots;
|
|
700
|
+
ctx->sh->num_groups = ctx->new_num_groups;
|
|
701
|
+
ctx->sh->group_mask = group_mask;
|
|
702
|
+
ctx->sh->capacity = ctx->new_cap;
|
|
703
|
+
ctx->sh->size = size;
|
|
704
|
+
ctx->sh->growth_left = growth_left;
|
|
705
|
+
ctx->sh->tombstone_count = 0;
|
|
706
|
+
ctx->new_ctrl = NULL;
|
|
707
|
+
ctx->new_slots = NULL;
|
|
708
|
+
return Qnil;
|
|
709
|
+
}
|
|
482
710
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
sh->growth_left = 0;
|
|
490
|
-
rb_raise(rb_eNoMemError, "failed to compact SwissHash");
|
|
711
|
+
static VALUE swiss_rebuild_ensure(VALUE arg) {
|
|
712
|
+
RebuildCtx *ctx = (RebuildCtx *)arg;
|
|
713
|
+
if (ctx->new_ctrl) {
|
|
714
|
+
free(ctx->new_ctrl);
|
|
715
|
+
ctx->new_ctrl = NULL;
|
|
716
|
+
ctx->new_slots = NULL;
|
|
491
717
|
}
|
|
718
|
+
return Qnil;
|
|
719
|
+
}
|
|
492
720
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
sh
|
|
721
|
+
static void swiss_rebuild(SwissHash *sh, size_t new_num_groups) {
|
|
722
|
+
RebuildCtx ctx;
|
|
723
|
+
memset(&ctx, 0, sizeof(ctx));
|
|
724
|
+
ctx.sh = sh;
|
|
725
|
+
ctx.old_ctrl = sh->ctrl;
|
|
726
|
+
ctx.old_slots = sh->slots;
|
|
727
|
+
ctx.old_cap = sh->capacity;
|
|
728
|
+
ctx.new_num_groups = new_num_groups;
|
|
729
|
+
ctx.new_cap = new_num_groups * GROUP_SIZE;
|
|
497
730
|
|
|
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
|
-
}
|
|
731
|
+
if (!swiss_alloc_block(ctx.new_cap, &ctx.new_ctrl, &ctx.new_slots)) {
|
|
732
|
+
rb_raise(rb_eNoMemError, "failed to rebuild SwissHash");
|
|
504
733
|
}
|
|
734
|
+
memset(ctx.new_ctrl, CTRL_EMPTY, ctx.new_cap);
|
|
735
|
+
rb_ensure(swiss_rebuild_body, (VALUE)&ctx, swiss_rebuild_ensure, (VALUE)&ctx);
|
|
736
|
+
}
|
|
505
737
|
|
|
506
|
-
|
|
507
|
-
|
|
738
|
+
SH_ALWAYS_INLINE void swiss_place_new(SwissHash *sh, size_t insert_idx, uint8_t h2, VALUE key, VALUE value) {
|
|
739
|
+
if (sh->ctrl[insert_idx] == CTRL_EMPTY) {
|
|
740
|
+
sh->growth_left--;
|
|
741
|
+
} else {
|
|
742
|
+
sh->tombstone_count--;
|
|
743
|
+
}
|
|
744
|
+
sh->ctrl[insert_idx] = h2;
|
|
745
|
+
sh->slots[insert_idx].key = key;
|
|
746
|
+
sh->slots[insert_idx].value = value;
|
|
747
|
+
sh->size++;
|
|
508
748
|
}
|
|
509
749
|
|
|
510
|
-
static VALUE
|
|
750
|
+
static VALUE swiss_insert_hashed(SwissHash *sh, VALUE key, VALUE value, uint64_t hash, int identity_only, int may_callback) {
|
|
511
751
|
if (sh->growth_left == 0) {
|
|
512
752
|
if (should_compact(sh)) {
|
|
513
753
|
swiss_compact(sh);
|
|
@@ -516,32 +756,33 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
516
756
|
}
|
|
517
757
|
}
|
|
518
758
|
|
|
519
|
-
uint64_t hash = compute_hash(key);
|
|
520
759
|
uint8_t h2 = H2(hash);
|
|
521
760
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
522
|
-
|
|
523
761
|
size_t insert_idx = (size_t)-1;
|
|
524
762
|
|
|
525
763
|
for (;;) {
|
|
526
764
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
527
|
-
|
|
528
|
-
#
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
765
|
+
ctrl_mask_t match, empty, avail;
|
|
766
|
+
#ifdef SWISS_USE_VEC
|
|
767
|
+
{
|
|
768
|
+
ctrl_vec_t cv = ctrl_load(sh->ctrl + off);
|
|
769
|
+
SH_PREFETCH(&sh->slots[off]);
|
|
770
|
+
match = ctrl_match_h2_vec(cv, h2);
|
|
771
|
+
empty = ctrl_match_empty_vec(cv);
|
|
772
|
+
avail = (sh->tombstone_count == 0) ? empty : ctrl_match_empty_or_deleted_vec(cv);
|
|
773
|
+
}
|
|
534
774
|
#else
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
775
|
+
SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail);
|
|
776
|
+
if (sh->tombstone_count == 0) {
|
|
777
|
+
avail = empty;
|
|
778
|
+
}
|
|
539
779
|
#endif
|
|
780
|
+
|
|
540
781
|
while (match) {
|
|
541
|
-
int slot =
|
|
782
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
542
783
|
size_t idx = off + slot;
|
|
543
784
|
Slot *s = &sh->slots[idx];
|
|
544
|
-
if (RB_LIKELY(s->key == key) || keys_equal(s->key, key)) {
|
|
785
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
545
786
|
s->value = value;
|
|
546
787
|
return value;
|
|
547
788
|
}
|
|
@@ -549,7 +790,7 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
549
790
|
}
|
|
550
791
|
|
|
551
792
|
if (insert_idx == (size_t)-1 && avail) {
|
|
552
|
-
insert_idx = off +
|
|
793
|
+
insert_idx = off + (size_t)CTRL_MASK_SLOT(avail);
|
|
553
794
|
}
|
|
554
795
|
|
|
555
796
|
if (empty)
|
|
@@ -557,108 +798,172 @@ static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
|
557
798
|
probe_next(&ps);
|
|
558
799
|
}
|
|
559
800
|
|
|
560
|
-
|
|
801
|
+
swiss_place_new(sh, insert_idx, h2, key, value);
|
|
802
|
+
return value;
|
|
803
|
+
}
|
|
561
804
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
805
|
+
SH_ALWAYS_INLINE VALUE swiss_insert_fixnum(SwissHash *sh, VALUE key, VALUE value) {
|
|
806
|
+
if (RB_UNLIKELY(sh->growth_left == 0)) {
|
|
807
|
+
if (should_compact(sh)) {
|
|
808
|
+
swiss_compact(sh);
|
|
809
|
+
} else {
|
|
810
|
+
swiss_grow(sh);
|
|
811
|
+
}
|
|
566
812
|
}
|
|
567
|
-
sh->ctrl[insert_idx] = h2;
|
|
568
|
-
sh->slots[insert_idx].key = key;
|
|
569
|
-
sh->slots[insert_idx].value = value;
|
|
570
|
-
sh->size++;
|
|
571
813
|
|
|
572
|
-
|
|
814
|
+
uint64_t hash = hash_fixnum(key);
|
|
815
|
+
uint8_t h2 = H2(hash);
|
|
816
|
+
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
817
|
+
size_t insert_idx = (size_t)-1;
|
|
818
|
+
|
|
819
|
+
for (;;) {
|
|
820
|
+
size_t off = GROUP_OFF(ps.group_idx);
|
|
821
|
+
ctrl_mask_t match, empty, avail;
|
|
822
|
+
#ifdef SWISS_USE_VEC
|
|
823
|
+
ctrl_vec_t cv = ctrl_load(sh->ctrl + off);
|
|
824
|
+
SH_PREFETCH(&sh->slots[off]);
|
|
825
|
+
match = ctrl_match_h2_vec(cv, h2);
|
|
826
|
+
empty = ctrl_match_empty_vec(cv);
|
|
827
|
+
avail = (sh->tombstone_count == 0) ? empty : ctrl_match_empty_or_deleted_vec(cv);
|
|
828
|
+
#else
|
|
829
|
+
SH_SCAN_GROUP_INSERT(sh, off, h2, match, empty, avail);
|
|
830
|
+
if (sh->tombstone_count == 0) {
|
|
831
|
+
avail = empty;
|
|
832
|
+
}
|
|
833
|
+
#endif
|
|
834
|
+
while (match) {
|
|
835
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
836
|
+
if (RB_LIKELY(sh->slots[off + slot].key == key)) {
|
|
837
|
+
sh->slots[off + slot].value = value;
|
|
838
|
+
return value;
|
|
839
|
+
}
|
|
840
|
+
match &= match - 1;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (insert_idx == (size_t)-1 && avail) {
|
|
844
|
+
insert_idx = off + (size_t)CTRL_MASK_SLOT(avail);
|
|
845
|
+
}
|
|
846
|
+
if (RB_LIKELY(empty))
|
|
847
|
+
break;
|
|
848
|
+
probe_next(&ps);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
swiss_place_new(sh, insert_idx, h2, key, value);
|
|
573
852
|
return value;
|
|
574
853
|
}
|
|
575
854
|
|
|
576
|
-
static VALUE
|
|
855
|
+
static VALUE swiss_insert_unlocked(VALUE arg) {
|
|
856
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
857
|
+
if (FIXNUM_P(c->key)) {
|
|
858
|
+
c->result = swiss_insert_fixnum(c->sh, c->key, c->value);
|
|
859
|
+
} else if (SYMBOL_P(c->key)) {
|
|
860
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, hash_immediate(c->key), 1, 0);
|
|
861
|
+
} else if (RB_TYPE_P(c->key, T_STRING)) {
|
|
862
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, hash_string(c->key), 0, 0);
|
|
863
|
+
} else {
|
|
864
|
+
c->result = swiss_insert_hashed(c->sh, c->key, c->value, compute_hash(c->key), 0, 0);
|
|
865
|
+
}
|
|
866
|
+
return c->result;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
static VALUE swiss_insert(SwissHash *sh, VALUE key, VALUE value) {
|
|
870
|
+
if (sh->mutating) {
|
|
871
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
872
|
+
"(#hash or #eql? callback modified the same table)");
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
CallbackCtx ctx = {sh, key, value, Qnil, NULL};
|
|
876
|
+
if (!key_may_callback(key) && sh->growth_left != 0) {
|
|
877
|
+
return swiss_insert_unlocked((VALUE)&ctx);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
swiss_lock(sh);
|
|
881
|
+
rb_ensure(swiss_insert_unlocked, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
882
|
+
return ctx.result;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
static VALUE swiss_delete_unlocked(VALUE arg) {
|
|
886
|
+
CallbackCtx *c = (CallbackCtx *)arg;
|
|
887
|
+
SwissHash *sh = c->sh;
|
|
888
|
+
VALUE key = c->key;
|
|
577
889
|
uint64_t hash = compute_hash(key);
|
|
890
|
+
int identity_only = FIXNUM_P(key) || SYMBOL_P(key);
|
|
578
891
|
uint8_t h2 = H2(hash);
|
|
579
892
|
ProbeSeq ps = probe_start(H1(hash), sh->group_mask);
|
|
580
893
|
|
|
581
894
|
for (;;) {
|
|
582
895
|
size_t off = GROUP_OFF(ps.group_idx);
|
|
896
|
+
ctrl_mask_t match, empty;
|
|
897
|
+
SH_SCAN_GROUP(sh, off, h2, match, empty);
|
|
583
898
|
|
|
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
899
|
while (match) {
|
|
595
|
-
int slot =
|
|
900
|
+
int slot = CTRL_MASK_SLOT(match);
|
|
596
901
|
size_t idx = off + slot;
|
|
597
902
|
Slot *s = &sh->slots[idx];
|
|
598
|
-
if (RB_LIKELY(s->key == key) || keys_equal(s->key, key)) {
|
|
903
|
+
if (RB_LIKELY(s->key == key) || (!identity_only && keys_equal(s->key, key))) {
|
|
599
904
|
VALUE old_value = s->value;
|
|
600
|
-
|
|
601
|
-
MUTATE_GUARD_BEGIN(sh);
|
|
602
905
|
sh->ctrl[idx] = CTRL_DELETED;
|
|
603
906
|
sh->slots[idx].key = Qnil;
|
|
604
907
|
sh->slots[idx].value = Qnil;
|
|
605
908
|
sh->size--;
|
|
606
909
|
sh->tombstone_count++;
|
|
607
|
-
|
|
608
|
-
|
|
910
|
+
c->result = old_value;
|
|
609
911
|
return old_value;
|
|
610
912
|
}
|
|
611
913
|
match &= match - 1;
|
|
612
914
|
}
|
|
613
915
|
|
|
614
|
-
if (empty)
|
|
916
|
+
if (empty) {
|
|
917
|
+
c->result = Qnil;
|
|
615
918
|
return Qnil;
|
|
919
|
+
}
|
|
616
920
|
probe_next(&ps);
|
|
617
921
|
}
|
|
618
922
|
}
|
|
619
923
|
|
|
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));
|
|
924
|
+
static VALUE swiss_delete(SwissHash *sh, VALUE key) {
|
|
925
|
+
if (sh->mutating) {
|
|
926
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
927
|
+
"(#hash or #eql? callback modified the same table)");
|
|
928
|
+
}
|
|
632
929
|
|
|
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");
|
|
930
|
+
CallbackCtx ctx = {sh, key, Qnil, Qnil, NULL};
|
|
931
|
+
if (!key_may_callback(key)) {
|
|
932
|
+
return swiss_delete_unlocked((VALUE)&ctx);
|
|
641
933
|
}
|
|
642
934
|
|
|
643
|
-
|
|
935
|
+
swiss_lock(sh);
|
|
936
|
+
rb_ensure(swiss_delete_unlocked, (VALUE)&ctx, swiss_unlock_ensure, (VALUE)sh);
|
|
937
|
+
return ctx.result;
|
|
938
|
+
}
|
|
644
939
|
|
|
645
|
-
|
|
646
|
-
sh
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
sh->growth_left = new_cap * MAX_LOAD_NUM / MAX_LOAD_DEN;
|
|
650
|
-
sh->tombstone_count = 0;
|
|
940
|
+
typedef struct {
|
|
941
|
+
SwissHash *sh;
|
|
942
|
+
size_t new_num_groups;
|
|
943
|
+
} RebuildCall;
|
|
651
944
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
945
|
+
static VALUE swiss_rebuild_call(VALUE arg) {
|
|
946
|
+
RebuildCall *c = (RebuildCall *)arg;
|
|
947
|
+
swiss_rebuild(c->sh, c->new_num_groups);
|
|
948
|
+
return Qnil;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
static void swiss_rebuild_locked(SwissHash *sh, size_t new_num_groups) {
|
|
952
|
+
RebuildCall call = {sh, new_num_groups};
|
|
953
|
+
if (sh->mutating) {
|
|
954
|
+
swiss_rebuild(sh, new_num_groups);
|
|
955
|
+
return;
|
|
658
956
|
}
|
|
957
|
+
swiss_lock(sh);
|
|
958
|
+
rb_ensure(swiss_rebuild_call, (VALUE)&call, swiss_unlock_ensure, (VALUE)sh);
|
|
959
|
+
}
|
|
659
960
|
|
|
660
|
-
|
|
661
|
-
|
|
961
|
+
static void swiss_grow(SwissHash *sh) {
|
|
962
|
+
swiss_rebuild_locked(sh, sh->num_groups * 2);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
static void swiss_compact(SwissHash *sh) {
|
|
966
|
+
swiss_rebuild_locked(sh, sh->num_groups);
|
|
662
967
|
}
|
|
663
968
|
|
|
664
969
|
static void swiss_hash_mark(void *ptr) {
|
|
@@ -712,6 +1017,10 @@ static VALUE swiss_hash_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
712
1017
|
SwissHash *sh;
|
|
713
1018
|
TypedData_Get_Struct(self, SwissHash, &swiss_hash_type, sh);
|
|
714
1019
|
|
|
1020
|
+
if (sh->mutating) {
|
|
1021
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected");
|
|
1022
|
+
}
|
|
1023
|
+
|
|
715
1024
|
size_t capacity = NIL_P(capacity_val) ? 16 : NUM2SIZET(capacity_val);
|
|
716
1025
|
swiss_init(sh, capacity);
|
|
717
1026
|
|
|
@@ -728,10 +1037,6 @@ static VALUE swiss_hash_initialize_copy(VALUE self, VALUE original) {
|
|
|
728
1037
|
TypedData_Get_Struct(original, SwissHash, &swiss_hash_type, src);
|
|
729
1038
|
TypedData_Get_Struct(self, SwissHash, &swiss_hash_type, dst);
|
|
730
1039
|
|
|
731
|
-
if (dst->ctrl || dst->slots) {
|
|
732
|
-
swiss_free_arrays(dst);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
1040
|
if (!src->ctrl || !src->slots) {
|
|
736
1041
|
swiss_init(dst, 16);
|
|
737
1042
|
return self;
|
|
@@ -750,7 +1055,14 @@ static VALUE swiss_hash_initialize_copy(VALUE self, VALUE original) {
|
|
|
750
1055
|
|
|
751
1056
|
static VALUE swiss_hash_aset(VALUE self, VALUE key, VALUE value) {
|
|
752
1057
|
SwissHash *sh = (SwissHash *)RTYPEDDATA_DATA(self);
|
|
753
|
-
if (RB_UNLIKELY(
|
|
1058
|
+
if (RB_UNLIKELY(sh->mutating)) {
|
|
1059
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
1060
|
+
"(#hash or #eql? callback modified the same table)");
|
|
1061
|
+
}
|
|
1062
|
+
if (FIXNUM_P(key)) {
|
|
1063
|
+
return swiss_insert_fixnum(sh, key, value);
|
|
1064
|
+
}
|
|
1065
|
+
if (RB_UNLIKELY(!SYMBOL_P(key))) {
|
|
754
1066
|
key = prepare_key(key);
|
|
755
1067
|
}
|
|
756
1068
|
return swiss_insert(sh, key, value);
|
|
@@ -758,7 +1070,12 @@ static VALUE swiss_hash_aset(VALUE self, VALUE key, VALUE value) {
|
|
|
758
1070
|
|
|
759
1071
|
static VALUE swiss_hash_aref(VALUE self, VALUE key) {
|
|
760
1072
|
SwissHash *sh = (SwissHash *)RTYPEDDATA_DATA(self);
|
|
761
|
-
VALUE *val
|
|
1073
|
+
VALUE *val;
|
|
1074
|
+
if (FIXNUM_P(key)) {
|
|
1075
|
+
val = swiss_lookup_hashed(sh, key, hash_fixnum(key), 1);
|
|
1076
|
+
} else {
|
|
1077
|
+
val = swiss_lookup(sh, key);
|
|
1078
|
+
}
|
|
762
1079
|
return val ? *val : Qnil;
|
|
763
1080
|
}
|
|
764
1081
|
|
|
@@ -783,6 +1100,11 @@ static VALUE swiss_hash_clear(VALUE self) {
|
|
|
783
1100
|
SwissHash *sh;
|
|
784
1101
|
TypedData_Get_Struct(self, SwissHash, &swiss_hash_type, sh);
|
|
785
1102
|
|
|
1103
|
+
if (RB_UNLIKELY(sh->mutating)) {
|
|
1104
|
+
rb_raise(rb_eRuntimeError, "SwissHash: reentrant modification detected "
|
|
1105
|
+
"(#hash or #eql? callback modified the same table)");
|
|
1106
|
+
}
|
|
1107
|
+
|
|
786
1108
|
memset(sh->ctrl, CTRL_EMPTY, sh->capacity);
|
|
787
1109
|
memset(sh->slots, 0, sh->capacity * sizeof(Slot));
|
|
788
1110
|
sh->size = 0;
|
|
@@ -1057,8 +1379,16 @@ static VALUE swiss_hash_merge(int argc, VALUE *argv, VALUE self) {
|
|
|
1057
1379
|
}
|
|
1058
1380
|
|
|
1059
1381
|
static VALUE swiss_hash_replace(VALUE self, VALUE other) {
|
|
1382
|
+
if (self == other) {
|
|
1383
|
+
return self;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
VALUE converted = rb_check_hash_type(other);
|
|
1387
|
+
if (NIL_P(converted) && !RTEST(rb_obj_is_kind_of(other, cSwissHashHash))) {
|
|
1388
|
+
rb_raise(rb_eTypeError, "no implicit conversion of %s into Hash", rb_obj_classname(other));
|
|
1389
|
+
}
|
|
1060
1390
|
swiss_hash_clear(self);
|
|
1061
|
-
VALUE argv[1] = {other};
|
|
1391
|
+
VALUE argv[1] = {NIL_P(converted) ? other : converted};
|
|
1062
1392
|
swiss_hash_merge_bang(1, argv, self);
|
|
1063
1393
|
return self;
|
|
1064
1394
|
}
|
|
@@ -1401,6 +1731,8 @@ static VALUE swiss_hash_stats(VALUE self) {
|
|
|
1401
1731
|
|
|
1402
1732
|
#ifdef SWISS_USE_SSE2
|
|
1403
1733
|
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("SSE2"));
|
|
1734
|
+
#elif defined(SWISS_USE_NEON)
|
|
1735
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("NEON"));
|
|
1404
1736
|
#else
|
|
1405
1737
|
rb_hash_aset(hash, ID2SYM(rb_intern("simd")), rb_str_new_cstr("SWAR"));
|
|
1406
1738
|
#endif
|
data/lib/swiss_hash/version.rb
CHANGED