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/README.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# StringZilla 🦖
|
|
2
2
|
|
|
3
|
+
_The world wastes a minimum of $100M annually due to inefficient string operations.
|
|
4
|
+
A typical codebase processes strings character by character, resulting in too many branches and data-dependencies, neglecting 90% of modern CPU's potential.
|
|
5
|
+
LibC is different.
|
|
6
|
+
It attempts to leverage SIMD instructions to boost some operations, and is often used by higher-level languages, runtimes, and databases.
|
|
7
|
+
But it isn't perfect.
|
|
8
|
+
1️⃣ First, even on common hardware, including over a billion 64-bit ARM CPUs, common functions like `strstr` and `memmem` only achieve 1/3 of the CPU's thoughput.
|
|
9
|
+
2️⃣ Second, SIMD coverage is inconsistent: acceleration in forward scans does not guarantee speed in the reverse-order search.
|
|
10
|
+
3️⃣ At last, most high-level languages can't always use LibC, as the strings are often not NULL-terminated or may contain the Unicode "Zero" character in the middle of the string.
|
|
11
|
+
That's why StringZilla was created.
|
|
12
|
+
To provide predictably high performance, portable to any modern platform, operating system, and programming language._
|
|
13
|
+
|
|
3
14
|
[](https://github.com/ashvardanian/stringzilla)
|
|
4
15
|
[](https://crates.io/crates/stringzilla)
|
|
5
16
|
[](https://github.com/ashvardanian/StringZilla/actions/workflows/release.yml)
|
|
@@ -20,9 +31,14 @@ It __accelerates exact and fuzzy string matching, edit distance computations, so
|
|
|
20
31
|
- 🐍 __[Python](#quick-start-python-🐍):__ Upgrade your `str` to faster `Str`
|
|
21
32
|
- 🍎 __[Swift](#quick-start-swift-🍏):__ Use the `String+StringZilla` extension
|
|
22
33
|
- 🦀 __[Rust](#quick-start-rust-🦀):__ Use the `StringZilla` traits crate
|
|
34
|
+
- 🐚 __[Shell][faq-shell]__: Accelerate common CLI tools with `sz_` prefix
|
|
23
35
|
- 📚 Researcher? Jump to [Algorithms & Design Decisions](#algorithms--design-decisions-📚)
|
|
24
|
-
-
|
|
25
|
-
-
|
|
36
|
+
- 💡 Thinking to contribute? Look for ["good first issues"][first-issues]
|
|
37
|
+
- 🤝 And check the [guide](CONTRIBUTING.md) to setup the environment
|
|
38
|
+
- Want more bindings or features? Let [me](https://github.com/ashvardanian) know!
|
|
39
|
+
|
|
40
|
+
[faq-shell]: https://github.com/ashvardanian/StringZilla/blob/main/cli/README.md
|
|
41
|
+
[first-issues]: https://github.com/ashvardanian/StringZilla/issues
|
|
26
42
|
|
|
27
43
|
__Who is this for?__
|
|
28
44
|
|
|
@@ -293,14 +309,14 @@ Consider contributing, if you need a feature that's not yet implemented.
|
|
|
293
309
|
|
|
294
310
|
### Basic Usage
|
|
295
311
|
|
|
296
|
-
If you've ever used the Python `str`
|
|
312
|
+
If you've ever used the Python `str`, `bytes`, `bytearray`, `memoryview` class, you'll know what to expect.
|
|
297
313
|
StringZilla's `Str` class is a hybrid of those two, providing `str`-like interface to byte-arrays.
|
|
298
314
|
|
|
299
315
|
```python
|
|
300
316
|
from stringzilla import Str, File
|
|
301
317
|
|
|
302
|
-
text_from_str = Str('some-string')
|
|
303
|
-
text_from_file = Str(File('some-file.txt'))
|
|
318
|
+
text_from_str = Str('some-string') # no copies, just a view
|
|
319
|
+
text_from_file = Str(File('some-file.txt')) # memory-mapped file
|
|
304
320
|
```
|
|
305
321
|
|
|
306
322
|
The `File` class memory-maps a file from persistent memory without loading its copy into RAM.
|
|
@@ -312,43 +328,94 @@ A standard dataset pre-processing use case would be to map a sizeable textual da
|
|
|
312
328
|
- Length: `len(text) -> int`
|
|
313
329
|
- Indexing: `text[42] -> str`
|
|
314
330
|
- Slicing: `text[42:46] -> Str`
|
|
315
|
-
- String conversion: `str(text) -> str`
|
|
316
331
|
- Substring check: `'substring' in text -> bool`
|
|
317
332
|
- Hashing: `hash(text) -> int`
|
|
333
|
+
- String conversion: `str(text) -> str`
|
|
318
334
|
|
|
319
335
|
### Advanced Operations
|
|
320
336
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
337
|
+
```py
|
|
338
|
+
import sys
|
|
339
|
+
|
|
340
|
+
x: bool = text.contains('substring', start=0, end=sys.maxsize)
|
|
341
|
+
x: int = text.find('substring', start=0, end=sys.maxsize)
|
|
342
|
+
x: int = text.count('substring', start=0, end=sys.maxsize, allowoverlap=False)
|
|
343
|
+
x: str = text.decode(encoding='utf-8', errors='strict')
|
|
344
|
+
x: Strs = text.split(separator=' ', maxsplit=sys.maxsize, keepseparator=False)
|
|
345
|
+
x: Strs = text.rsplit(separator=' ', maxsplit=sys.maxsize, keepseparator=False)
|
|
346
|
+
x: Strs = text.splitlines(keeplinebreaks=False, maxsplit=sys.maxsize)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
It's important to note, that the last function behavior is slightly different from Python's `str.splitlines`.
|
|
350
|
+
The [native version][faq-splitlines] matches `\n`, `\r`, `\v` or `\x0b`, `\f` or `\x0c`, `\x1c`, `\x1d`, `\x1e`, `\x85`, `\r\n`, `\u2028`, `\u2029`, including 3x two-bytes-long runes.
|
|
351
|
+
The StringZilla version matches only `\n` and is practically a shortcut for `text.split('\n')`.
|
|
352
|
+
|
|
353
|
+
[faq-splitlines]: https://docs.python.org/3/library/stdtypes.html#str.splitlines
|
|
354
|
+
|
|
355
|
+
### Character Set Operations
|
|
356
|
+
|
|
357
|
+
Python strings don't natively support character set operations.
|
|
358
|
+
This forces people to use regular expressions, which are slow and hard to read.
|
|
359
|
+
To avoid the need for `re.finditer`, StringZilla provides the following interfaces:
|
|
360
|
+
|
|
361
|
+
```py
|
|
362
|
+
x: int = text.find_first_of('chars', start=0, end=sys.maxsize)
|
|
363
|
+
x: int = text.find_last_of('chars', start=0, end=sys.maxsize)
|
|
364
|
+
x: int = text.find_first_not_of('chars', start=0, end=sys.maxsize)
|
|
365
|
+
x: int = text.find_last_not_of('chars', start=0, end=sys.maxsize)
|
|
366
|
+
x: Strs = text.split_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)
|
|
367
|
+
x: Strs = text.rsplit_charset(separator='chars', maxsplit=sys.maxsize, keepseparator=False)
|
|
368
|
+
```
|
|
326
369
|
|
|
327
370
|
### Collection-Level Operations
|
|
328
371
|
|
|
329
|
-
Once split into a `Strs` object, you can sort, shuffle, and reorganize the slices.
|
|
372
|
+
Once split into a `Strs` object, you can sort, shuffle, and reorganize the slices, with minimum memory footprint.
|
|
373
|
+
If all the chunks are located in consecutive memory regions, the memory overhead can be as low as 4 bytes per chunk.
|
|
330
374
|
|
|
331
375
|
```python
|
|
332
376
|
lines: Strs = text.split(separator='\n') # 4 bytes per line overhead for under 4 GB of text
|
|
333
|
-
lines.
|
|
334
|
-
lines.shuffle(seed=42) #
|
|
377
|
+
batch: Strs = lines.sample(seed=42) # 10x faster than `random.choices`
|
|
378
|
+
lines.shuffle(seed=42) # or shuffle all lines in place and shard with slices
|
|
379
|
+
# WIP: lines.sort() # explodes to 16 bytes per line overhead for any length text
|
|
380
|
+
# WIP: sorted_order: tuple = lines.argsort() # similar to `numpy.argsort`
|
|
335
381
|
```
|
|
336
382
|
|
|
337
|
-
|
|
338
|
-
|
|
383
|
+
Working on [RedPajama][redpajama], addressing 20 Billion annotated english documents, one will need only 160 GB of RAM instead of Terabytes.
|
|
384
|
+
Once loaded, the data will be memory-mapped, and can be reused between multiple Python processes without copies.
|
|
385
|
+
And of course, you can use slices to navigate the dataset and shard it between multiple workers.
|
|
339
386
|
|
|
340
387
|
```python
|
|
341
|
-
|
|
342
|
-
|
|
388
|
+
lines[::3] # every third line
|
|
389
|
+
lines[1::1] # every odd line
|
|
390
|
+
lines[:-100:-1] # last 100 lines in reverse order
|
|
343
391
|
```
|
|
344
392
|
|
|
345
|
-
Those collections of `Strs` are designed to keep the memory consumption low.
|
|
346
|
-
If all the chunks are located in consecutive memory regions, the memory overhead can be as low as 4 bytes per chunk.
|
|
347
|
-
That's designed to handle very large datasets, like [RedPajama][redpajama].
|
|
348
|
-
To address all 20 Billion annotated english documents in it, one will need only 160 GB of RAM instead of Terabytes.
|
|
349
|
-
|
|
350
393
|
[redpajama]: https://github.com/togethercomputer/RedPajama-Data
|
|
351
394
|
|
|
395
|
+
### Iterators and Memory Efficiency
|
|
396
|
+
|
|
397
|
+
Python's operations like `split()` and `readlines()` immediately materialize a `list` of copied parts.
|
|
398
|
+
This can be very memory-inefficient for large datasets.
|
|
399
|
+
StringZilla saves a lot of memory by viewing existing memory regions as substrings, but even more memory can be saved by using lazily evaluated iterators.
|
|
400
|
+
|
|
401
|
+
- `text.split_iter(separator=' ', keepseparator=False) -> SplitIterator[Str]`
|
|
402
|
+
- `text.rsplit_iter(separator=' ', keepseparator=False) -> SplitIterator[Str]`
|
|
403
|
+
- `text.split_charset_iter(separator='chars', keepseparator=False) -> SplitIterator[Str]`
|
|
404
|
+
- `text.rsplit_charset_iter(separator='chars', keepseparator=False) -> SplitIterator[Str]`
|
|
405
|
+
|
|
406
|
+
StringZilla can easily be 10x more memory efficient than native Python classes for tokenization.
|
|
407
|
+
With lazy operations, it practically becomes free.
|
|
408
|
+
|
|
409
|
+
```py
|
|
410
|
+
import stringzilla as sz
|
|
411
|
+
%load_ext memory_profiler
|
|
412
|
+
|
|
413
|
+
text = open("enwik9.txt", "r").read() # 1 GB, mean word length 7.73 bytes
|
|
414
|
+
%memit text.split() # increment: 8670.12 MiB (152 ms)
|
|
415
|
+
%memit sz.split(text) # increment: 530.75 MiB (25 ms)
|
|
416
|
+
%memit sum(1 for _ in sz.split_iter(text)) # increment: 0.00 MiB
|
|
417
|
+
```
|
|
418
|
+
|
|
352
419
|
### Low-Level Python API
|
|
353
420
|
|
|
354
421
|
Aside from calling the methods on the `Str` and `Strs` classes, you can also call the global functions directly on `str` and `bytes` instances.
|
|
@@ -357,9 +424,9 @@ Assuming StringZilla CPython bindings are implemented [without any intermediate
|
|
|
357
424
|
```py
|
|
358
425
|
import stringzilla as sz
|
|
359
426
|
|
|
360
|
-
contains: bool = sz.contains("haystack", "needle", start=0, end=
|
|
361
|
-
offset: int = sz.find("haystack", "needle", start=0, end=
|
|
362
|
-
count: int = sz.count("haystack", "needle", start=0, end=
|
|
427
|
+
contains: bool = sz.contains("haystack", "needle", start=0, end=sys.maxsize)
|
|
428
|
+
offset: int = sz.find("haystack", "needle", start=0, end=sys.maxsize)
|
|
429
|
+
count: int = sz.count("haystack", "needle", start=0, end=sys.maxsize, allowoverlap=False)
|
|
363
430
|
```
|
|
364
431
|
|
|
365
432
|
### Edit Distances
|
|
@@ -438,6 +505,35 @@ assert sz.alignment_score(
|
|
|
438
505
|
|
|
439
506
|
</details>
|
|
440
507
|
|
|
508
|
+
### Serialization
|
|
509
|
+
|
|
510
|
+
#### Filesystem
|
|
511
|
+
|
|
512
|
+
Similar to how `File` can be used to read a large file, other interfaces can be used to dump strings to disk faster.
|
|
513
|
+
The `Str` class has `write_to` to write the string to a file, and `offset_within` to obtain integer offsets of substring view in larger string for navigation.
|
|
514
|
+
|
|
515
|
+
```py
|
|
516
|
+
web_archieve = Str("<html>...</html><html>...</html>")
|
|
517
|
+
_, end_tag, next_doc = web_archieve.partition("</html>") # or use `find`
|
|
518
|
+
next_doc_offset = next_doc.offset_within(web_archieve)
|
|
519
|
+
web_archieve.write_to("next_doc.html")
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
#### PyArrow
|
|
523
|
+
|
|
524
|
+
A `Str` is easy to cast to [PyArrow](https://arrow.apache.org/docs/python/arrays.html#string-and-binary-types) buffers.
|
|
525
|
+
|
|
526
|
+
```py
|
|
527
|
+
from pyarrow as foreign_buffer
|
|
528
|
+
from stringzilla import Str
|
|
529
|
+
|
|
530
|
+
original = "hello"
|
|
531
|
+
view = Str(native)
|
|
532
|
+
arrow = foreign_buffer(view.address, view.nbytes, view)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
That means you can convert `Str` to `pyarrow.Buffer` and `Strs` to `pyarrow.Array` without extra copies.
|
|
536
|
+
|
|
441
537
|
## Quick Start: C/C++ 🛠️
|
|
442
538
|
|
|
443
539
|
The C library is header-only, so you can just copy the `stringzilla.h` header into your project.
|
|
@@ -638,8 +734,8 @@ sz_string_is_on_stack(&string); // == sz_true_k
|
|
|
638
734
|
sz_string_grow(&string, 100, &allocator); // == sz_true_k
|
|
639
735
|
|
|
640
736
|
// Append, erase, insert into the string.
|
|
641
|
-
|
|
642
|
-
|
|
737
|
+
sz_string_expand(&string, 0, "_Hello_", 7, &allocator); // == sz_true_k
|
|
738
|
+
sz_string_expand(&string, SZ_SIZE_MAX, "world", 5, &allocator); // == sz_true_k
|
|
643
739
|
sz_string_erase(&string, 0, 1);
|
|
644
740
|
|
|
645
741
|
// Unpacking & introspection.
|
|
@@ -1015,9 +1111,27 @@ __`STRINGZILLA_BUILD_SHARED`, `STRINGZILLA_BUILD_TEST`, `STRINGZILLA_BUILD_BENCH
|
|
|
1015
1111
|
## Quick Start: Rust 🦀
|
|
1016
1112
|
|
|
1017
1113
|
StringZilla is available as a Rust crate.
|
|
1018
|
-
|
|
1114
|
+
Some of the interfaces will look familiar to the users of the `memchr` crate.
|
|
1115
|
+
|
|
1116
|
+
```rust
|
|
1117
|
+
use stringzilla::sz;
|
|
1118
|
+
|
|
1119
|
+
// Identical to `memchr::memmem::find` and `memchr::memmem::rfind` functions
|
|
1120
|
+
sz::find("Hello, world!", "world") // 7
|
|
1121
|
+
sz::rfind("Hello, world!", "world") // 7
|
|
1122
|
+
|
|
1123
|
+
// Generalizations of `memchr::memrchr[123]`
|
|
1124
|
+
sz::find_char_from("Hello, world!", "world") // 2
|
|
1125
|
+
sz::rfind_char_from("Hello, world!", "world") // 11
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
Unlike `memchr`, the throughput of `stringzilla` is [high in both normal and reverse-order searches][memchr-benchmarks].
|
|
1129
|
+
It also provides no constraints on the size of the character set, while `memchr` allows only 1, 2, or 3 characters.
|
|
1130
|
+
In addition to global functions, `stringzilla` provides a `StringZilla` extension trait:
|
|
1019
1131
|
|
|
1020
1132
|
```rust
|
|
1133
|
+
use stringzilla::StringZilla;
|
|
1134
|
+
|
|
1021
1135
|
let my_string: String = String::from("Hello, world!");
|
|
1022
1136
|
let my_str = my_string.as_str();
|
|
1023
1137
|
let my_cow_str = Cow::from(&my_string);
|
|
@@ -1035,6 +1149,23 @@ assert_eq!(my_str.sz_find("world"), Some(7));
|
|
|
1035
1149
|
assert_eq!(my_cow_str.as_ref().sz_find("world"), Some(7));
|
|
1036
1150
|
```
|
|
1037
1151
|
|
|
1152
|
+
The library also exposes Levenshtein and Hamming edit-distances for byte-arrays and UTF-8 strings, as well as Needleman-Wunch alignment scores.
|
|
1153
|
+
|
|
1154
|
+
```rust
|
|
1155
|
+
use stringzilla::sz;
|
|
1156
|
+
|
|
1157
|
+
// Handling arbitrary byte arrays:
|
|
1158
|
+
sz::edit_distance("Hello, world!", "Hello, world?"); // 1
|
|
1159
|
+
sz::hamming_distance("Hello, world!", "Hello, world?"); // 1
|
|
1160
|
+
sz::alignment_score("Hello, world!", "Hello, world?", sz::unary_substitution_costs(), -1); // -1
|
|
1161
|
+
|
|
1162
|
+
// Handling UTF-8 strings:
|
|
1163
|
+
sz::hamming_distance_utf8("αβγδ", "αγγδ") // 1
|
|
1164
|
+
sz::edit_distance_utf8("façade", "facade") // 1
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
[memchr-benchmarks]: https://github.com/ashvardanian/memchr_vs_stringzilla
|
|
1168
|
+
|
|
1038
1169
|
## Quick Start: Swift 🍏
|
|
1039
1170
|
|
|
1040
1171
|
StringZilla is available as a Swift package.
|
|
@@ -1256,13 +1387,16 @@ Another one is the [Fibonacci hash trick](https://probablydance.com/2018/06/16/f
|
|
|
1256
1387
|
|
|
1257
1388
|
### Unicode, UTF-8, and Wide Characters
|
|
1258
1389
|
|
|
1259
|
-
StringZilla
|
|
1260
|
-
|
|
1261
|
-
|
|
1390
|
+
Most StringZilla operations are byte-level, so they work well with ASCII and UTF8 content out of the box.
|
|
1391
|
+
In some cases, like edit-distance computation, the result of byte-level evaluation and character-level evaluation may differ.
|
|
1392
|
+
So StringZilla provides following functions to work with Unicode:
|
|
1262
1393
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1394
|
+
- `sz_edit_distance_utf8` - computes the Levenshtein distance between two UTF-8 strings.
|
|
1395
|
+
- `sz_hamming_distance_utf8` - computes the Hamming distance between two UTF-8 strings.
|
|
1396
|
+
|
|
1397
|
+
Java, JavaScript, Python 2, C#, and Objective-C, however, use wide characters (`wchar`) - two byte long codes, instead of the more reasonable fixed-length UTF32 or variable-length UTF8.
|
|
1265
1398
|
This leads [to all kinds of offset-counting issues][wide-char-offsets] when facing four-byte long Unicode characters.
|
|
1399
|
+
So consider transcoding with [simdutf](https://github.com/simdutf/simdutf), if you are coming from such environments.
|
|
1266
1400
|
|
|
1267
1401
|
[wide-char-offsets]: https://josephg.com/blog/string-length-lies/
|
|
1268
1402
|
|
|
@@ -1276,6 +1410,13 @@ If you like this project, you may also enjoy [USearch][usearch], [UCall][ucall],
|
|
|
1276
1410
|
[uform]: https://github.com/unum-cloud/uform
|
|
1277
1411
|
[simsimd]: https://github.com/ashvardanian/simsimd
|
|
1278
1412
|
|
|
1413
|
+
If you like strings and value efficiency, you may also enjoy the following projects:
|
|
1414
|
+
|
|
1415
|
+
- [simdutf](https://github.com/simdutf/simdutf) - transcoding UTF8, UTF16, and UTF32 LE and BE.
|
|
1416
|
+
- [hyperscan](https://github.com/intel/hyperscan) - regular expressions with SIMD acceleration.
|
|
1417
|
+
- [pyahocorasick](https://github.com/WojciechMula/pyahocorasick) - Aho-Corasick algorithm in Python.
|
|
1418
|
+
- [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) - fast string matching in C++ and Python.
|
|
1419
|
+
|
|
1279
1420
|
## License 📜
|
|
1280
1421
|
|
|
1281
1422
|
Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
|
package/stringzilla/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.5.0
|
package/stringzilla/build.rs
CHANGED
|
@@ -1,12 +1,85 @@
|
|
|
1
|
+
use std::env;
|
|
2
|
+
|
|
1
3
|
fn main() {
|
|
2
|
-
cc::Build::new()
|
|
4
|
+
let mut build = cc::Build::new();
|
|
5
|
+
build
|
|
3
6
|
.file("c/lib.c")
|
|
4
7
|
.include("include")
|
|
5
8
|
.warnings(false)
|
|
6
9
|
.flag_if_supported("-std=c99")
|
|
7
|
-
.flag_if_supported("-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
.flag_if_supported("-fPIC");
|
|
11
|
+
|
|
12
|
+
// Cargo will set different environment variables that we can use to properly configure the build.
|
|
13
|
+
// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
|
|
14
|
+
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
|
|
15
|
+
let target_endian = env::var("CARGO_CFG_TARGET_ENDIAN").unwrap_or_default();
|
|
16
|
+
|
|
17
|
+
// To get the operating system we can use the TARGET environment variable.
|
|
18
|
+
// To check the list of available targets, run `rustc --print target-list`.
|
|
19
|
+
let target = env::var("TARGET").unwrap_or_default();
|
|
20
|
+
|
|
21
|
+
if target.contains("linux") {
|
|
22
|
+
build.flag_if_supported("-fdiagnostics-color=always");
|
|
23
|
+
build.flag_if_supported("-O3");
|
|
24
|
+
build.flag_if_supported("-pedantic");
|
|
25
|
+
|
|
26
|
+
// Set architecture-specific flags and macros
|
|
27
|
+
if target_arch == "x86_64" {
|
|
28
|
+
build.define("SZ_USE_X86_AVX512", "1");
|
|
29
|
+
build.define("SZ_USE_X86_AVX2", "1");
|
|
30
|
+
} else {
|
|
31
|
+
build.define("SZ_USE_X86_AVX512", "0");
|
|
32
|
+
build.define("SZ_USE_X86_AVX2", "0");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if target_arch == "aarch64" {
|
|
36
|
+
build.flag_if_supported("-march=armv8-a+simd");
|
|
37
|
+
build.define("SZ_USE_ARM_SVE", "1");
|
|
38
|
+
build.define("SZ_USE_ARM_NEON", "1");
|
|
39
|
+
} else {
|
|
40
|
+
build.define("SZ_USE_ARM_SVE", "0");
|
|
41
|
+
build.define("SZ_USE_ARM_NEON", "0");
|
|
42
|
+
}
|
|
43
|
+
} else if target.contains("darwin") {
|
|
44
|
+
build.flag_if_supported("-fcolor-diagnostics");
|
|
45
|
+
build.flag_if_supported("-O3");
|
|
46
|
+
build.flag_if_supported("-pedantic");
|
|
47
|
+
|
|
48
|
+
if target_arch == "x86_64" {
|
|
49
|
+
// Assuming no AVX-512 support for Darwin as per setup.py logic
|
|
50
|
+
build.define("SZ_USE_X86_AVX512", "0");
|
|
51
|
+
build.define("SZ_USE_X86_AVX2", "1");
|
|
52
|
+
} else {
|
|
53
|
+
build.define("SZ_USE_X86_AVX512", "0");
|
|
54
|
+
build.define("SZ_USE_X86_AVX2", "0");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if target_arch == "aarch64" {
|
|
58
|
+
build.define("SZ_USE_ARM_SVE", "0"); // Assuming no SVE support for Darwin
|
|
59
|
+
build.define("SZ_USE_ARM_NEON", "1");
|
|
60
|
+
} else {
|
|
61
|
+
build.define("SZ_USE_ARM_SVE", "0");
|
|
62
|
+
build.define("SZ_USE_ARM_NEON", "0");
|
|
63
|
+
}
|
|
64
|
+
} else if target.contains("windows") {
|
|
65
|
+
// Set architecture-specific flags and macros
|
|
66
|
+
if target_arch == "x86_64" {
|
|
67
|
+
build.define("SZ_USE_X86_AVX512", "1");
|
|
68
|
+
build.define("SZ_USE_X86_AVX2", "1");
|
|
69
|
+
} else {
|
|
70
|
+
build.define("SZ_USE_X86_AVX512", "0");
|
|
71
|
+
build.define("SZ_USE_X86_AVX2", "0");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Set endian-specific macro
|
|
76
|
+
if target_endian == "big" {
|
|
77
|
+
build.define("SZ_DETECT_BIG_ENDIAN", "1");
|
|
78
|
+
} else {
|
|
79
|
+
build.define("SZ_DETECT_BIG_ENDIAN", "0");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
build.compile("stringzilla");
|
|
10
83
|
|
|
11
84
|
println!("cargo:rerun-if-changed=c/lib.c");
|
|
12
85
|
println!("cargo:rerun-if-changed=rust/lib.rs");
|
package/stringzilla/c/lib.c
CHANGED
|
@@ -23,6 +23,7 @@ typedef sz_size_t size_t; // Reuse the type definition we've inferred from `stri
|
|
|
23
23
|
#else
|
|
24
24
|
typedef __SIZE_TYPE__ size_t; // For GCC/Clang
|
|
25
25
|
#endif
|
|
26
|
+
int rand(void) { return 0; }
|
|
26
27
|
void free(void *start) { sz_unused(start); }
|
|
27
28
|
void *malloc(size_t length) {
|
|
28
29
|
sz_unused(length);
|
|
@@ -255,6 +256,20 @@ SZ_DYNAMIC sz_cptr_t sz_rfind_charset(sz_cptr_t text, sz_size_t length, sz_chars
|
|
|
255
256
|
return sz_dispatch_table.rfind_from_set(text, length, set);
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
SZ_DYNAMIC sz_size_t sz_hamming_distance( //
|
|
260
|
+
sz_cptr_t a, sz_size_t a_length, //
|
|
261
|
+
sz_cptr_t b, sz_size_t b_length, //
|
|
262
|
+
sz_size_t bound) {
|
|
263
|
+
return sz_hamming_distance_serial(a, a_length, b, b_length, bound);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
SZ_DYNAMIC sz_size_t sz_hamming_distance_utf8( //
|
|
267
|
+
sz_cptr_t a, sz_size_t a_length, //
|
|
268
|
+
sz_cptr_t b, sz_size_t b_length, //
|
|
269
|
+
sz_size_t bound) {
|
|
270
|
+
return sz_hamming_distance_utf8_serial(a, a_length, b, b_length, bound);
|
|
271
|
+
}
|
|
272
|
+
|
|
258
273
|
SZ_DYNAMIC sz_size_t sz_edit_distance( //
|
|
259
274
|
sz_cptr_t a, sz_size_t a_length, //
|
|
260
275
|
sz_cptr_t b, sz_size_t b_length, //
|
|
@@ -262,6 +277,13 @@ SZ_DYNAMIC sz_size_t sz_edit_distance( //
|
|
|
262
277
|
return sz_dispatch_table.edit_distance(a, a_length, b, b_length, bound, alloc);
|
|
263
278
|
}
|
|
264
279
|
|
|
280
|
+
SZ_DYNAMIC sz_size_t sz_edit_distance_utf8( //
|
|
281
|
+
sz_cptr_t a, sz_size_t a_length, //
|
|
282
|
+
sz_cptr_t b, sz_size_t b_length, //
|
|
283
|
+
sz_size_t bound, sz_memory_allocator_t *alloc) {
|
|
284
|
+
return _sz_edit_distance_wagner_fisher_serial(a, a_length, b, b_length, bound, sz_true_k, alloc);
|
|
285
|
+
}
|
|
286
|
+
|
|
265
287
|
SZ_DYNAMIC sz_ssize_t sz_alignment_score(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
|
|
266
288
|
sz_error_cost_t const *subs, sz_error_cost_t gap,
|
|
267
289
|
sz_memory_allocator_t *alloc) {
|
|
@@ -302,3 +324,14 @@ SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_
|
|
|
302
324
|
sz_charset_invert(&set);
|
|
303
325
|
return sz_rfind_charset(h, h_length, &set);
|
|
304
326
|
}
|
|
327
|
+
|
|
328
|
+
sz_u64_t _sz_random_generator(void *empty_state) {
|
|
329
|
+
sz_unused(empty_state);
|
|
330
|
+
return (sz_u64_t)rand();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
SZ_DYNAMIC void sz_generate(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
|
|
334
|
+
sz_random_generator_t generator, void *generator_user_data) {
|
|
335
|
+
if (!generator) generator = _sz_random_generator;
|
|
336
|
+
sz_generate_serial(alphabet, alphabet_size, result, result_length, generator, generator_user_data);
|
|
337
|
+
}
|
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
# SIMD-
|
|
1
|
+
# SIMD-accelerated CLI utilities based on StringZilla
|
|
2
|
+
|
|
3
|
+
This section of the project is pretty much a work in progress.
|
|
4
|
+
The goal is to provide a set of command-line utilities that:
|
|
5
|
+
|
|
6
|
+
- ✅ that benefit the most from SIMD instructions,
|
|
7
|
+
- ✅ rely solely on core StringZilla functionality,
|
|
8
|
+
- ✅ works the same on Linux, MacOS, and Windows.
|
|
9
|
+
|
|
10
|
+
Other utilities are, of course, welcome to use StringZilla but may not be good candidates for this repository.
|
|
11
|
+
To install, pull the Python package from PyPi:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install stringzilla
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Currently implemented:
|
|
18
|
+
|
|
19
|
+
- `sz_wc`: 3x faster `wc` word count.
|
|
20
|
+
- `sz_split`: 4x faster `splt` file splitting.
|
|
21
|
+
|
|
22
|
+
What other interfaces should be added?
|
|
23
|
+
Levenshtein distances?
|
|
24
|
+
Fuzzy search?
|
|
25
|
+
Are there common alternatives to mimic?
|
|
2
26
|
|
|
3
27
|
## `wc`: Word Count
|
|
4
28
|
|
|
@@ -6,15 +30,15 @@ The `wc` utility on Linux can be used to count the number of lines, words, and b
|
|
|
6
30
|
Using SIMD-accelerated character and character-set search, StringZilla, even with slow SSDs, it can be noticeably faster.
|
|
7
31
|
|
|
8
32
|
```bash
|
|
9
|
-
$ time wc enwik9.txt
|
|
10
|
-
13147025
|
|
33
|
+
$ time wc enwik9.txt
|
|
34
|
+
13147025 129348346 1000000000 enwik9.txt
|
|
11
35
|
|
|
12
36
|
real 0m3.562s
|
|
13
37
|
user 0m3.470s
|
|
14
38
|
sys 0m0.092s
|
|
15
39
|
|
|
16
|
-
$ time
|
|
17
|
-
13147025 139132610 1000000000 enwik9.txt
|
|
40
|
+
$ time sz_wc enwik9.txt
|
|
41
|
+
13147025 139132610 1000000000 enwik9.txt # Note: different word count, WIP
|
|
18
42
|
|
|
19
43
|
real 0m1.165s
|
|
20
44
|
user 0m1.121s
|
|
@@ -33,16 +57,9 @@ real 0m6.424s
|
|
|
33
57
|
user 0m0.179s
|
|
34
58
|
sys 0m0.663s
|
|
35
59
|
|
|
36
|
-
$ time
|
|
60
|
+
$ time sz_split -l 100000 enwik9.txt ...
|
|
37
61
|
|
|
38
62
|
real 0m1.482s
|
|
39
63
|
user 0m1.020s
|
|
40
64
|
sys 0m0.460s
|
|
41
65
|
```
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
What other interfaces should be added?
|
|
46
|
-
|
|
47
|
-
- Levenshtein distances?
|
|
48
|
-
- Fuzzy search?
|
|
File without changes
|