usearch 2.15.3 → 2.16.0
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.
- package/README.md +1 -1
- package/cpp/test.cpp +16 -0
- package/include/usearch/index.hpp +21 -5
- package/include/usearch/index_dense.hpp +6 -1
- package/include/usearch/index_plugins.hpp +345 -8
- package/javascript/lib.cpp +17 -5
- package/javascript/usearch.test.js +190 -21
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/.git-blame-ignore-revs +2 -0
- package/simsimd/CONTRIBUTING.md +71 -23
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +4 -4
- package/simsimd/MANIFEST.in +2 -1
- package/simsimd/Package.swift +15 -3
- package/simsimd/README.md +104 -35
- package/simsimd/binding.gyp +6 -2
- package/simsimd/build.rs +2 -0
- package/simsimd/c/lib.c +180 -99
- package/simsimd/include/simsimd/binary.h +311 -102
- package/simsimd/include/simsimd/curved.h +134 -135
- package/simsimd/include/simsimd/dot.h +448 -274
- package/simsimd/include/simsimd/elementwise.h +1437 -0
- package/simsimd/include/simsimd/geospatial.h +2 -2
- package/simsimd/include/simsimd/mesh.h +2 -2
- package/simsimd/include/simsimd/probability.h +88 -88
- package/simsimd/include/simsimd/simsimd.h +1157 -772
- package/simsimd/include/simsimd/sparse.h +768 -147
- package/simsimd/include/simsimd/spatial.h +955 -376
- package/simsimd/include/simsimd/types.h +145 -71
- package/simsimd/javascript/lib.c +1 -2
- package/simsimd/package.json +4 -4
- package/simsimd/rust/lib.rs +14 -1
- package/simsimd/scripts/bench.cxx +1070 -0
- package/simsimd/scripts/bench.ipynb +187 -0
- package/simsimd/{javascript → scripts}/bench.js +1 -1
- package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
- package/simsimd/scripts/bench_vectors.py +700 -0
- package/simsimd/scripts/bench_vectors_live.py +213 -0
- package/simsimd/scripts/test.c +155 -0
- package/simsimd/{javascript → scripts}/test.mjs +2 -2
- package/simsimd/scripts/test.py +1410 -0
- package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
- package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
- /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
- /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* - Jaccard similarity (Tanimoto coefficient)
|
|
10
10
|
*
|
|
11
11
|
* For hardware architectures:
|
|
12
|
-
* - Arm
|
|
13
|
-
* - x86
|
|
12
|
+
* - Arm: NEON, SVE
|
|
13
|
+
* - x86: Haswell, Ice Lake
|
|
14
14
|
*
|
|
15
15
|
* x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
|
|
16
16
|
* Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
|
|
@@ -60,52 +60,86 @@ SIMSIMD_PUBLIC unsigned char simsimd_popcount_b8(simsimd_b8_t x) {
|
|
|
60
60
|
return lookup_table[x];
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
SIMSIMD_PUBLIC void simsimd_hamming_b8_serial(simsimd_b8_t const*
|
|
64
|
-
simsimd_distance_t*
|
|
63
|
+
SIMSIMD_PUBLIC void simsimd_hamming_b8_serial(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
64
|
+
simsimd_distance_t *result) {
|
|
65
65
|
simsimd_i32_t differences = 0;
|
|
66
|
-
for (simsimd_size_t i = 0; i != n_words; ++i)
|
|
67
|
-
differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
66
|
+
for (simsimd_size_t i = 0; i != n_words; ++i) differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
68
67
|
*result = differences;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const*
|
|
72
|
-
simsimd_distance_t*
|
|
70
|
+
SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
71
|
+
simsimd_distance_t *result) {
|
|
73
72
|
simsimd_i32_t intersection = 0, union_ = 0;
|
|
74
73
|
for (simsimd_size_t i = 0; i != n_words; ++i)
|
|
75
74
|
intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
|
|
76
75
|
*result = (union_ != 0) ? 1 - (simsimd_f64_t)intersection / (simsimd_f64_t)union_ : 1;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
#if
|
|
78
|
+
#if _SIMSIMD_TARGET_ARM
|
|
80
79
|
#if SIMSIMD_TARGET_NEON
|
|
81
80
|
#pragma GCC push_options
|
|
82
81
|
#pragma GCC target("arch=armv8.2-a+simd")
|
|
83
82
|
#pragma clang attribute push(__attribute__((target("arch=armv8.2-a+simd"))), apply_to = function)
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
SIMSIMD_INTERNAL simsimd_u32_t _simsimd_reduce_u8x16_neon(uint8x16_t vec) {
|
|
85
|
+
// Split the vector into two halves and widen to `uint16x8_t`
|
|
86
|
+
uint16x8_t low_half = vmovl_u8(vget_low_u8(vec)); // widen lower 8 elements
|
|
87
|
+
uint16x8_t high_half = vmovl_u8(vget_high_u8(vec)); // widen upper 8 elements
|
|
88
|
+
|
|
89
|
+
// Sum the widened halves
|
|
90
|
+
uint16x8_t sum16 = vaddq_u16(low_half, high_half);
|
|
91
|
+
|
|
92
|
+
// Now reduce the `uint16x8_t` to a single `uint32_t`
|
|
93
|
+
uint32x4_t sum32 = vpaddlq_u16(sum16); // pairwise add into 32-bit integers
|
|
94
|
+
uint64x2_t sum64 = vpaddlq_u32(sum32); // pairwise add into 64-bit integers
|
|
95
|
+
uint32_t final_sum = vaddvq_u64(sum64); // final horizontal add to 32-bit result
|
|
96
|
+
return final_sum;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
SIMSIMD_PUBLIC void simsimd_hamming_b8_neon(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
100
|
+
simsimd_distance_t *result) {
|
|
87
101
|
simsimd_i32_t differences = 0;
|
|
88
102
|
simsimd_size_t i = 0;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
// In each 8-bit word we may have up to 8 differences.
|
|
104
|
+
// So for up-to 31 cycles (31 * 16 = 496 word-dimensions = 3968 bits)
|
|
105
|
+
// we can aggregate the differences into a `uint8x16_t` vector,
|
|
106
|
+
// where each component will be up-to 255.
|
|
107
|
+
while (i + 16 <= n_words) {
|
|
108
|
+
uint8x16_t differences_cycle_vec = vdupq_n_u8(0);
|
|
109
|
+
for (simsimd_size_t cycle = 0; cycle < 31 && i + 16 <= n_words; ++cycle, i += 16) {
|
|
110
|
+
uint8x16_t a_vec = vld1q_u8(a + i);
|
|
111
|
+
uint8x16_t b_vec = vld1q_u8(b + i);
|
|
112
|
+
uint8x16_t xor_count_vec = vcntq_u8(veorq_u8(a_vec, b_vec));
|
|
113
|
+
differences_cycle_vec = vaddq_u8(differences_cycle_vec, xor_count_vec);
|
|
114
|
+
}
|
|
115
|
+
differences += _simsimd_reduce_u8x16_neon(differences_cycle_vec);
|
|
93
116
|
}
|
|
94
117
|
// Handle the tail
|
|
95
|
-
for (; i != n_words; ++i)
|
|
96
|
-
differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
118
|
+
for (; i != n_words; ++i) differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
97
119
|
*result = differences;
|
|
98
120
|
}
|
|
99
121
|
|
|
100
|
-
SIMSIMD_PUBLIC void simsimd_jaccard_b8_neon(simsimd_b8_t const*
|
|
101
|
-
simsimd_distance_t*
|
|
122
|
+
SIMSIMD_PUBLIC void simsimd_jaccard_b8_neon(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
123
|
+
simsimd_distance_t *result) {
|
|
102
124
|
simsimd_i32_t intersection = 0, union_ = 0;
|
|
103
125
|
simsimd_size_t i = 0;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
126
|
+
// In each 8-bit word we may have up to 8 intersections/unions.
|
|
127
|
+
// So for up-to 31 cycles (31 * 16 = 496 word-dimensions = 3968 bits)
|
|
128
|
+
// we can aggregate the intersections/unions into a `uint8x16_t` vector,
|
|
129
|
+
// where each component will be up-to 255.
|
|
130
|
+
while (i + 16 <= n_words) {
|
|
131
|
+
uint8x16_t intersections_cycle_vec = vdupq_n_u8(0);
|
|
132
|
+
uint8x16_t unions_cycle_vec = vdupq_n_u8(0);
|
|
133
|
+
for (simsimd_size_t cycle = 0; cycle < 31 && i + 16 <= n_words; ++cycle, i += 16) {
|
|
134
|
+
uint8x16_t a_vec = vld1q_u8(a + i);
|
|
135
|
+
uint8x16_t b_vec = vld1q_u8(b + i);
|
|
136
|
+
uint8x16_t and_count_vec = vcntq_u8(vandq_u8(a_vec, b_vec));
|
|
137
|
+
uint8x16_t or_count_vec = vcntq_u8(vorrq_u8(a_vec, b_vec));
|
|
138
|
+
intersections_cycle_vec = vaddq_u8(intersections_cycle_vec, and_count_vec);
|
|
139
|
+
unions_cycle_vec = vaddq_u8(unions_cycle_vec, or_count_vec);
|
|
140
|
+
}
|
|
141
|
+
intersection += _simsimd_reduce_u8x16_neon(intersections_cycle_vec);
|
|
142
|
+
union_ += _simsimd_reduce_u8x16_neon(unions_cycle_vec);
|
|
109
143
|
}
|
|
110
144
|
// Handle the tail
|
|
111
145
|
for (; i != n_words; ++i)
|
|
@@ -122,97 +156,274 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_neon(simsimd_b8_t const* a, simsimd_b8_t
|
|
|
122
156
|
#pragma GCC target("arch=armv8.2-a+sve")
|
|
123
157
|
#pragma clang attribute push(__attribute__((target("arch=armv8.2-a+sve"))), apply_to = function)
|
|
124
158
|
|
|
125
|
-
SIMSIMD_PUBLIC void simsimd_hamming_b8_sve(simsimd_b8_t const*
|
|
126
|
-
simsimd_distance_t*
|
|
127
|
-
|
|
159
|
+
SIMSIMD_PUBLIC void simsimd_hamming_b8_sve(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
160
|
+
simsimd_distance_t *result) {
|
|
161
|
+
|
|
162
|
+
// On very small register sizes, NEON is at least as fast as SVE.
|
|
163
|
+
simsimd_size_t const words_per_register = svcntb();
|
|
164
|
+
if (words_per_register <= 32) {
|
|
165
|
+
simsimd_hamming_b8_neon(a, b, n_words, result);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// On larger register sizes, SVE is faster.
|
|
170
|
+
simsimd_size_t i = 0, cycle = 0;
|
|
128
171
|
simsimd_i32_t differences = 0;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
172
|
+
svuint8_t differences_cycle_vec = svdup_n_u8(0);
|
|
173
|
+
svbool_t const all_vec = svptrue_b8();
|
|
174
|
+
while (i < n_words) {
|
|
175
|
+
do {
|
|
176
|
+
svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
|
|
177
|
+
svuint8_t a_vec = svld1_u8(pg_vec, a + i);
|
|
178
|
+
svuint8_t b_vec = svld1_u8(pg_vec, b + i);
|
|
179
|
+
differences_cycle_vec =
|
|
180
|
+
svadd_u8_z(all_vec, differences_cycle_vec, svcnt_u8_x(all_vec, sveor_u8_m(all_vec, a_vec, b_vec)));
|
|
181
|
+
i += words_per_register;
|
|
182
|
+
++cycle;
|
|
183
|
+
} while (i < n_words && cycle < 31);
|
|
184
|
+
differences += svaddv_u8(all_vec, differences_cycle_vec);
|
|
185
|
+
differences_cycle_vec = svdup_n_u8(0);
|
|
186
|
+
cycle = 0; // Reset the cycle counter.
|
|
187
|
+
}
|
|
188
|
+
|
|
136
189
|
*result = differences;
|
|
137
190
|
}
|
|
138
191
|
|
|
139
|
-
SIMSIMD_PUBLIC void simsimd_jaccard_b8_sve(simsimd_b8_t const*
|
|
140
|
-
simsimd_distance_t*
|
|
141
|
-
|
|
192
|
+
SIMSIMD_PUBLIC void simsimd_jaccard_b8_sve(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
193
|
+
simsimd_distance_t *result) {
|
|
194
|
+
|
|
195
|
+
// On very small register sizes, NEON is at least as fast as SVE.
|
|
196
|
+
simsimd_size_t const words_per_register = svcntb();
|
|
197
|
+
if (words_per_register <= 32) {
|
|
198
|
+
simsimd_jaccard_b8_neon(a, b, n_words, result);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// On larger register sizes, SVE is faster.
|
|
203
|
+
simsimd_size_t i = 0, cycle = 0;
|
|
142
204
|
simsimd_i32_t intersection = 0, union_ = 0;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
svuint8_t intersection_cycle_vec = svdup_n_u8(0);
|
|
206
|
+
svuint8_t union_cycle_vec = svdup_n_u8(0);
|
|
207
|
+
svbool_t const all_vec = svptrue_b8();
|
|
208
|
+
while (i < n_words) {
|
|
209
|
+
do {
|
|
210
|
+
svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
|
|
211
|
+
svuint8_t a_vec = svld1_u8(pg_vec, a + i);
|
|
212
|
+
svuint8_t b_vec = svld1_u8(pg_vec, b + i);
|
|
213
|
+
intersection_cycle_vec =
|
|
214
|
+
svadd_u8_z(all_vec, intersection_cycle_vec, svcnt_u8_x(all_vec, svand_u8_m(all_vec, a_vec, b_vec)));
|
|
215
|
+
union_cycle_vec =
|
|
216
|
+
svadd_u8_z(all_vec, union_cycle_vec, svcnt_u8_x(all_vec, svorr_u8_m(all_vec, a_vec, b_vec)));
|
|
217
|
+
i += words_per_register;
|
|
218
|
+
++cycle;
|
|
219
|
+
} while (i < n_words && cycle < 31);
|
|
220
|
+
intersection += svaddv_u8(all_vec, intersection_cycle_vec);
|
|
221
|
+
intersection_cycle_vec = svdup_n_u8(0);
|
|
222
|
+
union_ += svaddv_u8(all_vec, union_cycle_vec);
|
|
223
|
+
union_cycle_vec = svdup_n_u8(0);
|
|
224
|
+
cycle = 0; // Reset the cycle counter.
|
|
225
|
+
}
|
|
226
|
+
|
|
151
227
|
*result = (union_ != 0) ? 1 - (simsimd_f64_t)intersection / (simsimd_f64_t)union_ : 1;
|
|
152
228
|
}
|
|
153
229
|
|
|
154
230
|
#pragma clang attribute pop
|
|
155
231
|
#pragma GCC pop_options
|
|
156
232
|
#endif // SIMSIMD_TARGET_SVE
|
|
157
|
-
#endif //
|
|
233
|
+
#endif // _SIMSIMD_TARGET_ARM
|
|
158
234
|
|
|
159
|
-
#if
|
|
235
|
+
#if _SIMSIMD_TARGET_X86
|
|
160
236
|
#if SIMSIMD_TARGET_ICE
|
|
161
237
|
#pragma GCC push_options
|
|
162
238
|
#pragma GCC target("avx2", "avx512f", "avx512vl", "bmi2", "avx512bw", "avx512vpopcntdq")
|
|
163
|
-
#pragma clang attribute push(__attribute__((target("avx2,avx512f,avx512vl,bmi2,avx512bw,avx512vpopcntdq"))),
|
|
239
|
+
#pragma clang attribute push(__attribute__((target("avx2,avx512f,avx512vl,bmi2,avx512bw,avx512vpopcntdq"))), \
|
|
164
240
|
apply_to = function)
|
|
165
241
|
|
|
166
|
-
SIMSIMD_PUBLIC void simsimd_hamming_b8_ice(simsimd_b8_t const*
|
|
167
|
-
simsimd_distance_t*
|
|
168
|
-
__m512i differences_vec = _mm512_setzero_si512();
|
|
169
|
-
__m512i a_vec, b_vec;
|
|
242
|
+
SIMSIMD_PUBLIC void simsimd_hamming_b8_ice(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
243
|
+
simsimd_distance_t *result) {
|
|
170
244
|
|
|
171
|
-
|
|
172
|
-
|
|
245
|
+
simsimd_size_t xor_count;
|
|
246
|
+
// It's harder to squeeze out performance from tiny representations, so we unroll the loops for binary metrics.
|
|
247
|
+
if (n_words <= 64) { // Up to 512 bits.
|
|
173
248
|
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
|
|
174
|
-
a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
175
|
-
b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
a_vec = _mm512_loadu_epi8(a);
|
|
179
|
-
b_vec = _mm512_loadu_epi8(b);
|
|
180
|
-
a += 64, b += 64, n_words -= 64;
|
|
249
|
+
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
250
|
+
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
251
|
+
__m512i xor_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a_vec, b_vec));
|
|
252
|
+
xor_count = _mm512_reduce_add_epi64(xor_count_vec);
|
|
181
253
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
254
|
+
else if (n_words <= 128) { // Up to 1024 bits.
|
|
255
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 64);
|
|
256
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
257
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
258
|
+
__m512i a2_vec = _mm512_maskz_loadu_epi8(mask, a + 64);
|
|
259
|
+
__m512i b2_vec = _mm512_maskz_loadu_epi8(mask, b + 64);
|
|
260
|
+
__m512i xor1_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a1_vec, b1_vec));
|
|
261
|
+
__m512i xor2_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a2_vec, b2_vec));
|
|
262
|
+
xor_count = _mm512_reduce_add_epi64(_mm512_add_epi64(xor2_count_vec, xor1_count_vec));
|
|
263
|
+
}
|
|
264
|
+
else if (n_words <= 196) { // Up to 1568 bits.
|
|
265
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 128);
|
|
266
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
267
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
268
|
+
__m512i a2_vec = _mm512_loadu_epi8(a + 64);
|
|
269
|
+
__m512i b2_vec = _mm512_loadu_epi8(b + 64);
|
|
270
|
+
__m512i a3_vec = _mm512_maskz_loadu_epi8(mask, a + 128);
|
|
271
|
+
__m512i b3_vec = _mm512_maskz_loadu_epi8(mask, b + 128);
|
|
272
|
+
__m512i xor1_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a1_vec, b1_vec));
|
|
273
|
+
__m512i xor2_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a2_vec, b2_vec));
|
|
274
|
+
__m512i xor3_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a3_vec, b3_vec));
|
|
275
|
+
xor_count =
|
|
276
|
+
_mm512_reduce_add_epi64(_mm512_add_epi64(xor3_count_vec, _mm512_add_epi64(xor2_count_vec, xor1_count_vec)));
|
|
277
|
+
}
|
|
278
|
+
else if (n_words <= 256) { // Up to 2048 bits.
|
|
279
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 192);
|
|
280
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
281
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
282
|
+
__m512i a2_vec = _mm512_loadu_epi8(a + 64);
|
|
283
|
+
__m512i b2_vec = _mm512_loadu_epi8(b + 64);
|
|
284
|
+
__m512i a3_vec = _mm512_loadu_epi8(a + 128);
|
|
285
|
+
__m512i b3_vec = _mm512_loadu_epi8(b + 128);
|
|
286
|
+
__m512i a4_vec = _mm512_maskz_loadu_epi8(mask, a + 192);
|
|
287
|
+
__m512i b4_vec = _mm512_maskz_loadu_epi8(mask, b + 192);
|
|
288
|
+
__m512i xor1_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a1_vec, b1_vec));
|
|
289
|
+
__m512i xor2_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a2_vec, b2_vec));
|
|
290
|
+
__m512i xor3_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a3_vec, b3_vec));
|
|
291
|
+
__m512i xor4_count_vec = _mm512_popcnt_epi64(_mm512_xor_si512(a4_vec, b4_vec));
|
|
292
|
+
xor_count = _mm512_reduce_add_epi64(_mm512_add_epi64(_mm512_add_epi64(xor4_count_vec, xor3_count_vec),
|
|
293
|
+
_mm512_add_epi64(xor2_count_vec, xor1_count_vec)));
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
__m512i xor_count_vec = _mm512_setzero_si512();
|
|
297
|
+
__m512i a_vec, b_vec;
|
|
298
|
+
|
|
299
|
+
simsimd_hamming_b8_ice_cycle:
|
|
300
|
+
if (n_words < 64) {
|
|
301
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
|
|
302
|
+
a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
303
|
+
b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
304
|
+
n_words = 0;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
a_vec = _mm512_loadu_epi8(a);
|
|
308
|
+
b_vec = _mm512_loadu_epi8(b);
|
|
309
|
+
a += 64, b += 64, n_words -= 64;
|
|
310
|
+
}
|
|
311
|
+
__m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
|
|
312
|
+
xor_count_vec = _mm512_add_epi64(xor_count_vec, _mm512_popcnt_epi64(xor_vec));
|
|
313
|
+
if (n_words) goto simsimd_hamming_b8_ice_cycle;
|
|
314
|
+
|
|
315
|
+
xor_count = _mm512_reduce_add_epi64(xor_count_vec);
|
|
316
|
+
}
|
|
317
|
+
*result = xor_count;
|
|
189
318
|
}
|
|
190
319
|
|
|
191
|
-
SIMSIMD_PUBLIC void simsimd_jaccard_b8_ice(simsimd_b8_t const*
|
|
192
|
-
simsimd_distance_t*
|
|
193
|
-
__m512i intersection_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
|
|
194
|
-
__m512i a_vec, b_vec;
|
|
320
|
+
SIMSIMD_PUBLIC void simsimd_jaccard_b8_ice(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
321
|
+
simsimd_distance_t *result) {
|
|
195
322
|
|
|
196
|
-
|
|
197
|
-
|
|
323
|
+
simsimd_size_t intersection = 0, union_ = 0;
|
|
324
|
+
//? On such vectors we can clearly see that the CPU struggles to perform this many parallel
|
|
325
|
+
//? population counts, because the throughput of Jaccard and Hamming in this case starts to differ.
|
|
326
|
+
//? One optimization, aside from Harley-Seal transforms can be using "shuffles" for nibble-popcount
|
|
327
|
+
//? lookups, to utilize other ports on the CPU.
|
|
328
|
+
//? https://github.com/ashvardanian/SimSIMD/pull/138
|
|
329
|
+
//
|
|
330
|
+
// - `_mm512_popcnt_epi64` maps to `VPOPCNTQ (ZMM, K, ZMM)`:
|
|
331
|
+
// - On Ice Lake: 3 cycles latency, ports: 1*p5
|
|
332
|
+
// - On Genoa: 2 cycles latency, ports: 1*FP01
|
|
333
|
+
// - `_mm512_shuffle_epi8` maps to `VPSHUFB (ZMM, ZMM, ZMM)`:
|
|
334
|
+
// - On Ice Lake: 1 cycles latency, ports: 1*p5
|
|
335
|
+
// - On Genoa: 2 cycles latency, ports: 1*FP12
|
|
336
|
+
//
|
|
337
|
+
// It's harder to squeeze out performance from tiny representations, so we unroll the loops for binary metrics.
|
|
338
|
+
if (n_words <= 64) { // Up to 512 bits.
|
|
198
339
|
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
|
|
199
|
-
a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
200
|
-
b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
340
|
+
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
341
|
+
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
342
|
+
__m512i and_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a_vec, b_vec));
|
|
343
|
+
__m512i or_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a_vec, b_vec));
|
|
344
|
+
intersection = _mm512_reduce_add_epi64(and_count_vec);
|
|
345
|
+
union_ = _mm512_reduce_add_epi64(or_count_vec);
|
|
346
|
+
}
|
|
347
|
+
else if (n_words <= 128) { // Up to 1024 bits.
|
|
348
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 64);
|
|
349
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
350
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
351
|
+
__m512i a2_vec = _mm512_maskz_loadu_epi8(mask, a + 64);
|
|
352
|
+
__m512i b2_vec = _mm512_maskz_loadu_epi8(mask, b + 64);
|
|
353
|
+
__m512i and1_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a1_vec, b1_vec));
|
|
354
|
+
__m512i or1_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a1_vec, b1_vec));
|
|
355
|
+
__m512i and2_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a2_vec, b2_vec));
|
|
356
|
+
__m512i or2_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a2_vec, b2_vec));
|
|
357
|
+
intersection = _mm512_reduce_add_epi64(_mm512_add_epi64(and2_count_vec, and1_count_vec));
|
|
358
|
+
union_ = _mm512_reduce_add_epi64(_mm512_add_epi64(or2_count_vec, or1_count_vec));
|
|
359
|
+
}
|
|
360
|
+
else if (n_words <= 196) { // Up to 1568 bits.
|
|
361
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 128);
|
|
362
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
363
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
364
|
+
__m512i a2_vec = _mm512_loadu_epi8(a + 64);
|
|
365
|
+
__m512i b2_vec = _mm512_loadu_epi8(b + 64);
|
|
366
|
+
__m512i a3_vec = _mm512_maskz_loadu_epi8(mask, a + 128);
|
|
367
|
+
__m512i b3_vec = _mm512_maskz_loadu_epi8(mask, b + 128);
|
|
368
|
+
__m512i and1_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a1_vec, b1_vec));
|
|
369
|
+
__m512i or1_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a1_vec, b1_vec));
|
|
370
|
+
__m512i and2_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a2_vec, b2_vec));
|
|
371
|
+
__m512i or2_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a2_vec, b2_vec));
|
|
372
|
+
__m512i and3_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a3_vec, b3_vec));
|
|
373
|
+
__m512i or3_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a3_vec, b3_vec));
|
|
374
|
+
intersection = _mm512_reduce_add_epi64( //
|
|
375
|
+
_mm512_add_epi64(and3_count_vec, _mm512_add_epi64(and2_count_vec, and1_count_vec)));
|
|
376
|
+
union_ = _mm512_reduce_add_epi64( //
|
|
377
|
+
_mm512_add_epi64(or3_count_vec, _mm512_add_epi64(or2_count_vec, or1_count_vec)));
|
|
378
|
+
}
|
|
379
|
+
else if (n_words <= 256) { // Up to 2048 bits.
|
|
380
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words - 192);
|
|
381
|
+
__m512i a1_vec = _mm512_loadu_epi8(a);
|
|
382
|
+
__m512i b1_vec = _mm512_loadu_epi8(b);
|
|
383
|
+
__m512i a2_vec = _mm512_loadu_epi8(a + 64);
|
|
384
|
+
__m512i b2_vec = _mm512_loadu_epi8(b + 64);
|
|
385
|
+
__m512i a3_vec = _mm512_loadu_epi8(a + 128);
|
|
386
|
+
__m512i b3_vec = _mm512_loadu_epi8(b + 128);
|
|
387
|
+
__m512i a4_vec = _mm512_maskz_loadu_epi8(mask, a + 192);
|
|
388
|
+
__m512i b4_vec = _mm512_maskz_loadu_epi8(mask, b + 192);
|
|
389
|
+
__m512i and1_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a1_vec, b1_vec));
|
|
390
|
+
__m512i or1_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a1_vec, b1_vec));
|
|
391
|
+
__m512i and2_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a2_vec, b2_vec));
|
|
392
|
+
__m512i or2_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a2_vec, b2_vec));
|
|
393
|
+
__m512i and3_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a3_vec, b3_vec));
|
|
394
|
+
__m512i or3_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a3_vec, b3_vec));
|
|
395
|
+
__m512i and4_count_vec = _mm512_popcnt_epi64(_mm512_and_si512(a4_vec, b4_vec));
|
|
396
|
+
__m512i or4_count_vec = _mm512_popcnt_epi64(_mm512_or_si512(a4_vec, b4_vec));
|
|
397
|
+
intersection = _mm512_reduce_add_epi64(_mm512_add_epi64(_mm512_add_epi64(and4_count_vec, and3_count_vec),
|
|
398
|
+
_mm512_add_epi64(and2_count_vec, and1_count_vec)));
|
|
399
|
+
union_ = _mm512_reduce_add_epi64(_mm512_add_epi64(_mm512_add_epi64(or4_count_vec, or3_count_vec),
|
|
400
|
+
_mm512_add_epi64(or2_count_vec, or1_count_vec)));
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
__m512i and_count_vec = _mm512_setzero_si512(), or_count_vec = _mm512_setzero_si512();
|
|
404
|
+
__m512i a_vec, b_vec;
|
|
405
|
+
|
|
406
|
+
simsimd_jaccard_b8_ice_cycle:
|
|
407
|
+
if (n_words < 64) {
|
|
408
|
+
__mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n_words);
|
|
409
|
+
a_vec = _mm512_maskz_loadu_epi8(mask, a);
|
|
410
|
+
b_vec = _mm512_maskz_loadu_epi8(mask, b);
|
|
411
|
+
n_words = 0;
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
a_vec = _mm512_loadu_epi8(a);
|
|
415
|
+
b_vec = _mm512_loadu_epi8(b);
|
|
416
|
+
a += 64, b += 64, n_words -= 64;
|
|
417
|
+
}
|
|
418
|
+
__m512i and_vec = _mm512_and_si512(a_vec, b_vec);
|
|
419
|
+
__m512i or_vec = _mm512_or_si512(a_vec, b_vec);
|
|
420
|
+
and_count_vec = _mm512_add_epi64(and_count_vec, _mm512_popcnt_epi64(and_vec));
|
|
421
|
+
or_count_vec = _mm512_add_epi64(or_count_vec, _mm512_popcnt_epi64(or_vec));
|
|
422
|
+
if (n_words) goto simsimd_jaccard_b8_ice_cycle;
|
|
423
|
+
|
|
424
|
+
intersection = _mm512_reduce_add_epi64(and_count_vec);
|
|
425
|
+
union_ = _mm512_reduce_add_epi64(or_count_vec);
|
|
206
426
|
}
|
|
207
|
-
__m512i and_vec = _mm512_and_si512(a_vec, b_vec);
|
|
208
|
-
__m512i or_vec = _mm512_or_si512(a_vec, b_vec);
|
|
209
|
-
intersection_vec = _mm512_add_epi64(intersection_vec, _mm512_popcnt_epi64(and_vec));
|
|
210
|
-
union_vec = _mm512_add_epi64(union_vec, _mm512_popcnt_epi64(or_vec));
|
|
211
|
-
if (n_words)
|
|
212
|
-
goto simsimd_jaccard_b8_ice_cycle;
|
|
213
|
-
|
|
214
|
-
simsimd_size_t intersection = _mm512_reduce_add_epi64(intersection_vec),
|
|
215
|
-
union_ = _mm512_reduce_add_epi64(union_vec);
|
|
216
427
|
*result = (union_ != 0) ? 1 - (simsimd_f64_t)intersection / (simsimd_f64_t)union_ : 1;
|
|
217
428
|
}
|
|
218
429
|
|
|
@@ -225,33 +436,31 @@ simsimd_jaccard_b8_ice_cycle:
|
|
|
225
436
|
#pragma GCC target("popcnt")
|
|
226
437
|
#pragma clang attribute push(__attribute__((target("popcnt"))), apply_to = function)
|
|
227
438
|
|
|
228
|
-
SIMSIMD_PUBLIC void simsimd_hamming_b8_haswell(simsimd_b8_t const*
|
|
229
|
-
simsimd_distance_t*
|
|
439
|
+
SIMSIMD_PUBLIC void simsimd_hamming_b8_haswell(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
440
|
+
simsimd_distance_t *result) {
|
|
230
441
|
// x86 supports unaligned loads and works just fine with the scalar version for small vectors.
|
|
231
442
|
simsimd_size_t differences = 0;
|
|
232
443
|
for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
|
|
233
|
-
differences += _mm_popcnt_u64(*(simsimd_u64_t const*)a ^ *(simsimd_u64_t const*)b);
|
|
234
|
-
for (; n_words; --n_words, ++a, ++b)
|
|
235
|
-
differences += _mm_popcnt_u32(*a ^ *b);
|
|
444
|
+
differences += _mm_popcnt_u64(*(simsimd_u64_t const *)a ^ *(simsimd_u64_t const *)b);
|
|
445
|
+
for (; n_words; --n_words, ++a, ++b) differences += _mm_popcnt_u32(*a ^ *b);
|
|
236
446
|
*result = differences;
|
|
237
447
|
}
|
|
238
448
|
|
|
239
|
-
SIMSIMD_PUBLIC void simsimd_jaccard_b8_haswell(simsimd_b8_t const*
|
|
240
|
-
simsimd_distance_t*
|
|
449
|
+
SIMSIMD_PUBLIC void simsimd_jaccard_b8_haswell(simsimd_b8_t const *a, simsimd_b8_t const *b, simsimd_size_t n_words,
|
|
450
|
+
simsimd_distance_t *result) {
|
|
241
451
|
// x86 supports unaligned loads and works just fine with the scalar version for small vectors.
|
|
242
452
|
simsimd_size_t intersection = 0, union_ = 0;
|
|
243
453
|
for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
|
|
244
|
-
intersection += _mm_popcnt_u64(*(simsimd_u64_t const*)a & *(simsimd_u64_t const*)b),
|
|
245
|
-
union_ += _mm_popcnt_u64(*(simsimd_u64_t const*)a | *(simsimd_u64_t const*)b);
|
|
246
|
-
for (; n_words; --n_words, ++a, ++b)
|
|
247
|
-
intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
|
|
454
|
+
intersection += _mm_popcnt_u64(*(simsimd_u64_t const *)a & *(simsimd_u64_t const *)b),
|
|
455
|
+
union_ += _mm_popcnt_u64(*(simsimd_u64_t const *)a | *(simsimd_u64_t const *)b);
|
|
456
|
+
for (; n_words; --n_words, ++a, ++b) intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
|
|
248
457
|
*result = (union_ != 0) ? 1 - (simsimd_f64_t)intersection / (simsimd_f64_t)union_ : 1;
|
|
249
458
|
}
|
|
250
459
|
|
|
251
460
|
#pragma clang attribute pop
|
|
252
461
|
#pragma GCC pop_options
|
|
253
462
|
#endif // SIMSIMD_TARGET_HASWELL
|
|
254
|
-
#endif //
|
|
463
|
+
#endif // _SIMSIMD_TARGET_X86
|
|
255
464
|
|
|
256
465
|
#ifdef __cplusplus
|
|
257
466
|
}
|