usearch 2.9.0 → 2.9.2
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/CITATION.cff +1 -1
- package/CONTRIBUTING.md +36 -2
- package/Cargo.toml +1 -1
- package/README.md +12 -15
- package/VERSION +1 -1
- package/c/lib.cpp +19 -17
- package/c/test.c +80 -38
- package/c/usearch.h +15 -15
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +5 -5
- package/include/usearch/index_dense.hpp +8 -4
- package/include/usearch/index_plugins.hpp +52 -32
- package/javascript/types/usearch.d.ts.map +1 -1
- package/javascript/usearch.js +4 -1
- package/package.json +3 -2
- package/simsimd/CONTRIBUTING.md +88 -0
- package/simsimd/Cargo.lock +4 -3
- package/simsimd/Cargo.toml +10 -2
- package/simsimd/README.md +102 -60
- package/simsimd/include/simsimd/binary.h +44 -0
- package/simsimd/include/simsimd/complex.h +174 -0
- package/simsimd/include/simsimd/simsimd.h +18 -9
- package/simsimd/include/simsimd/spatial.h +128 -36
- package/simsimd/include/simsimd/types.h +3 -2
- package/simsimd/package-lock.json +5 -5
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/native.rs +12 -9
- package/simsimd/rust/lib.c +32 -55
- package/simsimd/rust/lib.rs +374 -56
- package/stringzilla/CONTRIBUTING.md +17 -0
- package/stringzilla/Cargo.lock +1 -1
- package/stringzilla/Cargo.toml +1 -1
- package/stringzilla/README.md +175 -34
- package/stringzilla/VERSION +1 -1
- package/stringzilla/build.rs +77 -4
- package/stringzilla/c/lib.c +33 -0
- package/stringzilla/cli/README.md +30 -13
- package/stringzilla/cli/__init__.py +0 -0
- package/stringzilla/cli/split.py +66 -33
- package/stringzilla/cli/wc.py +101 -21
- package/stringzilla/include/stringzilla/experimental.h +122 -1
- package/stringzilla/include/stringzilla/stringzilla.h +262 -132
- package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
- package/stringzilla/package.json +1 -1
- package/stringzilla/rust/lib.rs +673 -162
- package/wasmer.toml +1 -1
package/stringzilla/cli/split.py
CHANGED
|
@@ -1,50 +1,91 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
+
import argparse
|
|
3
4
|
import sys
|
|
4
|
-
|
|
5
|
+
import stringzilla
|
|
5
6
|
from stringzilla import File, Str
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
+
def parse_arguments():
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="Output pieces of FILE to PREFIXaa, PREFIXab, ...; default size is 1000 lines, and default PREFIX is 'x'."
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"file", nargs="?", default="-", help='File to process, "-" for standard input'
|
|
15
|
+
)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"prefix", nargs="?", default="X", help='Output file prefix, default is "x"'
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"-l",
|
|
21
|
+
"--lines",
|
|
22
|
+
type=int,
|
|
23
|
+
default=1000,
|
|
24
|
+
help="Number of lines per output file, default is 1000",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-t",
|
|
28
|
+
"--separator",
|
|
29
|
+
default="\n",
|
|
30
|
+
help="Use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"-n",
|
|
34
|
+
"--number",
|
|
35
|
+
type=int,
|
|
36
|
+
default=None,
|
|
37
|
+
help="Generate N output files based on size of input",
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument("--version", action="version", version=stringzilla.__version__)
|
|
40
|
+
return parser.parse_args()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def split_file(file_path, lines_per_file, output_prefix, separator, number_of_files):
|
|
9
44
|
try:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
45
|
+
if separator == "\\0":
|
|
46
|
+
separator = "\0"
|
|
47
|
+
if file_path == "-":
|
|
48
|
+
file_contents = Str(sys.stdin.read())
|
|
49
|
+
else:
|
|
50
|
+
file_mapped = File(file_path)
|
|
51
|
+
file_contents = Str(file_mapped)
|
|
52
|
+
|
|
53
|
+
if number_of_files is not None:
|
|
54
|
+
total_length = len(file_contents)
|
|
55
|
+
chunk_size = total_length // number_of_files
|
|
56
|
+
for file_part in range(number_of_files):
|
|
57
|
+
start = file_part * chunk_size
|
|
58
|
+
end = (
|
|
59
|
+
start + chunk_size
|
|
60
|
+
if file_part < number_of_files - 1
|
|
61
|
+
else total_length
|
|
62
|
+
)
|
|
63
|
+
current_slice = file_contents[start:end]
|
|
64
|
+
output_path = f"{output_prefix}{file_part}"
|
|
65
|
+
current_slice.write_to(output_path)
|
|
66
|
+
return
|
|
15
67
|
current_position = 0
|
|
16
68
|
file_part = 0
|
|
17
|
-
newline_position =
|
|
18
|
-
-1
|
|
19
|
-
) # Start before file begins to find the first newline correctly
|
|
69
|
+
newline_position = -1
|
|
20
70
|
|
|
21
|
-
# Loop until the end of the file
|
|
22
71
|
while current_position < len(file_contents):
|
|
23
|
-
# 2. Loop to skip `lines_per_file` lines
|
|
24
72
|
for _ in range(lines_per_file):
|
|
25
|
-
newline_position = file_contents.find(
|
|
26
|
-
if newline_position == -1:
|
|
73
|
+
newline_position = file_contents.find(separator, newline_position + 1)
|
|
74
|
+
if newline_position == -1:
|
|
27
75
|
break
|
|
28
76
|
|
|
29
|
-
# If no newlines were found and we're not at the start, process the rest of the file
|
|
30
77
|
if newline_position == -1 and current_position < len(file_contents):
|
|
31
78
|
newline_position = len(file_contents)
|
|
32
79
|
|
|
33
|
-
# 3. Use offset_within to get the length of the current section
|
|
34
|
-
# Assuming offset_within gives you the length from the current position
|
|
35
80
|
section_length = (
|
|
36
81
|
newline_position - current_position if newline_position != -1 else 0
|
|
37
82
|
)
|
|
38
83
|
|
|
39
|
-
|
|
40
|
-
if section_length > 0: # Prevent creating empty files
|
|
84
|
+
if section_length > 0:
|
|
41
85
|
current_slice = file_contents[current_position : newline_position + 1]
|
|
42
|
-
|
|
43
|
-
# 4. Save the current slice to file
|
|
44
86
|
output_path = f"{output_prefix}{file_part}"
|
|
45
87
|
current_slice.write_to(output_path)
|
|
46
88
|
|
|
47
|
-
# Prepare for the next slice
|
|
48
89
|
file_part += 1
|
|
49
90
|
current_position = newline_position + 1
|
|
50
91
|
|
|
@@ -52,20 +93,12 @@ def split_file(file_path, lines_per_file, output_prefix):
|
|
|
52
93
|
print(f"No such file: {file_path}")
|
|
53
94
|
except Exception as e:
|
|
54
95
|
print(f"An error occurred: {e}")
|
|
96
|
+
print("Usage example: split.py [-l LINES] [file] [prefix]")
|
|
55
97
|
|
|
56
98
|
|
|
57
99
|
def main():
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"Usage: python split_file.py <lines_per_file> <input_file> <output_prefix>"
|
|
61
|
-
)
|
|
62
|
-
sys.exit(1)
|
|
63
|
-
|
|
64
|
-
lines_per_file = int(sys.argv[1])
|
|
65
|
-
file_path = sys.argv[2]
|
|
66
|
-
output_prefix = sys.argv[3]
|
|
67
|
-
|
|
68
|
-
split_file(file_path, lines_per_file, output_prefix)
|
|
100
|
+
args = parse_arguments()
|
|
101
|
+
split_file(args.file, args.lines, args.prefix, args.separator, args.number)
|
|
69
102
|
|
|
70
103
|
|
|
71
104
|
if __name__ == "__main__":
|
package/stringzilla/cli/wc.py
CHANGED
|
@@ -1,36 +1,116 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
-
|
|
4
|
+
import argparse
|
|
5
|
+
import stringzilla
|
|
5
6
|
from stringzilla import File, Str
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
def parse_arguments():
|
|
10
|
+
parser = argparse.ArgumentParser(
|
|
11
|
+
description="Print newline, word, and byte counts for each FILE, and a total line if more than one FILE is \
|
|
12
|
+
specified. A word is a non-zero-length sequence of acters delimited by white space."
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument("files", nargs="*", default=["-"], help="Files to process")
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"-c", "--bytes", action="store_true", help="print the byte counts"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"-m", "--chars", action="store_true", help="print the character counts"
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"-l", "--lines", action="store_true", help="print the newline counts"
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"-L",
|
|
26
|
+
"--max-line-length",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help="print the maximum display width",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-w", "--words", action="store_true", help="print the word counts"
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--version", action="version", version=stringzilla.__version__)
|
|
34
|
+
return parser.parse_args()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def wc(file_path, args):
|
|
38
|
+
if file_path == "-": # read from stdin
|
|
39
|
+
content = sys.stdin.read()
|
|
40
|
+
mapped_bytes = Str(content)
|
|
41
|
+
else:
|
|
42
|
+
try:
|
|
43
|
+
mapped_file = File(file_path)
|
|
44
|
+
mapped_bytes = Str(mapped_file)
|
|
45
|
+
except RuntimeError: # File gives a RuntimeError if the file does not exist
|
|
46
|
+
return f"No such file: {file_path}", False
|
|
47
|
+
|
|
48
|
+
line_count = mapped_bytes.count("\n")
|
|
49
|
+
word_count = mapped_bytes.count(" ") + 1
|
|
50
|
+
char_count = mapped_bytes.__len__()
|
|
51
|
+
counts = {
|
|
52
|
+
"line_count": line_count,
|
|
53
|
+
"word_count": word_count,
|
|
54
|
+
"char_count": char_count,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if args.max_line_length:
|
|
58
|
+
max_line_length = max(len(line) for line in str(mapped_bytes).split("\n"))
|
|
59
|
+
counts["max_line_length"] = max_line_length
|
|
15
60
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
61
|
+
if args.bytes or args.chars:
|
|
62
|
+
byte_count = char_count # assume 1 char = 1 byte
|
|
63
|
+
counts["byte_count"] = byte_count
|
|
64
|
+
|
|
65
|
+
return counts, True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_output(counts, args):
|
|
69
|
+
selected_counts = []
|
|
70
|
+
if args.lines:
|
|
71
|
+
selected_counts.append(counts["line_count"])
|
|
72
|
+
if args.words:
|
|
73
|
+
selected_counts.append(counts["word_count"])
|
|
74
|
+
if args.chars:
|
|
75
|
+
selected_counts.append(counts["char_count"])
|
|
76
|
+
if args.bytes:
|
|
77
|
+
selected_counts.append(counts.get("byte_count", counts["char_count"]))
|
|
78
|
+
if args.max_line_length:
|
|
79
|
+
selected_counts.append(counts.get("max_line_length", 0))
|
|
80
|
+
|
|
81
|
+
if not any([args.lines, args.words, args.chars, args.bytes, args.max_line_length]):
|
|
82
|
+
selected_counts = [
|
|
83
|
+
counts["line_count"],
|
|
84
|
+
counts["word_count"],
|
|
85
|
+
counts["char_count"],
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
return " ".join(str(count) for count in selected_counts)
|
|
19
89
|
|
|
20
90
|
|
|
21
91
|
def main():
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
92
|
+
args = parse_arguments()
|
|
93
|
+
total_counts = {
|
|
94
|
+
"line_count": 0,
|
|
95
|
+
"word_count": 0,
|
|
96
|
+
"char_count": 0,
|
|
97
|
+
"max_line_length": 0,
|
|
98
|
+
"byte_count": 0,
|
|
99
|
+
}
|
|
25
100
|
|
|
26
|
-
file_path
|
|
27
|
-
|
|
101
|
+
for file_path in args.files:
|
|
102
|
+
counts, success = wc(file_path, args)
|
|
103
|
+
if success:
|
|
104
|
+
for key in total_counts.keys():
|
|
105
|
+
total_counts[key] += counts.get(key, 0)
|
|
106
|
+
output = format_output(counts, args) + f" {file_path}"
|
|
107
|
+
print(output)
|
|
108
|
+
else:
|
|
109
|
+
print(counts)
|
|
28
110
|
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
print(
|
|
32
|
-
else:
|
|
33
|
-
print(counts)
|
|
111
|
+
if len(args.files) > 1:
|
|
112
|
+
total_output = format_output(total_counts, args) + " total"
|
|
113
|
+
print(total_output)
|
|
34
114
|
|
|
35
115
|
|
|
36
116
|
if __name__ == "__main__":
|
|
@@ -368,6 +368,127 @@ sz_u512_vec_t sz_inclusive_min(sz_i32_t previous, sz_error_cost_t gap, sz_u512_v
|
|
|
368
368
|
|
|
369
369
|
#if SZ_USE_ARM_NEON
|
|
370
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
|
+
|
|
371
492
|
SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
|
|
372
493
|
uint64x2_t *out_third_fourth) {
|
|
373
494
|
// Interleave elements
|
|
@@ -588,7 +709,7 @@ SZ_PUBLIC void sz_hashes_neon_reusing_loads(sz_cptr_t start, sz_size_t length, s
|
|
|
588
709
|
}
|
|
589
710
|
|
|
590
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,
|
|
591
|
-
|
|
712
|
+
sz_hash_callback_t callback, void *callback_handle) {
|
|
592
713
|
|
|
593
714
|
if (length < window_length || !window_length) return;
|
|
594
715
|
if (length < 2 * window_length) {
|