usearch 2.9.2 → 2.10.1
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 +39 -38
- package/javascript/{types → dist/cjs}/usearch.d.ts +90 -115
- package/javascript/dist/cjs/usearch.js +531 -0
- package/javascript/dist/esm/usearch.d.ts +212 -0
- package/javascript/{usearch.js → dist/esm/usearch.js} +199 -153
- package/package.json +25 -23
- 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/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
- package/.clang-format +0 -10
- package/.clang-tidy +0 -60
- package/.cmake-format.py +0 -19
- package/.dockerignore +0 -93
- package/.gitattributes +0 -9
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
- package/.github/ISSUE_TEMPLATE/config.yml +0 -5
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -64
- package/.github/workflows/Dockerfile.libs +0 -36
- package/.github/workflows/prerelease.yml +0 -331
- package/.github/workflows/release.yml +0 -722
- package/.github/workflows/update_version.sh +0 -15
- package/.gitmodules +0 -9
- package/.releaserc +0 -90
- package/CITATION.cff +0 -11
- package/CMakeLists.txt +0 -324
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -361
- package/Cargo.toml +0 -19
- package/Dockerfile +0 -16
- package/VERSION +0 -1
- package/assets/unum.png +0 -0
- package/assets/usearch-approaches-transparent.png +0 -0
- package/assets/usearch-approaches-white.png +0 -0
- package/assets/usearch-neighbor-types.png +0 -0
- package/binding.gyp +0 -87
- package/build.gradle +0 -204
- package/build.rs +0 -18
- package/c/CMakeLists.txt +0 -25
- package/c/README.md +0 -34
- package/c/lib.cpp +0 -358
- package/c/test.c +0 -382
- package/c/usearch.h +0 -311
- package/cmake/clipp.cmake +0 -7
- package/cmake/config.cmake.in +0 -15
- package/cmake/emscripten.cmake +0 -8
- package/cmake/jemalloc.cmake +0 -62
- package/cmake/pkg-config.pc.in +0 -4
- package/cmake/usearchConfigVersion.cmake.in +0 -20
- package/conanfile.py +0 -26
- package/cpp/CMakeLists.txt +0 -14
- package/cpp/README.md +0 -161
- package/cpp/bench.cpp +0 -618
- package/cpp/test.cpp +0 -323
- package/fp16/CMakeLists.txt +0 -176
- package/fp16/LICENSE +0 -11
- package/fp16/README.md +0 -20
- package/fp16/bench/alt-element.cc +0 -89
- package/fp16/bench/from-alt-array.cc +0 -175
- package/fp16/bench/from-ieee-array.cc +0 -399
- package/fp16/bench/ieee-element.cc +0 -272
- package/fp16/bench/to-alt-array.cc +0 -91
- package/fp16/bench/to-ieee-array.cc +0 -303
- package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
- package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
- package/fp16/cmake/DownloadPSimd.cmake +0 -15
- package/fp16/configure.py +0 -65
- package/fp16/confu.yaml +0 -8
- package/fp16/include/fp16/__init__.py +0 -0
- package/fp16/include/fp16/avx.py +0 -50
- package/fp16/include/fp16/avx2.py +0 -53
- package/fp16/include/fp16/bitcasts.h +0 -92
- package/fp16/include/fp16/fp16.h +0 -451
- package/fp16/include/fp16/psimd.h +0 -131
- package/fp16/include/fp16.h +0 -11
- package/fp16/jni/Android.mk +0 -6
- package/fp16/jni/Application.mk +0 -4
- package/fp16/test/alt-from-fp32-value.cc +0 -498
- package/fp16/test/alt-to-fp32-bits.cc +0 -263
- package/fp16/test/alt-to-fp32-psimd.cc +0 -145
- package/fp16/test/alt-to-fp32-value.cc +0 -348
- package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
- package/fp16/test/bitcasts.cc +0 -56
- package/fp16/test/ieee-from-fp32-value.cc +0 -523
- package/fp16/test/ieee-to-fp32-bits.cc +0 -333
- package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
- package/fp16/test/ieee-to-fp32-value.cc +0 -420
- package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
- package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
- package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
- package/fp16/test/peachpy/stubs.py +0 -38
- package/fp16/test/tables.cc +0 -530
- package/fp16/test/tables.h +0 -13
- package/fp16/third-party/THHalf.h +0 -92
- package/fp16/third-party/eigen-half.h +0 -170
- package/fp16/third-party/float16-compressor.h +0 -80
- package/fp16/third-party/half.hpp +0 -2916
- package/fp16/third-party/npy-halffloat.h +0 -328
- package/include/usearch/index.hpp +0 -3836
- package/include/usearch/index_dense.hpp +0 -2004
- package/include/usearch/index_plugins.hpp +0 -2213
- package/javascript/lib.cpp +0 -341
- package/javascript/types/usearch.d.ts.map +0 -1
- package/javascript/usearch.test.js +0 -74
- package/package-ci.json +0 -10
- package/pyproject.toml +0 -51
- package/setup.py +0 -201
- package/simsimd/.releaserc +0 -86
- package/simsimd/CONTRIBUTING.md +0 -88
- package/simsimd/Cargo.lock +0 -709
- package/simsimd/Cargo.toml +0 -44
- package/simsimd/LICENSE +0 -201
- package/simsimd/README.md +0 -317
- package/simsimd/binding.gyp +0 -36
- package/simsimd/build.rs +0 -20
- package/simsimd/include/simsimd/binary.h +0 -245
- package/simsimd/include/simsimd/complex.h +0 -174
- package/simsimd/include/simsimd/probability.h +0 -548
- package/simsimd/include/simsimd/simsimd.h +0 -465
- package/simsimd/include/simsimd/spatial.h +0 -1267
- package/simsimd/include/simsimd/types.h +0 -208
- package/simsimd/javascript/bench.js +0 -127
- package/simsimd/javascript/fallback.ts +0 -193
- package/simsimd/javascript/lib.c +0 -102
- package/simsimd/javascript/node-gyp-build.d.ts +0 -1
- package/simsimd/javascript/simsimd.ts +0 -124
- package/simsimd/javascript/test.mjs +0 -153
- package/simsimd/javascript/tsconfig-base.json +0 -25
- package/simsimd/javascript/tsconfig-cjs.json +0 -8
- package/simsimd/javascript/tsconfig-esm.json +0 -8
- package/simsimd/package-lock.json +0 -1688
- package/simsimd/package.json +0 -61
- package/simsimd/rust/benches/cosine.rs +0 -27
- package/simsimd/rust/benches/native.rs +0 -30
- package/simsimd/rust/benches/sqeuclidean.rs +0 -27
- package/simsimd/rust/lib.c +0 -45
- package/simsimd/rust/lib.rs +0 -550
- package/sqlite/README.md +0 -178
- package/sqlite/lib.cpp +0 -325
- package/stringzilla/CONTRIBUTING.md +0 -446
- package/stringzilla/Cargo.lock +0 -25
- package/stringzilla/Cargo.toml +0 -26
- package/stringzilla/LICENSE +0 -201
- package/stringzilla/README.md +0 -1422
- package/stringzilla/VERSION +0 -1
- package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
- package/stringzilla/binding.gyp +0 -16
- package/stringzilla/build.rs +0 -87
- package/stringzilla/c/lib.c +0 -337
- package/stringzilla/cli/README.md +0 -65
- package/stringzilla/cli/__init__.py +0 -0
- package/stringzilla/cli/split.py +0 -105
- package/stringzilla/cli/wc.py +0 -117
- package/stringzilla/include/stringzilla/experimental.h +0 -845
- package/stringzilla/include/stringzilla/module.modulemap +0 -9
- package/stringzilla/include/stringzilla/stringzilla.h +0 -5370
- package/stringzilla/include/stringzilla/stringzilla.hpp +0 -3838
- package/stringzilla/javascript/lib.c +0 -125
- package/stringzilla/javascript/stringzilla.js +0 -22
- package/stringzilla/package-ci.json +0 -10
- package/stringzilla/package.json +0 -32
- package/stringzilla/rust/lib.rs +0 -873
- package/stringzilla/swift/StringProtocol+StringZilla.swift +0 -280
- package/stringzilla/swift/Test.swift +0 -60
- package/tsconfig.json +0 -21
- package/wasmer.toml +0 -12
- package/winlibinstaller.bat +0 -42
- /package/{simsimd/javascript/dist-package-cjs.json → javascript/dist/cjs/package.json} +0 -0
- /package/{simsimd/javascript/dist-package-esm.json → javascript/dist/esm/package.json} +0 -0
|
@@ -1,845 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @brief Experimental kernels for StringZilla.
|
|
3
|
-
* @file experimental.h
|
|
4
|
-
* @author Ash Vardanian
|
|
5
|
-
*/
|
|
6
|
-
#ifndef STRINGZILLA_EXPERIMENTAL_H_
|
|
7
|
-
#define STRINGZILLA_EXPERIMENTAL_H_
|
|
8
|
-
|
|
9
|
-
#include "stringzilla.h"
|
|
10
|
-
|
|
11
|
-
#ifdef __cplusplus
|
|
12
|
-
extern "C" {
|
|
13
|
-
#endif
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @brief Bitap algo for exact matching of patterns up to @b 8-bytes long.
|
|
17
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
18
|
-
*/
|
|
19
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
20
|
-
sz_size_t n_length) {
|
|
21
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
22
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
23
|
-
sz_u8_t const *h_end = h_unsigned + h_length;
|
|
24
|
-
|
|
25
|
-
// Here is our baseline:
|
|
26
|
-
//
|
|
27
|
-
// sz_u8_t running_match = 0xFF;
|
|
28
|
-
// sz_u8_t character_position_masks[256];
|
|
29
|
-
// for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
|
|
30
|
-
// for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
|
|
31
|
-
// for (sz_size_t i = 0; i < h_length; ++i) {
|
|
32
|
-
// running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
|
|
33
|
-
// if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
|
|
34
|
-
// }
|
|
35
|
-
//
|
|
36
|
-
// On very short patterns, however, every tiny condition may have a huge affect on performance.
|
|
37
|
-
// 1. Let's replace byte-level intialization of `character_position_masks` with 64-bit ops.
|
|
38
|
-
// 2. Let's combine the first `n_length - 1` passes of the last loop into the previous loop.
|
|
39
|
-
typedef sz_u8_t offset_mask_t;
|
|
40
|
-
|
|
41
|
-
// Initialize the possible offset masks.
|
|
42
|
-
// Even using 8-byte `wide_masks` words, this would require 64 iterations to populate 256 bytes.
|
|
43
|
-
union {
|
|
44
|
-
offset_mask_t masks[256];
|
|
45
|
-
sz_u64_t wide_masks[sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t)];
|
|
46
|
-
} character_positions;
|
|
47
|
-
for (sz_size_t i = 0; i != sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t); ++i) {
|
|
48
|
-
character_positions.wide_masks[i] = 0xFFFFFFFFFFFFFFFFull;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Populate the mask with possible positions for each character.
|
|
52
|
-
for (sz_size_t i = 0; i != n_length; ++i) { character_positions.masks[n_unsigned[i]] &= ~((offset_mask_t)1 << i); }
|
|
53
|
-
|
|
54
|
-
// The "running match" for the serial algorithm should be at least as wide as the `offset_mask_t`.
|
|
55
|
-
// But on modern systems larger integers may work better.
|
|
56
|
-
offset_mask_t running_match = 0, final_match = 1;
|
|
57
|
-
running_match = ~(running_match ^ running_match); //< Initialize with all-ones
|
|
58
|
-
final_match <<= n_length - 1;
|
|
59
|
-
|
|
60
|
-
for (; (h_unsigned != h_end) + ((running_match & final_match) != 0) == 2; ++h_unsigned) {
|
|
61
|
-
running_match = (running_match << 1) | character_positions.masks[h_unsigned[0]];
|
|
62
|
-
}
|
|
63
|
-
return ((running_match & final_match) == 0) ? (sz_cptr_t)(h_unsigned - n_length) : NULL;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @brief Bitap algorithm for exact matching of patterns up to @b 8-bytes long in @b reverse order.
|
|
68
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
69
|
-
*/
|
|
70
|
-
SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
71
|
-
sz_size_t n_length) {
|
|
72
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
73
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
74
|
-
sz_u8_t running_match = 0xFF;
|
|
75
|
-
sz_u8_t character_position_masks[256];
|
|
76
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
|
|
77
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
|
|
78
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
79
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
|
|
80
|
-
if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return NULL;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* @brief Bitap algo for exact matching of patterns up to @b 16-bytes long.
|
|
88
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
89
|
-
*/
|
|
90
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
91
|
-
sz_size_t n_length) {
|
|
92
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
93
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
94
|
-
sz_u16_t running_match = 0xFFFF;
|
|
95
|
-
sz_u16_t character_position_masks[256];
|
|
96
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
|
|
97
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
|
|
98
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
99
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
|
|
100
|
-
if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return NULL;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* @brief Bitap algorithm for exact matching of patterns up to @b 16-bytes long in @b reverse order.
|
|
108
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
109
|
-
*/
|
|
110
|
-
SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
111
|
-
sz_size_t n_length) {
|
|
112
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
113
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
114
|
-
sz_u16_t running_match = 0xFFFF;
|
|
115
|
-
sz_u16_t character_position_masks[256];
|
|
116
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
|
|
117
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
|
|
118
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
119
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
|
|
120
|
-
if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return NULL;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @brief Bitap algo for exact matching of patterns up to @b 32-bytes long.
|
|
128
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
129
|
-
*/
|
|
130
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
131
|
-
sz_size_t n_length) {
|
|
132
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
133
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
134
|
-
sz_u32_t running_match = 0xFFFFFFFF;
|
|
135
|
-
sz_u32_t character_position_masks[256];
|
|
136
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
|
|
137
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
|
|
138
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
139
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
|
|
140
|
-
if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return NULL;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @brief Bitap algorithm for exact matching of patterns up to @b 32-bytes long in @b reverse order.
|
|
148
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
149
|
-
*/
|
|
150
|
-
SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
151
|
-
sz_size_t n_length) {
|
|
152
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
153
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
154
|
-
sz_u32_t running_match = 0xFFFFFFFF;
|
|
155
|
-
sz_u32_t character_position_masks[256];
|
|
156
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
|
|
157
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
|
|
158
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
159
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
|
|
160
|
-
if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return NULL;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* @brief Bitap algo for exact matching of patterns up to @b 64-bytes long.
|
|
168
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
169
|
-
*/
|
|
170
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
171
|
-
sz_size_t n_length) {
|
|
172
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
173
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
174
|
-
sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
|
|
175
|
-
sz_u64_t character_position_masks[256];
|
|
176
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
|
|
177
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
|
|
178
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
179
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
|
|
180
|
-
if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return NULL;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* @brief Bitap algorithm for exact matching of patterns up to @b 64-bytes long in @b reverse order.
|
|
188
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
189
|
-
*/
|
|
190
|
-
SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
191
|
-
sz_size_t n_length) {
|
|
192
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
193
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
194
|
-
sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
|
|
195
|
-
sz_u64_t character_position_masks[256];
|
|
196
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
|
|
197
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
|
|
198
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
199
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
|
|
200
|
-
if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return NULL;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* @brief Bitap algo for approximate matching of patterns up to @b 64-bytes long.
|
|
208
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
209
|
-
*/
|
|
210
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bounded_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
211
|
-
sz_size_t n_length) {
|
|
212
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
213
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
214
|
-
sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
|
|
215
|
-
sz_u64_t character_position_masks[256];
|
|
216
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
|
|
217
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
|
|
218
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
219
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
|
|
220
|
-
if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return NULL;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* @brief Bitap algorithm for approximate matching of patterns up to @b 64-bytes long in @b reverse order.
|
|
228
|
-
* https://en.wikipedia.org/wiki/Bitap_algorithm
|
|
229
|
-
*/
|
|
230
|
-
SZ_INTERNAL sz_cptr_t _sz_find_bounded_last_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
|
|
231
|
-
sz_size_t n_length) {
|
|
232
|
-
sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
|
|
233
|
-
sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
|
|
234
|
-
sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
|
|
235
|
-
sz_u64_t character_position_masks[256];
|
|
236
|
-
for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
|
|
237
|
-
for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
|
|
238
|
-
for (sz_size_t i = 0; i < h_length; ++i) {
|
|
239
|
-
running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
|
|
240
|
-
if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return NULL;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
#if SZ_USE_AVX512
|
|
247
|
-
|
|
248
|
-
SZ_PUBLIC sz_size_t sz_edit_distance_avx512( //
|
|
249
|
-
sz_cptr_t const a, sz_size_t const a_length, //
|
|
250
|
-
sz_cptr_t const b, sz_size_t const b_length, //
|
|
251
|
-
sz_size_t const bound, sz_memory_allocator_t *alloc) {
|
|
252
|
-
|
|
253
|
-
sz_u512_vec_t a_vec, b_vec, previous_vec, current_vec, permutation_vec;
|
|
254
|
-
sz_u512_vec_t cost_deletion_vec, cost_insertion_vec, cost_substitution_vec;
|
|
255
|
-
sz_size_t min_distance;
|
|
256
|
-
|
|
257
|
-
b_vec.zmm = _mm512_maskz_loadu_epi8(_sz_u64_mask_until(b_length), b);
|
|
258
|
-
previous_vec.zmm = _mm512_set_epi8(63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, //
|
|
259
|
-
47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, //
|
|
260
|
-
31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, //
|
|
261
|
-
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
|
|
262
|
-
|
|
263
|
-
// Shifting bytes across the whole ZMM register is quite complicated, so let's use a permutation for that.
|
|
264
|
-
permutation_vec.zmm = _mm512_set_epi8(62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, //
|
|
265
|
-
46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, //
|
|
266
|
-
30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, //
|
|
267
|
-
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 63);
|
|
268
|
-
|
|
269
|
-
for (sz_size_t idx_a = 0; idx_a != a_length; ++idx_a) {
|
|
270
|
-
min_distance = bound - 1;
|
|
271
|
-
|
|
272
|
-
a_vec.zmm = _mm512_set1_epi8(a[idx_a]);
|
|
273
|
-
// We first start by computing the cost of deletions and substitutions
|
|
274
|
-
// for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
|
|
275
|
-
// sz_u8_t cost_deletion = previous_vec.u8s[idx_b + 1] + 1;
|
|
276
|
-
// sz_u8_t cost_substitution = previous_vec.u8s[idx_b] + (a[idx_a] != b[idx_b]);
|
|
277
|
-
// current_vec.u8s[idx_b + 1] = sz_min_of_two(cost_deletion, cost_substitution);
|
|
278
|
-
// }
|
|
279
|
-
cost_deletion_vec.zmm = _mm512_add_epi8(previous_vec.zmm, _mm512_set1_epi8(1));
|
|
280
|
-
cost_substitution_vec.zmm =
|
|
281
|
-
_mm512_mask_set1_epi8(_mm512_setzero_si512(), _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm), 0x01);
|
|
282
|
-
cost_substitution_vec.zmm = _mm512_add_epi8(previous_vec.zmm, cost_substitution_vec.zmm);
|
|
283
|
-
cost_substitution_vec.zmm = _mm512_permutexvar_epi8(permutation_vec.zmm, cost_substitution_vec.zmm);
|
|
284
|
-
current_vec.zmm = _mm512_min_epu8(cost_deletion_vec.zmm, cost_substitution_vec.zmm);
|
|
285
|
-
current_vec.u8s[0] = idx_a + 1;
|
|
286
|
-
|
|
287
|
-
// Now we need to compute the inclusive prefix sums using the minimum operator
|
|
288
|
-
// In one line:
|
|
289
|
-
// current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], current_vec.u8s[idx_b] + 1)
|
|
290
|
-
//
|
|
291
|
-
// Unrolling this:
|
|
292
|
-
// current_vec.u8s[0 + 1] = sz_min_of_two(current_vec.u8s[0 + 1], current_vec.u8s[0] + 1)
|
|
293
|
-
// current_vec.u8s[1 + 1] = sz_min_of_two(current_vec.u8s[1 + 1], current_vec.u8s[1] + 1)
|
|
294
|
-
// current_vec.u8s[2 + 1] = sz_min_of_two(current_vec.u8s[2 + 1], current_vec.u8s[2] + 1)
|
|
295
|
-
// current_vec.u8s[3 + 1] = sz_min_of_two(current_vec.u8s[3 + 1], current_vec.u8s[3] + 1)
|
|
296
|
-
//
|
|
297
|
-
// Alternatively, using a tree-like reduction in log2 steps:
|
|
298
|
-
// - 6 cycles of reductions shifting by 1, 2, 4, 8, 16, 32, 64 bytes;
|
|
299
|
-
// - with each cycle containing at least one shift, min, add, blend.
|
|
300
|
-
//
|
|
301
|
-
// Which adds meaningless complexity without any performance gains.
|
|
302
|
-
for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
|
|
303
|
-
sz_u8_t cost_insertion = current_vec.u8s[idx_b] + 1;
|
|
304
|
-
current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], cost_insertion);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Swap previous_distances and current_distances pointers
|
|
308
|
-
sz_u512_vec_t temp_vec;
|
|
309
|
-
temp_vec.zmm = previous_vec.zmm;
|
|
310
|
-
previous_vec.zmm = current_vec.zmm;
|
|
311
|
-
current_vec.zmm = temp_vec.zmm;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return previous_vec.u8s[b_length] < bound ? previous_vec.u8s[b_length] : bound;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
sz_u512_vec_t sz_inclusive_min(sz_i32_t previous, sz_error_cost_t gap, sz_u512_vec_t base_vec) {
|
|
318
|
-
|
|
319
|
-
sz_u512_vec_t gap_vec, gap_double_vec, gap_quad_vec, gap_octa_vec;
|
|
320
|
-
gap_vec.zmm = _mm512_set1_epi32(gap);
|
|
321
|
-
gap_double_vec.zmm = _mm512_set1_epi32(2 * gap);
|
|
322
|
-
gap_quad_vec.zmm = _mm512_set1_epi32(4 * gap);
|
|
323
|
-
gap_octa_vec.zmm = _mm512_set1_epi32(8 * gap);
|
|
324
|
-
|
|
325
|
-
// __mmask16 mask_skip_one = 0xFFFF - 1;
|
|
326
|
-
// __mmask16 mask_skip_two = 0xFFFF - 3;
|
|
327
|
-
// __mmask16 mask_skip_four = 0xFFF0;
|
|
328
|
-
// __mmask16 mask_skip_eight = 0xFF00;
|
|
329
|
-
__mmask16 mask_skip_one = 0x7FFF;
|
|
330
|
-
__mmask16 mask_skip_two = 0x3FFF;
|
|
331
|
-
__mmask16 mask_skip_four = 0x0FFF;
|
|
332
|
-
__mmask16 mask_skip_eight = 0x00FF;
|
|
333
|
-
sz_u512_vec_t shift_by_one_vec, shift_by_two_vec, shift_by_four_vec, shift_by_eight_vec;
|
|
334
|
-
shift_by_one_vec.zmm = _mm512_set_epi32(14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0);
|
|
335
|
-
shift_by_two_vec.zmm = _mm512_set_epi32(13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0);
|
|
336
|
-
shift_by_four_vec.zmm = _mm512_set_epi32(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0);
|
|
337
|
-
shift_by_eight_vec.zmm = _mm512_set_epi32(7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
338
|
-
|
|
339
|
-
sz_u512_vec_t shifted_vec;
|
|
340
|
-
sz_u512_vec_t new_vec = base_vec;
|
|
341
|
-
shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_one_vec.zmm, new_vec.zmm);
|
|
342
|
-
shifted_vec.i32s[0] = previous;
|
|
343
|
-
shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_vec.zmm);
|
|
344
|
-
new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_one, new_vec.zmm, shifted_vec.zmm);
|
|
345
|
-
sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
|
|
346
|
-
|
|
347
|
-
shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_two_vec.zmm, new_vec.zmm);
|
|
348
|
-
shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_double_vec.zmm);
|
|
349
|
-
new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_two, new_vec.zmm, shifted_vec.zmm);
|
|
350
|
-
sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
|
|
351
|
-
|
|
352
|
-
shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_four_vec.zmm, new_vec.zmm);
|
|
353
|
-
shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_quad_vec.zmm);
|
|
354
|
-
new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_four, new_vec.zmm, shifted_vec.zmm);
|
|
355
|
-
sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
|
|
356
|
-
|
|
357
|
-
shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_eight_vec.zmm, new_vec.zmm);
|
|
358
|
-
shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_octa_vec.zmm);
|
|
359
|
-
new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_eight, new_vec.zmm, shifted_vec.zmm);
|
|
360
|
-
|
|
361
|
-
sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
|
|
362
|
-
for (sz_size_t i = 1; i < 16; i++) sz_assert(new_vec.i32s[i] == max(new_vec.i32s[i - 1] + gap, new_vec.i32s[i]));
|
|
363
|
-
|
|
364
|
-
return new_vec;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
#endif // SZ_USE_AVX512
|
|
368
|
-
|
|
369
|
-
#if SZ_USE_ARM_NEON
|
|
370
|
-
|
|
371
|
-
SZ_PUBLIC sz_cptr_t sz_find_neon_too_smart(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
|
|
372
|
-
|
|
373
|
-
// This almost never fires, but it's better to be safe than sorry.
|
|
374
|
-
if (h_length < n_length || !n_length) return SZ_NULL_CHAR;
|
|
375
|
-
if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
|
|
376
|
-
|
|
377
|
-
// Scan through the string.
|
|
378
|
-
// Assuming how tiny the Arm NEON registers are, we should avoid internal branches at all costs.
|
|
379
|
-
// That's why, for smaller needles, we use different loops.
|
|
380
|
-
if (n_length == 2) {
|
|
381
|
-
// This is a common case. Aside from ASCII bigrams, it's also the most common case for UTF-16,
|
|
382
|
-
// or any UTF8 content in Cyrillic, Greek, Armenian, Hebrew, Arabic, Coptic, Syriac, Thaana,
|
|
383
|
-
// N'Ko writing system of West-African nations, and, of course, Latin scripts.
|
|
384
|
-
// Dealing with 16-bit values, we can check 15 possible offsets in a single loop iteration.
|
|
385
|
-
// For that we are going to keep 2 registers populated with haystack data.
|
|
386
|
-
// First - bigrams at even offsets - 0, 2, 4, 6, 8, 10, 12, 14.
|
|
387
|
-
// Second - bigrams at odd offsets - 1, 3, 5, 7, 9, 11, 13. One less than the first one.
|
|
388
|
-
sz_u64_t matches;
|
|
389
|
-
sz_u128_vec_t h_even_vec, h_odd_vec, n_vec, interleave_mask_vec, matches_vec;
|
|
390
|
-
// Broadcast needle characters into SIMD registers.
|
|
391
|
-
n_vec.u16x8 = vdupq_n_u16(sz_u16_load(n).u16);
|
|
392
|
-
interleave_mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
|
|
393
|
-
for (; h_length >= 16; h += 15, h_length -= 15) {
|
|
394
|
-
h_even_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
|
|
395
|
-
h_odd_vec.u8x16 = vextq_u8(h_even_vec.u8x16, /* can be any noise: */ h_even_vec.u8x16, 1);
|
|
396
|
-
// We can now compare both 16-bit arrays with the needle.
|
|
397
|
-
// The result of each comparison will also be 16 bits long.
|
|
398
|
-
// Then - we blend!
|
|
399
|
-
// For odd offsets we are gonna take the bottom 8 bits, and for even - the top ones!
|
|
400
|
-
matches_vec.u8x16 =
|
|
401
|
-
vbslq_u8(interleave_mask_vec.u8x16, vreinterpretq_u8_u16(vceqq_u16(h_even_vec.u16x8, n_vec.u16x8)),
|
|
402
|
-
vreinterpretq_u8_u16(vceqq_u16(h_odd_vec.u16x8, n_vec.u16x8)));
|
|
403
|
-
matches = vreinterpretq_u8_u4(matches_vec.u8x16);
|
|
404
|
-
if (matches) return h + sz_u64_ctz(matches) / 4;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
else if (n_length == 3) {
|
|
408
|
-
// Comparing 24-bit values is a bumer. Being lazy, I went with a simple design.
|
|
409
|
-
// Instead of keeping one register per haystack offset, I keep a register per needle character.
|
|
410
|
-
sz_u64_t matches;
|
|
411
|
-
sz_u128_vec_t h_vec, n_first_vec, n_second_vec, n_third_vec, matches_vec;
|
|
412
|
-
// Broadcast needle characters into SIMD registers.
|
|
413
|
-
n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
|
|
414
|
-
n_second_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
|
|
415
|
-
n_third_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[2]);
|
|
416
|
-
for (; h_length >= 16; h += 14, h_length -= 14) {
|
|
417
|
-
h_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
|
|
418
|
-
// Let's compare the first character.
|
|
419
|
-
matches_vec.u8x16 = vceqq_u8(h_vec.u8x16, n_first_vec.u8x16);
|
|
420
|
-
// Let's compare the second one, shift the equality indicators left by 8 bits, and blend.
|
|
421
|
-
matches_vec.u8x16 =
|
|
422
|
-
vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_second_vec.u8x16), vdupq_n_u8(0), 1));
|
|
423
|
-
// Let's compare the third one, shift the equality indicators left by 16 bits, and blend.
|
|
424
|
-
matches_vec.u8x16 =
|
|
425
|
-
vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_third_vec.u8x16), vdupq_n_u8(0), 2));
|
|
426
|
-
// Now reduce bytes to nibbles, and check for matches.
|
|
427
|
-
matches = vreinterpretq_u8_u4(matches_vec.u8x16);
|
|
428
|
-
if (matches) return h + sz_u64_ctz(matches) / 4;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
else if (n_length == 4) {
|
|
432
|
-
// This is a common case not only for ASCII 4-grams, but also UTF-32 content,
|
|
433
|
-
// emojis, Chinese, and many other east-Asian languages.
|
|
434
|
-
// Dealing with 32-bit values, we can analyze 13 offsets at once.
|
|
435
|
-
sz_u128_vec_t h_first_vec, h_second_vec, h_third_vec, h_fourth_vec, interleave_2mask_vec, interleave_4mask_vec,
|
|
436
|
-
n_vec, matches_vec;
|
|
437
|
-
sz_u64_t matches;
|
|
438
|
-
// Broadcast needle characters into SIMD registers.
|
|
439
|
-
n_vec.u32x4 = vdupq_n_u32(sz_u32_load(n).u32);
|
|
440
|
-
interleave_2mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
|
|
441
|
-
interleave_4mask_vec.u32x4 = vdupq_n_u32(0x0000FFFFu);
|
|
442
|
-
for (; h_length >= 16; h += 13, h_length -= 13) {
|
|
443
|
-
h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
|
|
444
|
-
h_second_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 1);
|
|
445
|
-
h_third_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 2);
|
|
446
|
-
h_fourth_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 3);
|
|
447
|
-
// We can now compare all four arrays of 32-bit values with the needle.
|
|
448
|
-
// The result of each comparison will also be 32 bits long.
|
|
449
|
-
// Then - we blend!
|
|
450
|
-
matches_vec.u8x16 = vbslq_u8(
|
|
451
|
-
interleave_4mask_vec.u8x16,
|
|
452
|
-
vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_first_vec.u32x4, n_vec.u32x4)),
|
|
453
|
-
vreinterpretq_u8_u32(vceqq_u32(h_second_vec.u32x4, n_vec.u32x4))),
|
|
454
|
-
vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_third_vec.u32x4, n_vec.u32x4)),
|
|
455
|
-
vreinterpretq_u8_u32(vceqq_u32(h_fourth_vec.u32x4, n_vec.u32x4))));
|
|
456
|
-
matches = vreinterpretq_u8_u4(matches_vec.u8x16);
|
|
457
|
-
if (matches) return h + sz_u64_ctz(matches) / 4;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
// Pick the parts of the needle that are worth comparing.
|
|
462
|
-
sz_size_t offset_first, offset_mid, offset_last;
|
|
463
|
-
_sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
|
|
464
|
-
// Broadcast those characters into SIMD registers.
|
|
465
|
-
sz_u64_t matches;
|
|
466
|
-
sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
|
|
467
|
-
n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
|
|
468
|
-
n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
|
|
469
|
-
n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
|
|
470
|
-
// Walk through the string.
|
|
471
|
-
for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
|
|
472
|
-
h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
|
|
473
|
-
h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
|
|
474
|
-
h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
|
|
475
|
-
matches_vec.u8x16 = vandq_u8( //
|
|
476
|
-
vandq_u8( //
|
|
477
|
-
vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
|
|
478
|
-
vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
|
|
479
|
-
vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
|
|
480
|
-
matches = vreinterpretq_u8_u4(matches_vec.u8x16);
|
|
481
|
-
while (matches) {
|
|
482
|
-
int potential_offset = sz_u64_ctz(matches) / 4;
|
|
483
|
-
if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
|
|
484
|
-
matches &= matches - 1;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return sz_find_serial(h, h_length, n, n_length);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
|
|
493
|
-
uint64x2_t *out_third_fourth) {
|
|
494
|
-
// Interleave elements
|
|
495
|
-
uint32x4x2_t interleaved = vzipq_u32(in_low, in_high);
|
|
496
|
-
|
|
497
|
-
// The results are now in two uint32x4_t vectors, which we need to cast to uint64x2_t
|
|
498
|
-
*out_first_second = vreinterpretq_u64_u32(interleaved.val[0]);
|
|
499
|
-
*out_third_fourth = vreinterpretq_u64_u32(interleaved.val[1]);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/* Arm NEON has several very relevant extensions for 32-bit FMA we can use for rolling hashes:
|
|
503
|
-
* * vmlaq_u32 - vector "fused-multiply-add"
|
|
504
|
-
* * vmlaq_n_u32 - vector-scalar "fused-multiply-add"
|
|
505
|
-
* * vmlsq_u32 - vector "fused-multiply-subtract"
|
|
506
|
-
* * vmlsq_n_u32 - vector-scalar "fused-multiply-subtract"
|
|
507
|
-
* Other basic intrinsics worth remembering:
|
|
508
|
-
* * vbslq_u32 - bitwise select to avoid branching
|
|
509
|
-
* * vld1q_dup_u32 - broadcast a 32-bit word into all 4 lanes of a 128-bit register
|
|
510
|
-
*/
|
|
511
|
-
|
|
512
|
-
SZ_PUBLIC void sz_hashes_neon_naive(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
|
|
513
|
-
sz_hash_callback_t callback, void *callback_handle) {
|
|
514
|
-
|
|
515
|
-
if (length < window_length || !window_length) return;
|
|
516
|
-
if (length < 2 * window_length) {
|
|
517
|
-
sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Using NEON, we can perform 4 integer multiplications and additions within one register.
|
|
522
|
-
// So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
|
|
523
|
-
sz_u8_t const *text = (sz_u8_t const *)start;
|
|
524
|
-
sz_u8_t const *text_end = text + length;
|
|
525
|
-
|
|
526
|
-
// Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
|
|
527
|
-
sz_u32_t prime_power_low = 1, prime_power_high = 1;
|
|
528
|
-
for (sz_size_t i = 0; i + 1 < window_length; ++i)
|
|
529
|
-
prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
|
|
530
|
-
prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
|
|
531
|
-
|
|
532
|
-
sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
|
|
533
|
-
uint8_t high_shift = 77u;
|
|
534
|
-
uint32_t prime = SZ_U32_MAX_PRIME;
|
|
535
|
-
|
|
536
|
-
sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
|
|
537
|
-
// Let's skip the first window, as we are going to compute it in the loop.
|
|
538
|
-
sz_size_t cycles = 0;
|
|
539
|
-
sz_size_t step_mask = step - 1;
|
|
540
|
-
sz_u32_t one = 1;
|
|
541
|
-
|
|
542
|
-
// In every iteration we process 4 consecutive sliding windows.
|
|
543
|
-
// Once each of them computes separate values, we step forward (W-1) times,
|
|
544
|
-
// computing all interleaving values. That way the byte spilled from the second
|
|
545
|
-
// hash, can be added to the first one. That way we minimize the number of separate loads.
|
|
546
|
-
for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
|
|
547
|
-
hash_low_vec.u32x4 = vld1q_dup_u32(&one);
|
|
548
|
-
hash_high_vec.u32x4 = vld1q_dup_u32(&one);
|
|
549
|
-
for (sz_size_t i = 0; i != window_length; ++i) {
|
|
550
|
-
chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
|
|
551
|
-
chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
|
|
552
|
-
chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
|
|
553
|
-
chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
|
|
554
|
-
chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
555
|
-
|
|
556
|
-
// Append new data.
|
|
557
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
558
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
559
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
560
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
561
|
-
hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
562
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if ((cycles & step_mask) == 0) {
|
|
566
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
567
|
-
&hash_mix23_vec.u64x2);
|
|
568
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
|
|
569
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
|
|
570
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
|
|
571
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
|
|
572
|
-
}
|
|
573
|
-
++cycles;
|
|
574
|
-
|
|
575
|
-
for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
|
|
576
|
-
// Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
|
|
577
|
-
// we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
|
|
578
|
-
chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
|
|
579
|
-
chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
|
|
580
|
-
chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
|
|
581
|
-
chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
|
|
582
|
-
chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
|
|
583
|
-
chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
584
|
-
chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
585
|
-
|
|
586
|
-
// Drop old data.
|
|
587
|
-
hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
|
|
588
|
-
hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
|
|
589
|
-
|
|
590
|
-
// Append new data.
|
|
591
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
592
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
593
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
594
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
595
|
-
hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
596
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
597
|
-
// Mix and call the user if needed
|
|
598
|
-
if ((cycles & step_mask) == 0) {
|
|
599
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
600
|
-
&hash_mix23_vec.u64x2);
|
|
601
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
|
|
602
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
|
|
603
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
|
|
604
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
SZ_PUBLIC void sz_hashes_neon_reusing_loads(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
|
|
611
|
-
sz_hash_callback_t callback, void *callback_handle) {
|
|
612
|
-
|
|
613
|
-
if (length < window_length || !window_length) return;
|
|
614
|
-
if (length < 2 * window_length) {
|
|
615
|
-
sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// Using NEON, we can perform 4 integer multiplications and additions within one register.
|
|
620
|
-
// So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
|
|
621
|
-
sz_u8_t const *text = (sz_u8_t const *)start;
|
|
622
|
-
sz_u8_t const *text_end = text + length;
|
|
623
|
-
|
|
624
|
-
// Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
|
|
625
|
-
sz_u32_t prime_power_low = 1, prime_power_high = 1;
|
|
626
|
-
for (sz_size_t i = 0; i + 1 < window_length; ++i)
|
|
627
|
-
prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
|
|
628
|
-
prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
|
|
629
|
-
|
|
630
|
-
sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
|
|
631
|
-
uint8_t high_shift = 77u;
|
|
632
|
-
uint32_t prime = SZ_U32_MAX_PRIME;
|
|
633
|
-
|
|
634
|
-
sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
|
|
635
|
-
// Let's skip the first window, as we are going to compute it in the loop.
|
|
636
|
-
sz_size_t cycles = 0;
|
|
637
|
-
sz_size_t const step_mask = step - 1;
|
|
638
|
-
sz_u32_t const one = 1;
|
|
639
|
-
|
|
640
|
-
// In every iteration we process 4 consecutive sliding windows.
|
|
641
|
-
// Once each of them computes separate values, we step forward (W-1) times,
|
|
642
|
-
// computing all interleaving values. That way the byte spilled from the second
|
|
643
|
-
// hash, can be added to the first one. That way we minimize the number of separate loads.
|
|
644
|
-
for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
|
|
645
|
-
hash_low_vec.u32x4 = vld1q_dup_u32(&one);
|
|
646
|
-
hash_high_vec.u32x4 = vld1q_dup_u32(&one);
|
|
647
|
-
for (sz_size_t i = 0; i != window_length; ++i) {
|
|
648
|
-
chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
|
|
649
|
-
chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
|
|
650
|
-
chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
|
|
651
|
-
chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
|
|
652
|
-
chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
653
|
-
|
|
654
|
-
// Append new data.
|
|
655
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
656
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
657
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
658
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
659
|
-
hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
660
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
if ((cycles & step_mask) == 0) {
|
|
664
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
665
|
-
&hash_mix23_vec.u64x2);
|
|
666
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
|
|
667
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
|
|
668
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
|
|
669
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
|
|
670
|
-
}
|
|
671
|
-
++cycles;
|
|
672
|
-
|
|
673
|
-
for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
|
|
674
|
-
// Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
|
|
675
|
-
// we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
|
|
676
|
-
// Vectorizing these loads is a huge opportunity for performance optimizations, but naive prefetching
|
|
677
|
-
// into the register just makes things worse.
|
|
678
|
-
chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
|
|
679
|
-
chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
|
|
680
|
-
chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
|
|
681
|
-
chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
|
|
682
|
-
chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
|
|
683
|
-
chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
684
|
-
chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
685
|
-
|
|
686
|
-
// Drop old data.
|
|
687
|
-
hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
|
|
688
|
-
hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
|
|
689
|
-
|
|
690
|
-
// Append new data.
|
|
691
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
692
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
693
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
694
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
695
|
-
hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
696
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
697
|
-
|
|
698
|
-
// Mix and call the user if needed
|
|
699
|
-
if ((cycles & step_mask) == 0) {
|
|
700
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
701
|
-
&hash_mix23_vec.u64x2);
|
|
702
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
|
|
703
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
|
|
704
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
|
|
705
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
SZ_PUBLIC void sz_hashes_neon_readahead(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
|
|
712
|
-
sz_hash_callback_t callback, void *callback_handle) {
|
|
713
|
-
|
|
714
|
-
if (length < window_length || !window_length) return;
|
|
715
|
-
if (length < 2 * window_length) {
|
|
716
|
-
sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Using NEON, we can perform 4 integer multiplications and additions within one register.
|
|
721
|
-
// So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
|
|
722
|
-
sz_u8_t const *text = (sz_u8_t const *)start;
|
|
723
|
-
sz_u8_t const *text_end = text + length;
|
|
724
|
-
|
|
725
|
-
// Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
|
|
726
|
-
sz_u32_t prime_power_low = 1, prime_power_high = 1;
|
|
727
|
-
for (sz_size_t i = 0; i + 1 < window_length; ++i)
|
|
728
|
-
prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
|
|
729
|
-
prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
|
|
730
|
-
|
|
731
|
-
sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
|
|
732
|
-
uint8_t high_shift = 77u;
|
|
733
|
-
uint32_t prime = SZ_U32_MAX_PRIME;
|
|
734
|
-
|
|
735
|
-
/// Primary buffers containing four upcasted characters as uint32_t values.
|
|
736
|
-
sz_u128_vec_t chars_outgoing_low_vec, chars_incoming_low_vec;
|
|
737
|
-
sz_u128_vec_t chars_outgoing_high_vec, chars_incoming_high_vec;
|
|
738
|
-
// Let's skip the first window, as we are going to compute it in the loop.
|
|
739
|
-
sz_size_t cycles = 0;
|
|
740
|
-
sz_size_t const step_mask = step - 1;
|
|
741
|
-
sz_u32_t const one = 1;
|
|
742
|
-
|
|
743
|
-
// In every iteration we process 4 consecutive sliding windows.
|
|
744
|
-
// Once each of them computes separate values, we step forward (W-1) times,
|
|
745
|
-
// computing all interleaving values. That way the byte spilled from the second
|
|
746
|
-
// hash, can be added to the first one. That way we minimize the number of separate loads.
|
|
747
|
-
sz_size_t read_ahead_length = window_length - 1 + 16; // TODO: Instead of +16 round up to 16 multiple
|
|
748
|
-
for (; text + window_length * 4 + read_ahead_length <= text_end; text += window_length * 4) {
|
|
749
|
-
hash_low_vec.u32x4 = vld1q_dup_u32(&one);
|
|
750
|
-
hash_high_vec.u32x4 = vld1q_dup_u32(&one);
|
|
751
|
-
|
|
752
|
-
for (sz_size_t i = 0; i < window_length;) {
|
|
753
|
-
sz_u128_vec_t chars_readahead_vec[4];
|
|
754
|
-
chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
|
|
755
|
-
chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
|
|
756
|
-
chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
|
|
757
|
-
chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
|
|
758
|
-
|
|
759
|
-
for (; i != window_length; ++i) {
|
|
760
|
-
chars_incoming_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
|
|
761
|
-
chars_incoming_low_vec.u32s[1] = chars_readahead_vec[1].u8x16[i];
|
|
762
|
-
chars_incoming_low_vec.u32s[2] = chars_readahead_vec[2].u8x16[i];
|
|
763
|
-
chars_incoming_low_vec.u32s[3] = chars_readahead_vec[3].u8x16[i];
|
|
764
|
-
chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
765
|
-
|
|
766
|
-
// Append new data.
|
|
767
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
768
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
769
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
770
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
771
|
-
hash_high_vec.u32x4 =
|
|
772
|
-
vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
773
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if ((cycles & step_mask) == 0) {
|
|
778
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
779
|
-
&hash_mix23_vec.u64x2);
|
|
780
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
|
|
781
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
|
|
782
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
|
|
783
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
|
|
784
|
-
}
|
|
785
|
-
++cycles;
|
|
786
|
-
|
|
787
|
-
for (sz_size_t i = 0; i + 1 < window_length; ++i, ++cycles) {
|
|
788
|
-
// Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
|
|
789
|
-
// we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
|
|
790
|
-
sz_u128_vec_t chars_readahead_vec[5];
|
|
791
|
-
chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
|
|
792
|
-
chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
|
|
793
|
-
chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
|
|
794
|
-
chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
|
|
795
|
-
chars_readahead_vec[4].u8x16 = vld1q_u8(text + window_length * 4 + i);
|
|
796
|
-
|
|
797
|
-
for (; i + 1 < window_length; ++i) {
|
|
798
|
-
// Transpose
|
|
799
|
-
chars_outgoing_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
|
|
800
|
-
chars_outgoing_low_vec.u32s[1] = chars_incoming_low_vec.u32s[0] = chars_readahead_vec[1].u8x16[i];
|
|
801
|
-
chars_outgoing_low_vec.u32s[2] = chars_incoming_low_vec.u32s[1] = chars_readahead_vec[2].u8x16[i];
|
|
802
|
-
chars_outgoing_low_vec.u32s[3] = chars_incoming_low_vec.u32s[2] = chars_readahead_vec[3].u8x16[i];
|
|
803
|
-
chars_incoming_low_vec.u32s[3] = chars_readahead_vec[4].u8x16[i];
|
|
804
|
-
|
|
805
|
-
chars_outgoing_high_vec.u8x16 = vaddq_u8(chars_outgoing_low_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
806
|
-
chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
|
|
807
|
-
|
|
808
|
-
// Drop old data.
|
|
809
|
-
hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_low_vec.u32x4, prime_power_low);
|
|
810
|
-
hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_high_vec.u32x4, prime_power_high);
|
|
811
|
-
|
|
812
|
-
// Append new data.
|
|
813
|
-
hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
|
|
814
|
-
hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
|
|
815
|
-
hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
816
|
-
vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
817
|
-
hash_high_vec.u32x4 =
|
|
818
|
-
vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
|
|
819
|
-
vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
|
|
820
|
-
|
|
821
|
-
// Mix and call the user if needed
|
|
822
|
-
if ((cycles & step_mask) == 0) {
|
|
823
|
-
interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
|
|
824
|
-
&hash_mix23_vec.u64x2);
|
|
825
|
-
callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0],
|
|
826
|
-
callback_handle);
|
|
827
|
-
callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1],
|
|
828
|
-
callback_handle);
|
|
829
|
-
callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0],
|
|
830
|
-
callback_handle);
|
|
831
|
-
callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1],
|
|
832
|
-
callback_handle);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
#endif // SZ_USE_ARM_NEON
|
|
840
|
-
|
|
841
|
-
#ifdef __cplusplus
|
|
842
|
-
} // extern "C"
|
|
843
|
-
#endif
|
|
844
|
-
|
|
845
|
-
#endif // STRINGZILLA_EXPERIMENTAL_H_
|