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.
Files changed (46) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +36 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/lib.cpp +19 -17
  7. package/c/test.c +80 -38
  8. package/c/usearch.h +15 -15
  9. package/conanfile.py +1 -1
  10. package/include/usearch/index.hpp +5 -5
  11. package/include/usearch/index_dense.hpp +8 -4
  12. package/include/usearch/index_plugins.hpp +52 -32
  13. package/javascript/types/usearch.d.ts.map +1 -1
  14. package/javascript/usearch.js +4 -1
  15. package/package.json +3 -2
  16. package/simsimd/CONTRIBUTING.md +88 -0
  17. package/simsimd/Cargo.lock +4 -3
  18. package/simsimd/Cargo.toml +10 -2
  19. package/simsimd/README.md +102 -60
  20. package/simsimd/include/simsimd/binary.h +44 -0
  21. package/simsimd/include/simsimd/complex.h +174 -0
  22. package/simsimd/include/simsimd/simsimd.h +18 -9
  23. package/simsimd/include/simsimd/spatial.h +128 -36
  24. package/simsimd/include/simsimd/types.h +3 -2
  25. package/simsimd/package-lock.json +5 -5
  26. package/simsimd/package.json +1 -1
  27. package/simsimd/rust/benches/native.rs +12 -9
  28. package/simsimd/rust/lib.c +32 -55
  29. package/simsimd/rust/lib.rs +374 -56
  30. package/stringzilla/CONTRIBUTING.md +17 -0
  31. package/stringzilla/Cargo.lock +1 -1
  32. package/stringzilla/Cargo.toml +1 -1
  33. package/stringzilla/README.md +175 -34
  34. package/stringzilla/VERSION +1 -1
  35. package/stringzilla/build.rs +77 -4
  36. package/stringzilla/c/lib.c +33 -0
  37. package/stringzilla/cli/README.md +30 -13
  38. package/stringzilla/cli/__init__.py +0 -0
  39. package/stringzilla/cli/split.py +66 -33
  40. package/stringzilla/cli/wc.py +101 -21
  41. package/stringzilla/include/stringzilla/experimental.h +122 -1
  42. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  43. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  44. package/stringzilla/package.json +1 -1
  45. package/stringzilla/rust/lib.rs +673 -162
  46. package/wasmer.toml +1 -1
@@ -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
  [![StringZilla Python installs](https://static.pepy.tech/personalized-badge/stringzilla?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=StringZilla%20Python%20installs)](https://github.com/ashvardanian/stringzilla)
4
15
  [![StringZilla Rust installs](https://img.shields.io/crates/d/stringzilla?logo=rust&label=Rust%20installs)](https://crates.io/crates/stringzilla)
5
16
  [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ashvardanian/StringZilla/release.yml?branch=main&label=Ubuntu)](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
- - 🤝 Want to benchmark or contribute? Jump to [Contributing](CONTRIBUTING.md)
25
- - Code in other languages? Let [me](https://github.com/ashvardanian) know!
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` or `bytes` class, you'll know what to expect.
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
- - `text.contains('substring', start=0, end=9223372036854775807) -> bool`
322
- - `text.find('substring', start=0, end=9223372036854775807) -> int`
323
- - `text.count('substring', start=0, end=9223372036854775807, allowoverlap=False) -> int`
324
- - `text.splitlines(keeplinebreaks=False, separator='\n') -> Strs`
325
- - `text.split(separator=' ', maxsplit=9223372036854775807, keepseparator=False) -> Strs`
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.sort() # explodes to 16 bytes per line overhead for any length text
334
- lines.shuffle(seed=42) # reproducing dataset shuffling with a seed
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
- Assuming superior search speed splitting should also work 3x faster than with native Python strings.
338
- Need copies?
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
- sorted_copy: Strs = lines.sorted()
342
- shuffled_copy: Strs = lines.shuffled(seed=42)
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=9223372036854775807)
361
- offset: int = sz.find("haystack", "needle", start=0, end=9223372036854775807)
362
- count: int = sz.count("haystack", "needle", start=0, end=9223372036854775807, allowoverlap=False)
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
- sz_string_append(&string, "_Hello_", 7, &allocator); // == sz_true_k
642
- sz_string_append(&string, "world", 5, &allocator); // == sz_true_k
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
- It currently covers only the most basic functionality, but is planned to be extended to cover the full C++ API.
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 does not __yet__ implement any Unicode-specific algorithms.
1260
- The content is addressed at byte-level, and the string is assumed to be encoded in UTF-8 or extended ASCII.
1261
- Refer to [simdutf](https://github.com/simdutf/simdutf) for fast conversions and [icu](https://github.com/unicode-org/icu) for character metadata.
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
- This may introduce frictions, when binding to some programming languages.
1264
- Namely, Java, JavaScript, Python 2, C#, and Objective-C use wide characters (`wchar`) - two byte long codes.
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.
@@ -1 +1 @@
1
- 3.2.0
1
+ 3.5.0
@@ -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("-fcolor-diagnostics")
8
- .flag_if_supported("-fPIC")
9
- .compile("stringzilla");
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");
@@ -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-accelerate CLI utilities based on StringZilla
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 129348346 1000000000 enwik9.txt
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 cli/wc.py enwik9.txt
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 cli/split.py 100000 enwik9.txt ...
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