usearch 2.7.2 → 2.7.6
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/.github/workflows/Dockerfile.libs +4 -6
- package/.github/workflows/prerelease.yml +4 -1
- package/.github/workflows/release.yml +8 -28
- package/.github/workflows/update_version.sh +1 -0
- package/CITATION.cff +2 -2
- package/CMakeLists.txt +1 -1
- package/Cargo.toml +1 -1
- package/README.md +4 -4
- package/binding.gyp +1 -1
- package/build.gradle +20 -5
- package/conanfile.py +1 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +0 -2
- package/golang/README.md +0 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +119 -243
- package/javascript/lib.cpp +4 -3
- package/package.json +1 -1
- package/simsimd/.github/workflows/prerelease.yml +33 -58
- package/simsimd/.github/workflows/release.yml +22 -9
- package/simsimd/.github/workflows/update_version.sh +3 -1
- package/simsimd/.releaserc +9 -1
- package/simsimd/.vscode/launch.json +3 -0
- package/simsimd/.vscode/settings.json +15 -2
- package/simsimd/.vscode/tasks.json +2 -2
- package/simsimd/CMakeLists.txt +36 -55
- package/simsimd/README.md +163 -109
- package/simsimd/binding.gyp +18 -0
- package/simsimd/cpp/bench.cxx +187 -0
- package/simsimd/go.mod +3 -0
- package/simsimd/golang/bench_test.go +40 -0
- package/simsimd/golang/simsimd.go +47 -0
- package/simsimd/golang/unit_test.go +40 -0
- package/simsimd/include/simsimd/binary.h +186 -0
- package/simsimd/include/simsimd/probability.h +645 -0
- package/simsimd/include/simsimd/simsimd.h +335 -575
- package/simsimd/include/simsimd/spatial.h +924 -0
- package/simsimd/include/simsimd/types.h +196 -0
- package/simsimd/javascript/bench.js +117 -0
- package/simsimd/javascript/lib.c +102 -0
- package/simsimd/javascript/simsimd.js +45 -0
- package/simsimd/javascript/test.js +29 -0
- package/simsimd/package.json +31 -2
- package/simsimd/pyproject.toml +5 -6
- package/simsimd/python/bench.ipynb +187 -0
- package/simsimd/python/bench.py +268 -0
- package/simsimd/python/lib.c +453 -72
- package/simsimd/python/test.py +167 -35
- package/simsimd/setup.py +51 -8
- package/wasmer.toml +1 -1
- package/simsimd/bench.cxx +0 -137
- package/simsimd/include/simsimd/simsimd_bio.h +0 -54
- package/simsimd/include/simsimd/simsimd_chem.h +0 -167
- package/simsimd/include/simsimd/simsimd_text.h +0 -92
- package/simsimd/python/run.sh +0 -1
- package/simsimd/python/test.c +0 -109
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#include <cmath> // `std::sqrt`
|
|
2
|
+
#include <thread> // `std::thread`
|
|
3
|
+
|
|
4
|
+
#include <benchmark/benchmark.h>
|
|
5
|
+
|
|
6
|
+
#define SIMSIMD_RSQRT simsimd_approximate_inverse_square_root
|
|
7
|
+
#define SIMSIMD_LOG simsimd_approximate_log
|
|
8
|
+
#include <simsimd/simsimd.h>
|
|
9
|
+
|
|
10
|
+
namespace bm = benchmark;
|
|
11
|
+
|
|
12
|
+
template <typename return_at, typename... args_at>
|
|
13
|
+
constexpr std::size_t number_of_arguments(return_at (*f)(args_at...)) {
|
|
14
|
+
return sizeof...(args_at);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
template <typename scalar_at, std::size_t dimensions_ak> struct vectors_pair_gt {
|
|
18
|
+
scalar_at a[dimensions_ak]{};
|
|
19
|
+
scalar_at b[dimensions_ak]{};
|
|
20
|
+
|
|
21
|
+
std::size_t dimensions() const noexcept { return dimensions_ak; }
|
|
22
|
+
std::size_t size_bytes() const noexcept { return dimensions_ak * sizeof(scalar_at); }
|
|
23
|
+
|
|
24
|
+
void set(scalar_at v) noexcept {
|
|
25
|
+
for (std::size_t i = 0; i != dimensions_ak; ++i)
|
|
26
|
+
a[i] = b[i] = v;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void randomize() noexcept {
|
|
30
|
+
|
|
31
|
+
double a2_sum = 0, b2_sum = 0;
|
|
32
|
+
for (std::size_t i = 0; i != dimensions_ak; ++i) {
|
|
33
|
+
if constexpr (std::is_integral_v<scalar_at>)
|
|
34
|
+
a[i] = static_cast<scalar_at>(rand()), b[i] = static_cast<scalar_at>(rand());
|
|
35
|
+
else {
|
|
36
|
+
double ai = double(rand()) / double(RAND_MAX), bi = double(rand()) / double(RAND_MAX);
|
|
37
|
+
a2_sum += ai * ai, b2_sum += bi * bi;
|
|
38
|
+
a[i] = static_cast<scalar_at>(ai), b[i] = static_cast<scalar_at>(bi);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Normalize the vectors:
|
|
43
|
+
if constexpr (!std::is_integral_v<scalar_at>) {
|
|
44
|
+
a2_sum = std::sqrt(a2_sum);
|
|
45
|
+
b2_sum = std::sqrt(b2_sum);
|
|
46
|
+
for (std::size_t i = 0; i != dimensions_ak; ++i)
|
|
47
|
+
a[i] /= a2_sum, b[i] /= b2_sum;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
template <typename pair_at, typename metric_at = void>
|
|
53
|
+
static void measure(bm::State& state, metric_at metric, metric_at baseline) {
|
|
54
|
+
|
|
55
|
+
pair_at pair;
|
|
56
|
+
pair.randomize();
|
|
57
|
+
// pair.set(1);
|
|
58
|
+
|
|
59
|
+
double c_baseline = baseline(pair.a, pair.b, pair.dimensions());
|
|
60
|
+
double c = 0;
|
|
61
|
+
std::size_t iterations = 0;
|
|
62
|
+
for (auto _ : state)
|
|
63
|
+
bm::DoNotOptimize((c = metric(pair.a, pair.b, pair.dimensions()))), iterations++;
|
|
64
|
+
|
|
65
|
+
state.counters["bytes"] = bm::Counter(iterations * pair.size_bytes() * 2, bm::Counter::kIsRate);
|
|
66
|
+
state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
|
|
67
|
+
|
|
68
|
+
double delta = std::abs(c - c_baseline) > 0.0001 ? std::abs(c - c_baseline) : 0;
|
|
69
|
+
double error = delta != 0 && c_baseline != 0 ? delta / c_baseline : 0;
|
|
70
|
+
state.counters["abs_delta"] = delta;
|
|
71
|
+
state.counters["relative_error"] = error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
template <typename scalar_at, typename metric_at = void>
|
|
75
|
+
void register_(std::string name, metric_at* distance_func, metric_at* baseline_func) {
|
|
76
|
+
|
|
77
|
+
using pair_dims_t = vectors_pair_gt<scalar_at, 1536>;
|
|
78
|
+
using pair_bytes_t = vectors_pair_gt<scalar_at, 1536 / sizeof(scalar_at)>;
|
|
79
|
+
|
|
80
|
+
std::size_t seconds = 10;
|
|
81
|
+
std::size_t threads = std::thread::hardware_concurrency(); // 1;
|
|
82
|
+
std::string name_dims = name + "_" + std::to_string(pair_dims_t{}.dimensions()) + "d";
|
|
83
|
+
std::string name_bytes = name + "_" + std::to_string(pair_bytes_t{}.size_bytes()) + "b";
|
|
84
|
+
|
|
85
|
+
bm::RegisterBenchmark(name_dims.c_str(), measure<pair_dims_t, metric_at*>, distance_func, baseline_func)
|
|
86
|
+
->MinTime(seconds)
|
|
87
|
+
->Threads(threads);
|
|
88
|
+
bm::RegisterBenchmark(name_bytes.c_str(), measure<pair_bytes_t, metric_at*>, distance_func, baseline_func)
|
|
89
|
+
->MinTime(seconds)
|
|
90
|
+
->Threads(threads);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
int main(int argc, char** argv) {
|
|
94
|
+
|
|
95
|
+
bool compiled_with_sve = false;
|
|
96
|
+
bool compiled_with_neon = false;
|
|
97
|
+
bool compiled_with_avx2 = false;
|
|
98
|
+
bool compiled_with_avx512popcnt = false;
|
|
99
|
+
|
|
100
|
+
#if defined(__ARM_FEATURE_SVE)
|
|
101
|
+
compiled_with_sve = true;
|
|
102
|
+
#endif
|
|
103
|
+
#if defined(__ARM_NEON)
|
|
104
|
+
compiled_with_neon = true;
|
|
105
|
+
#endif
|
|
106
|
+
#if defined(__AVX2__)
|
|
107
|
+
compiled_with_avx2 = true;
|
|
108
|
+
#endif
|
|
109
|
+
#if defined(__AVX512VPOPCNTDQ__)
|
|
110
|
+
compiled_with_avx512popcnt = true;
|
|
111
|
+
#endif
|
|
112
|
+
|
|
113
|
+
// Log supported functionality
|
|
114
|
+
char const* flags[2] = {"false", "true"};
|
|
115
|
+
std::printf("Benchmarking Similarity Measures\n");
|
|
116
|
+
std::printf("\n");
|
|
117
|
+
std::printf("- Arm NEON support enabled: %s\n", flags[compiled_with_neon]);
|
|
118
|
+
std::printf("- Arm SVE support enabled: %s\n", flags[compiled_with_sve]);
|
|
119
|
+
std::printf("- x86 AVX2 support enabled: %s\n", flags[compiled_with_avx2]);
|
|
120
|
+
std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512popcnt]);
|
|
121
|
+
std::printf("\n");
|
|
122
|
+
|
|
123
|
+
// Run the benchmarks
|
|
124
|
+
bm::Initialize(&argc, argv);
|
|
125
|
+
if (bm::ReportUnrecognizedArguments(argc, argv))
|
|
126
|
+
return 1;
|
|
127
|
+
|
|
128
|
+
#if SIMSIMD_TARGET_ARM_NEON
|
|
129
|
+
register_<simsimd_f16_t>("neon_f16_ip", simsimd_neon_f16_ip, simsimd_accurate_f16_ip);
|
|
130
|
+
register_<simsimd_f16_t>("neon_f16_cos", simsimd_neon_f16_cos, simsimd_accurate_f16_cos);
|
|
131
|
+
register_<simsimd_f16_t>("neon_f16_l2sq", simsimd_neon_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
132
|
+
|
|
133
|
+
register_<simsimd_f32_t>("neon_f32_ip", simsimd_neon_f32_ip, simsimd_accurate_f32_ip);
|
|
134
|
+
register_<simsimd_f32_t>("neon_f32_cos", simsimd_neon_f32_cos, simsimd_accurate_f32_cos);
|
|
135
|
+
register_<simsimd_f32_t>("neon_f32_l2sq", simsimd_neon_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
136
|
+
|
|
137
|
+
register_<simsimd_i8_t>("neon_i8_cos", simsimd_neon_i8_cos, simsimd_accurate_i8_cos);
|
|
138
|
+
register_<simsimd_i8_t>("neon_i8_l2sq", simsimd_neon_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
139
|
+
#endif
|
|
140
|
+
|
|
141
|
+
#if SIMSIMD_TARGET_ARM_SVE
|
|
142
|
+
register_<simsimd_f16_t>("sve_f16_ip", simsimd_sve_f16_ip, simsimd_accurate_f16_ip);
|
|
143
|
+
register_<simsimd_f16_t>("sve_f16_cos", simsimd_sve_f16_cos, simsimd_accurate_f16_cos);
|
|
144
|
+
register_<simsimd_f16_t>("sve_f16_l2sq", simsimd_sve_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
145
|
+
|
|
146
|
+
register_<simsimd_f32_t>("sve_f32_ip", simsimd_sve_f32_ip, simsimd_accurate_f32_ip);
|
|
147
|
+
register_<simsimd_f32_t>("sve_f32_cos", simsimd_sve_f32_cos, simsimd_accurate_f32_cos);
|
|
148
|
+
register_<simsimd_f32_t>("sve_f32_l2sq", simsimd_sve_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
149
|
+
#endif
|
|
150
|
+
|
|
151
|
+
#if SIMSIMD_TARGET_X86_AVX2
|
|
152
|
+
register_<simsimd_f16_t>("avx2_f16_ip", simsimd_avx2_f16_ip, simsimd_accurate_f16_ip);
|
|
153
|
+
register_<simsimd_f16_t>("avx2_f16_cos", simsimd_avx2_f16_cos, simsimd_accurate_f16_cos);
|
|
154
|
+
register_<simsimd_f16_t>("avx2_f16_l2sq", simsimd_avx2_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
155
|
+
|
|
156
|
+
register_<simsimd_i8_t>("avx2_i8_cos", simsimd_avx2_i8_cos, simsimd_accurate_i8_cos);
|
|
157
|
+
register_<simsimd_i8_t>("avx2_i8_l2sq", simsimd_avx2_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
158
|
+
#endif
|
|
159
|
+
|
|
160
|
+
#if SIMSIMD_TARGET_X86_AVX512
|
|
161
|
+
register_<simsimd_f16_t>("avx512_f16_ip", simsimd_avx512_f16_ip, simsimd_accurate_f16_ip);
|
|
162
|
+
register_<simsimd_f16_t>("avx512_f16_cos", simsimd_avx512_f16_cos, simsimd_accurate_f16_cos);
|
|
163
|
+
register_<simsimd_f16_t>("avx512_f16_l2sq", simsimd_avx512_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
164
|
+
|
|
165
|
+
register_<simsimd_i8_t>("avx512_i8_cos", simsimd_avx512_i8_cos, simsimd_accurate_i8_cos);
|
|
166
|
+
register_<simsimd_i8_t>("avx512_i8_l2sq", simsimd_avx512_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
167
|
+
|
|
168
|
+
register_<simsimd_f32_t>("avx512_f32_ip", simsimd_avx512_f32_ip, simsimd_accurate_f32_ip);
|
|
169
|
+
register_<simsimd_f32_t>("avx512_f32_cos", simsimd_avx512_f32_cos, simsimd_accurate_f32_cos);
|
|
170
|
+
register_<simsimd_f32_t>("avx512_f32_l2sq", simsimd_avx512_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
171
|
+
#endif
|
|
172
|
+
|
|
173
|
+
register_<simsimd_f16_t>("serial_f16_ip", simsimd_serial_f16_ip, simsimd_accurate_f16_ip);
|
|
174
|
+
register_<simsimd_f16_t>("serial_f16_cos", simsimd_serial_f16_cos, simsimd_accurate_f16_cos);
|
|
175
|
+
register_<simsimd_f16_t>("serial_f16_l2sq", simsimd_serial_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
176
|
+
|
|
177
|
+
register_<simsimd_f32_t>("serial_f32_ip", simsimd_serial_f32_ip, simsimd_accurate_f32_ip);
|
|
178
|
+
register_<simsimd_f32_t>("serial_f32_cos", simsimd_serial_f32_cos, simsimd_accurate_f32_cos);
|
|
179
|
+
register_<simsimd_f32_t>("serial_f32_l2sq", simsimd_serial_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
180
|
+
|
|
181
|
+
register_<simsimd_i8_t>("serial_i8_cos", simsimd_serial_i8_cos, simsimd_accurate_i8_cos);
|
|
182
|
+
register_<simsimd_i8_t>("serial_i8_l2sq", simsimd_serial_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
183
|
+
|
|
184
|
+
bm::RunSpecifiedBenchmarks();
|
|
185
|
+
bm::Shutdown();
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
package/simsimd/go.mod
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package simsimd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"math"
|
|
5
|
+
"math/rand"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func cosineSimilarity(a, b []float32) float32 {
|
|
10
|
+
var dotProduct float32
|
|
11
|
+
var normA, normB float32
|
|
12
|
+
for i := range a {
|
|
13
|
+
dotProduct += a[i] * b[i]
|
|
14
|
+
normA += a[i] * a[i]
|
|
15
|
+
normB += b[i] * b[i]
|
|
16
|
+
}
|
|
17
|
+
return dotProduct / (float32(math.Sqrt(float64(normA))) * float32(math.Sqrt(float64(normB))))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func generateRandomVector(dim int) []float32 {
|
|
21
|
+
vec := make([]float32, dim)
|
|
22
|
+
for i := range vec {
|
|
23
|
+
vec[i] = rand.Float32()
|
|
24
|
+
}
|
|
25
|
+
return vec
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func BenchmarkCosineSimilarityNative(b *testing.B) {
|
|
29
|
+
first, second := generateRandomVector(1536), generateRandomVector(1536)
|
|
30
|
+
for i := 0; i < b.N; i++ {
|
|
31
|
+
cosineSimilarity(first, second)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func BenchmarkCosineSimilaritySIMD(b *testing.B) {
|
|
36
|
+
first, second := generateRandomVector(1536), generateRandomVector(1536)
|
|
37
|
+
for i := 0; i < b.N; i++ {
|
|
38
|
+
CosineF32(first, second)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package simsimd
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
#cgo CFLAGS: -O3
|
|
5
|
+
#cgo LDFLAGS: -O3 -L.
|
|
6
|
+
#include "../simsimd/simsimd.h"
|
|
7
|
+
#include <stdlib.h>
|
|
8
|
+
|
|
9
|
+
inline static simsimd_f32_t cosine_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
|
|
10
|
+
inline static simsimd_f32_t cosine_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_neon_f32_cos(a, b, d); }
|
|
11
|
+
inline static simsimd_f32_t inner_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_inner_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
|
|
12
|
+
inline static simsimd_f32_t inner_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_inner_k, simsimd_datatype_f32_k, simsimd_cap_any_k)(a, b, d, d); }
|
|
13
|
+
inline static simsimd_f32_t sqeuclidean_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_i8_k, simsimd_cap_any_k)(a, b, d, d); }
|
|
14
|
+
inline static simsimd_f32_t sqeuclidean_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) { return simsimd_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_f32_k, simsimd_cap_any_k)(a, b, d, d); }
|
|
15
|
+
*/
|
|
16
|
+
import "C"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// CosineI8 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
20
|
+
func CosineI8(a, b []int8) float32 {
|
|
21
|
+
return float32(C.cosine_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// CosineF32 computes the cosine similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
25
|
+
func CosineF32(a, b []float32) float32 {
|
|
26
|
+
return float32(C.cosine_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// InnerI8 computes the inner-product similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
30
|
+
func InnerI8(a, b []int8) float32 {
|
|
31
|
+
return float32(C.inner_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// InnerF32 computes the inner-product similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
35
|
+
func InnerF32(a, b []float32) float32 {
|
|
36
|
+
return float32(C.inner_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// SqEuclideanI8 computes the squared euclidean similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
40
|
+
func SqEuclideanI8(a, b []int8) float32 {
|
|
41
|
+
return float32(C.sqeuclidean_i8((*C.simsimd_i8_t)(&a[0]), (*C.simsimd_i8_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// SqEuclideanF32 computes the squared euclidean similarity between two i8 vectors using the most suitable SIMD instruction set available.
|
|
45
|
+
func SqEuclideanF32(a, b []float32) float32 {
|
|
46
|
+
return float32(C.sqeuclidean_f32((*C.simsimd_f32_t)(&a[0]), (*C.simsimd_f32_t)(&b[0]), C.simsimd_size_t(len(a))))
|
|
47
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package simsimd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"math"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestCosineI8(t *testing.T) {
|
|
9
|
+
a := []int8{1, 0}
|
|
10
|
+
b := []int8{0, 1}
|
|
11
|
+
|
|
12
|
+
result := CosineI8(a, b)
|
|
13
|
+
expected := float32(1.0) // Cosine similarity of orthogonal vectors is 0
|
|
14
|
+
if math.Abs(float64(result-expected)) > 1e-3 {
|
|
15
|
+
t.Errorf("Expected %v, got %v", expected, result)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func TestCosineF32(t *testing.T) {
|
|
20
|
+
a := []float32{1, 0}
|
|
21
|
+
b := []float32{0, 1}
|
|
22
|
+
|
|
23
|
+
result := CosineF32(a, b)
|
|
24
|
+
expected := float32(1.0) // Cosine similarity of orthogonal vectors is 0
|
|
25
|
+
if math.Abs(float64(result-expected)) > 1e-3 {
|
|
26
|
+
t.Errorf("Expected %v, got %v", expected, result)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func TestVectorLengthMismatch(t *testing.T) {
|
|
31
|
+
defer func() {
|
|
32
|
+
if r := recover(); r == nil {
|
|
33
|
+
t.Errorf("The code did not panic")
|
|
34
|
+
}
|
|
35
|
+
}()
|
|
36
|
+
|
|
37
|
+
a := []int8{1, 0}
|
|
38
|
+
b := []int8{0}
|
|
39
|
+
_ = CosineI8(a, b) // This should panic
|
|
40
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @brief SIMD-accelerated Binary Similarity Measures.
|
|
3
|
+
* @author Ash Vardanian
|
|
4
|
+
* @date July 1, 2023
|
|
5
|
+
*
|
|
6
|
+
* Contains:
|
|
7
|
+
* - Hamming distance
|
|
8
|
+
* - Jaccard similarity (Tanimoto coefficient)
|
|
9
|
+
*
|
|
10
|
+
* For datatypes:
|
|
11
|
+
* - 8-bit bitsets
|
|
12
|
+
*
|
|
13
|
+
* For hardware architectures:
|
|
14
|
+
* - Arm (NEON, SVE)
|
|
15
|
+
* - x86 (AVX512)
|
|
16
|
+
*
|
|
17
|
+
* x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
|
|
18
|
+
* Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
#pragma once
|
|
22
|
+
#include "types.h"
|
|
23
|
+
|
|
24
|
+
#ifdef __cplusplus
|
|
25
|
+
extern "C" {
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
inline static unsigned char simsimd_popcount_b8(simsimd_b8_t x) {
|
|
29
|
+
static unsigned char lookup_table[] = {
|
|
30
|
+
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //
|
|
31
|
+
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
|
32
|
+
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
|
33
|
+
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
|
34
|
+
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
|
35
|
+
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
|
36
|
+
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
|
|
37
|
+
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
|
|
38
|
+
return lookup_table[x];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
inline static simsimd_f32_t simsimd_serial_b8_hamming( //
|
|
42
|
+
simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
43
|
+
simsimd_i32_t differences = 0;
|
|
44
|
+
for (simsimd_size_t i = 0; i != n_words; ++i)
|
|
45
|
+
differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
46
|
+
return (simsimd_f32_t)differences;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
inline static simsimd_f32_t simsimd_serial_b8_jaccard( //
|
|
50
|
+
simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
51
|
+
simsimd_i32_t intersection = 0, union_ = 0;
|
|
52
|
+
for (simsimd_size_t i = 0; i != n_words; ++i)
|
|
53
|
+
intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
|
|
54
|
+
return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#if SIMSIMD_TARGET_ARM
|
|
58
|
+
#if SIMSIMD_TARGET_ARM_NEON
|
|
59
|
+
|
|
60
|
+
__attribute__((target("+simd"))) //
|
|
61
|
+
inline static simsimd_f32_t
|
|
62
|
+
simsimd_neon_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
63
|
+
simsimd_i32_t differences = 0;
|
|
64
|
+
simsimd_size_t i = 0;
|
|
65
|
+
for (; i + 16 <= n_words; i += 16) {
|
|
66
|
+
uint8x16_t a_first = vld1q_u8(a + i);
|
|
67
|
+
uint8x16_t b_first = vld1q_u8(b + i);
|
|
68
|
+
differences += vaddvq_u8(vcntq_u8(veorq_u8(a_first, b_first)));
|
|
69
|
+
}
|
|
70
|
+
// Handle the tail
|
|
71
|
+
for (; i != n_words; ++i)
|
|
72
|
+
differences += simsimd_popcount_b8(a[i] ^ b[i]);
|
|
73
|
+
return (simsimd_f32_t)differences;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
__attribute__((target("+simd"))) //
|
|
77
|
+
inline static simsimd_f32_t
|
|
78
|
+
simsimd_neon_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
79
|
+
simsimd_i32_t intersection = 0, union_ = 0;
|
|
80
|
+
simsimd_size_t i = 0;
|
|
81
|
+
for (; i + 16 <= n_words; i += 16) {
|
|
82
|
+
uint8x16_t a_first = vld1q_u8(a + i);
|
|
83
|
+
uint8x16_t b_first = vld1q_u8(b + i);
|
|
84
|
+
intersection += vaddvq_u8(vcntq_u8(vandq_u8(a_first, b_first)));
|
|
85
|
+
union_ += vaddvq_u8(vcntq_u8(vorrq_u8(a_first, b_first)));
|
|
86
|
+
}
|
|
87
|
+
// Handle the tail
|
|
88
|
+
for (; i != n_words; ++i)
|
|
89
|
+
intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
|
|
90
|
+
return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#endif // SIMSIMD_TARGET_ARM_NEON
|
|
94
|
+
|
|
95
|
+
#if SIMSIMD_TARGET_ARM_SVE
|
|
96
|
+
|
|
97
|
+
__attribute__((target("+sve"))) //
|
|
98
|
+
inline static simsimd_f32_t
|
|
99
|
+
simsimd_sve_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
100
|
+
simsimd_size_t i = 0;
|
|
101
|
+
simsimd_i32_t differences = 0;
|
|
102
|
+
do {
|
|
103
|
+
svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
|
|
104
|
+
svuint8_t a_vec = svld1_u8(pg_vec, a + i);
|
|
105
|
+
svuint8_t b_vec = svld1_u8(pg_vec, b + i);
|
|
106
|
+
differences += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), sveor_u8_m(svptrue_b8(), a_vec, b_vec)));
|
|
107
|
+
i += svcntb();
|
|
108
|
+
} while (i < n_words);
|
|
109
|
+
return (simsimd_f32_t)differences;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
__attribute__((target("+sve"))) //
|
|
113
|
+
inline static simsimd_f32_t
|
|
114
|
+
simsimd_sve_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
115
|
+
simsimd_size_t i = 0;
|
|
116
|
+
simsimd_i32_t intersection = 0, union_ = 0;
|
|
117
|
+
do {
|
|
118
|
+
svbool_t pg_vec = svwhilelt_b8((unsigned int)i, (unsigned int)n_words);
|
|
119
|
+
svuint8_t a_vec = svld1_u8(pg_vec, a + i);
|
|
120
|
+
svuint8_t b_vec = svld1_u8(pg_vec, b + i);
|
|
121
|
+
intersection += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), svand_u8_m(svptrue_b8(), a_vec, b_vec)));
|
|
122
|
+
union_ += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), svorr_u8_m(svptrue_b8(), a_vec, b_vec)));
|
|
123
|
+
i += svcntb();
|
|
124
|
+
} while (i < n_words);
|
|
125
|
+
return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#endif // SIMSIMD_TARGET_ARM_SVE
|
|
129
|
+
|
|
130
|
+
#endif // SIMSIMD_TARGET_ARM
|
|
131
|
+
|
|
132
|
+
#if SIMSIMD_TARGET_X86
|
|
133
|
+
|
|
134
|
+
#if SIMSIMD_TARGET_X86_AVX512
|
|
135
|
+
|
|
136
|
+
__attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
|
|
137
|
+
inline static simsimd_f32_t
|
|
138
|
+
simsimd_avx512_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
139
|
+
__m512i differences_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
|
|
140
|
+
for (simsimd_size_t i = 0; i < n_words; i += 64) {
|
|
141
|
+
|
|
142
|
+
// Compute mask for tail elements
|
|
143
|
+
__mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
|
|
144
|
+
|
|
145
|
+
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
|
|
146
|
+
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
|
|
147
|
+
__m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
|
|
148
|
+
|
|
149
|
+
differences_vec = _mm512_add_epi64(differences_vec, _mm512_popcnt_epi64(xor_vec));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
simsimd_size_t differences = _mm512_reduce_add_epi64(differences_vec);
|
|
153
|
+
return (simsimd_f32_t)differences;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
__attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
|
|
157
|
+
inline static simsimd_f32_t
|
|
158
|
+
simsimd_avx512_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
159
|
+
__m512i intersection_vec = _mm512_setzero_si512(), union_vec = _mm512_setzero_si512();
|
|
160
|
+
for (simsimd_size_t i = 0; i < n_words; i += 64) {
|
|
161
|
+
|
|
162
|
+
// Compute mask for tail elements
|
|
163
|
+
__mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
|
|
164
|
+
|
|
165
|
+
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
|
|
166
|
+
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
|
|
167
|
+
__m512i and_vec = _mm512_and_si512(a_vec, b_vec);
|
|
168
|
+
__m512i or_vec = _mm512_or_si512(a_vec, b_vec);
|
|
169
|
+
|
|
170
|
+
intersection_vec = _mm512_add_epi64(intersection_vec, _mm512_popcnt_epi64(and_vec));
|
|
171
|
+
union_vec = _mm512_add_epi64(union_vec, _mm512_popcnt_epi64(or_vec));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
simsimd_size_t intersection = _mm512_reduce_add_epi64(intersection_vec),
|
|
175
|
+
union_ = _mm512_reduce_add_epi64(union_vec);
|
|
176
|
+
return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#endif // SIMSIMD_TARGET_X86_AVX512
|
|
180
|
+
#endif // SIMSIMD_TARGET_X86
|
|
181
|
+
|
|
182
|
+
#undef popcount64
|
|
183
|
+
|
|
184
|
+
#ifdef __cplusplus
|
|
185
|
+
}
|
|
186
|
+
#endif
|