usearch 2.9.1 → 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 (43) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +10 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/test.c +58 -50
  7. package/conanfile.py +1 -1
  8. package/include/usearch/index.hpp +1 -1
  9. package/include/usearch/index_plugins.hpp +20 -10
  10. package/javascript/types/usearch.d.ts.map +1 -1
  11. package/javascript/usearch.js +4 -1
  12. package/package.json +1 -1
  13. package/simsimd/CONTRIBUTING.md +88 -0
  14. package/simsimd/Cargo.lock +4 -3
  15. package/simsimd/Cargo.toml +10 -2
  16. package/simsimd/README.md +102 -60
  17. package/simsimd/include/simsimd/binary.h +44 -0
  18. package/simsimd/include/simsimd/complex.h +174 -0
  19. package/simsimd/include/simsimd/simsimd.h +18 -9
  20. package/simsimd/include/simsimd/spatial.h +128 -36
  21. package/simsimd/include/simsimd/types.h +3 -2
  22. package/simsimd/package-lock.json +5 -5
  23. package/simsimd/package.json +1 -1
  24. package/simsimd/rust/benches/native.rs +12 -9
  25. package/simsimd/rust/lib.c +32 -55
  26. package/simsimd/rust/lib.rs +374 -56
  27. package/stringzilla/CONTRIBUTING.md +17 -0
  28. package/stringzilla/Cargo.lock +1 -1
  29. package/stringzilla/Cargo.toml +1 -1
  30. package/stringzilla/README.md +175 -34
  31. package/stringzilla/VERSION +1 -1
  32. package/stringzilla/build.rs +77 -4
  33. package/stringzilla/c/lib.c +33 -0
  34. package/stringzilla/cli/README.md +30 -13
  35. package/stringzilla/cli/__init__.py +0 -0
  36. package/stringzilla/cli/split.py +66 -33
  37. package/stringzilla/cli/wc.py +101 -21
  38. package/stringzilla/include/stringzilla/experimental.h +122 -1
  39. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  40. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  41. package/stringzilla/package.json +1 -1
  42. package/stringzilla/rust/lib.rs +673 -162
  43. package/wasmer.toml +1 -1
@@ -24,7 +24,7 @@
24
24
  #define STRINGZILLA_H_
25
25
 
26
26
  #define STRINGZILLA_VERSION_MAJOR 3
27
- #define STRINGZILLA_VERSION_MINOR 2
27
+ #define STRINGZILLA_VERSION_MINOR 5
28
28
  #define STRINGZILLA_VERSION_PATCH 0
29
29
 
30
30
  /**
@@ -497,8 +497,12 @@ SZ_PUBLIC sz_bool_t sz_isascii(sz_cptr_t text, sz_size_t length);
497
497
  * @param generate Callback producing random numbers given the generator state.
498
498
  * @param generator Generator state, can be a pointer to a seed, or a pointer to a random number generator.
499
499
  */
500
- SZ_PUBLIC void sz_generate(sz_cptr_t alphabet, sz_size_t cardinality, sz_ptr_t text, sz_size_t length,
501
- sz_random_generator_t generate, void *generator);
500
+ SZ_DYNAMIC void sz_generate(sz_cptr_t alphabet, sz_size_t cardinality, sz_ptr_t text, sz_size_t length,
501
+ sz_random_generator_t generate, void *generator);
502
+
503
+ /** @copydoc sz_generate */
504
+ SZ_PUBLIC void sz_generate_serial(sz_cptr_t alphabet, sz_size_t cardinality, sz_ptr_t text, sz_size_t length,
505
+ sz_random_generator_t generate, void *generator);
502
506
 
503
507
  /**
504
508
  * @brief Similar to `memcpy`, copies contents of one string into another.
@@ -764,8 +768,12 @@ SZ_PUBLIC sz_cptr_t sz_rfind_charset_serial(sz_cptr_t text, sz_size_t length, sz
764
768
  * @see sz_hamming_distance_utf8
765
769
  * @see https://en.wikipedia.org/wiki/Hamming_distance
766
770
  */
767
- SZ_PUBLIC sz_size_t sz_hamming_distance(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
768
- sz_size_t bound);
771
+ SZ_DYNAMIC sz_size_t sz_hamming_distance(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
772
+ sz_size_t bound);
773
+
774
+ /** @copydoc sz_hamming_distance */
775
+ SZ_PUBLIC sz_size_t sz_hamming_distance_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
776
+ sz_size_t bound);
769
777
 
770
778
  /**
771
779
  * @brief Computes the Hamming distance between two @b UTF8 strings - number of not matching characters.
@@ -783,8 +791,12 @@ SZ_PUBLIC sz_size_t sz_hamming_distance(sz_cptr_t a, sz_size_t a_length, sz_cptr
783
791
  * @see sz_hamming_distance
784
792
  * @see https://en.wikipedia.org/wiki/Hamming_distance
785
793
  */
786
- SZ_PUBLIC sz_size_t sz_hamming_distance_utf8(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
787
- sz_size_t bound);
794
+ SZ_DYNAMIC sz_size_t sz_hamming_distance_utf8(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
795
+ sz_size_t bound);
796
+
797
+ /** @copydoc sz_hamming_distance_utf8 */
798
+ SZ_PUBLIC sz_size_t sz_hamming_distance_utf8_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length,
799
+ sz_size_t bound);
788
800
 
789
801
  typedef sz_size_t (*sz_hamming_distance_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t, sz_size_t);
790
802
 
@@ -835,11 +847,15 @@ SZ_PUBLIC sz_size_t sz_edit_distance_serial(sz_cptr_t a, sz_size_t a_length, sz_
835
847
  * @see sz_memory_allocator_init_fixed, sz_memory_allocator_init_default, sz_edit_distance
836
848
  * @see https://en.wikipedia.org/wiki/Levenshtein_distance
837
849
  */
838
- SZ_PUBLIC sz_size_t sz_edit_distance_utf8(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
839
- sz_size_t bound, sz_memory_allocator_t *alloc);
850
+ SZ_DYNAMIC sz_size_t sz_edit_distance_utf8(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
851
+ sz_size_t bound, sz_memory_allocator_t *alloc);
840
852
 
841
853
  typedef sz_size_t (*sz_edit_distance_t)(sz_cptr_t, sz_size_t, sz_cptr_t, sz_size_t, sz_size_t, sz_memory_allocator_t *);
842
854
 
855
+ /** @copydoc sz_edit_distance_utf8 */
856
+ SZ_PUBLIC sz_size_t sz_edit_distance_utf8_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length, //
857
+ sz_size_t bound, sz_memory_allocator_t *alloc);
858
+
843
859
  /**
844
860
  * @brief Computes Needleman–Wunsch alignment score for two string. Often used in bioinformatics and cheminformatics.
845
861
  * Similar to the Levenshtein edit-distance, parameterized for gap and substitution penalties.
@@ -1609,14 +1625,51 @@ SZ_INTERNAL void _sz_locate_needle_anomalies(sz_cptr_t start, sz_size_t length,
1609
1625
 
1610
1626
  // Loop through letters to find non-colliding variants.
1611
1627
  if (length > 3 && has_duplicates) {
1612
- // Pivot the middle point left, until we find a character different from the first one.
1613
- for (; start[*second] == start[*first] && *second; --(*second)) {}
1614
1628
  // Pivot the middle point right, until we find a character different from the first one.
1615
1629
  for (; start[*second] == start[*first] && *second + 1 < *third; ++(*second)) {}
1616
1630
  // Pivot the third (last) point left, until we find a different character.
1617
1631
  for (; (start[*third] == start[*second] || start[*third] == start[*first]) && *third > (*second + 1);
1618
1632
  --(*third)) {}
1619
1633
  }
1634
+
1635
+ // TODO: Investigate alternative strategies for long needles.
1636
+ // On very long needles we have the luxury to choose!
1637
+ // Often dealing with UTF8, we will likely benfit from shifting the first and second characters
1638
+ // further to the right, to achieve not only uniqness within the needle, but also avoid common
1639
+ // rune prefixes of 2-, 3-, and 4-byte codes.
1640
+ if (length > 8) {
1641
+ // Pivot the first and second points right, until we find a character, that:
1642
+ // > is different from others.
1643
+ // > doesn't start with 0b'110x'xxxx - only 5 bits of relevant info.
1644
+ // > doesn't start with 0b'1110'xxxx - only 4 bits of relevant info.
1645
+ // > doesn't start with 0b'1111'0xxx - only 3 bits of relevant info.
1646
+ //
1647
+ // So we are practically searching for byte values that start with 0b0xxx'xxxx or 0b'10xx'xxxx.
1648
+ // Meaning they fall in the range [0, 127] and [128, 191], in other words any unsigned int up to 191.
1649
+ sz_u8_t const *start_u8 = (sz_u8_t const *)start;
1650
+ sz_size_t vibrant_first = *first, vibrant_second = *second, vibrant_third = *third;
1651
+
1652
+ // Let's begin with the seccond character, as the termination criterea there is more obvious
1653
+ // and we may end up with more variants to check for the first candidate.
1654
+ for (; (start_u8[vibrant_second] > 191 || start_u8[vibrant_second] == start_u8[vibrant_third]) &&
1655
+ (vibrant_second + 1 < vibrant_third);
1656
+ ++vibrant_second) {}
1657
+
1658
+ // Now check if we've indeed found a good candidate or should revert the `vibrant_second` to `second`.
1659
+ if (start_u8[vibrant_second] < 191) { *second = vibrant_second; }
1660
+ else { vibrant_second = *second; }
1661
+
1662
+ // Now check the first character.
1663
+ for (; (start_u8[vibrant_first] > 191 || start_u8[vibrant_first] == start_u8[vibrant_second] ||
1664
+ start_u8[vibrant_first] == start_u8[vibrant_third]) &&
1665
+ (vibrant_first + 1 < vibrant_second);
1666
+ ++vibrant_first) {}
1667
+
1668
+ // Now check if we've indeed found a good candidate or should revert the `vibrant_first` to `first`.
1669
+ // We don't need to shift the third one when dealing with texts as the last byte of the text is
1670
+ // also the last byte of a rune and contains the most information.
1671
+ if (start_u8[vibrant_first] < 191) { *first = vibrant_first; }
1672
+ }
1620
1673
  }
1621
1674
 
1622
1675
  #pragma GCC visibility pop
@@ -1685,21 +1738,34 @@ SZ_PUBLIC sz_cptr_t sz_rfind_charset_serial(sz_cptr_t text, sz_size_t length, sz
1685
1738
  #pragma GCC diagnostic pop
1686
1739
  }
1687
1740
 
1741
+ /**
1742
+ * One option to avoid branching is to use conditional moves and lookup the comparison result in a table:
1743
+ * sz_ordering_t ordering_lookup[2] = {sz_greater_k, sz_less_k};
1744
+ * for (; a != min_end; ++a, ++b)
1745
+ * if (*a != *b) return ordering_lookup[*a < *b];
1746
+ * That, however, introduces a data-dependency.
1747
+ * A cleaner option is to perform two comparisons and a subtraction.
1748
+ * One instruction more, but no data-dependency.
1749
+ */
1750
+ #define _sz_order_scalars(a, b) ((sz_ordering_t)((a > b) - (a < b)))
1751
+
1688
1752
  SZ_PUBLIC sz_ordering_t sz_order_serial(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
1689
- sz_ordering_t ordering_lookup[2] = {sz_greater_k, sz_less_k};
1690
1753
  sz_bool_t a_shorter = (sz_bool_t)(a_length < b_length);
1691
1754
  sz_size_t min_length = a_shorter ? a_length : b_length;
1692
1755
  sz_cptr_t min_end = a + min_length;
1693
1756
  #if SZ_USE_MISALIGNED_LOADS && !SZ_DETECT_BIG_ENDIAN
1694
1757
  for (sz_u64_vec_t a_vec, b_vec; a + 8 <= min_end; a += 8, b += 8) {
1695
- a_vec.u64 = sz_u64_bytes_reverse(sz_u64_load(a).u64);
1696
- b_vec.u64 = sz_u64_bytes_reverse(sz_u64_load(b).u64);
1697
- if (a_vec.u64 != b_vec.u64) return ordering_lookup[a_vec.u64 < b_vec.u64];
1758
+ a_vec = sz_u64_load(a);
1759
+ b_vec = sz_u64_load(b);
1760
+ if (a_vec.u64 != b_vec.u64)
1761
+ return _sz_order_scalars(sz_u64_bytes_reverse(a_vec.u64), sz_u64_bytes_reverse(b_vec.u64));
1698
1762
  }
1699
1763
  #endif
1700
1764
  for (; a != min_end; ++a, ++b)
1701
- if (*a != *b) return ordering_lookup[*a < *b];
1702
- return a_length != b_length ? ordering_lookup[a_shorter] : sz_equal_k;
1765
+ if (*a != *b) return _sz_order_scalars(*a, *b);
1766
+
1767
+ // If the strings are equal up to `min_end`, then the shorter string is smaller
1768
+ return _sz_order_scalars(a_length, b_length);
1703
1769
  }
1704
1770
 
1705
1771
  /**
@@ -2597,6 +2663,74 @@ SZ_PUBLIC sz_ssize_t sz_alignment_score_serial( //
2597
2663
  return result;
2598
2664
  }
2599
2665
 
2666
+ SZ_PUBLIC sz_size_t sz_hamming_distance_serial( //
2667
+ sz_cptr_t a, sz_size_t a_length, //
2668
+ sz_cptr_t b, sz_size_t b_length, //
2669
+ sz_size_t bound) {
2670
+
2671
+ sz_size_t const min_length = sz_min_of_two(a_length, b_length);
2672
+ sz_size_t const max_length = sz_max_of_two(a_length, b_length);
2673
+ sz_cptr_t const a_end = a + min_length;
2674
+ bound = bound == 0 ? max_length : bound;
2675
+
2676
+ // Walk through both strings using SWAR and counting the number of differing characters.
2677
+ sz_size_t distance = max_length - min_length;
2678
+ #if SZ_USE_MISALIGNED_LOADS && !SZ_DETECT_BIG_ENDIAN
2679
+ if (min_length >= SZ_SWAR_THRESHOLD) {
2680
+ sz_u64_vec_t a_vec, b_vec, match_vec;
2681
+ for (; a + 8 <= a_end && distance < bound; a += 8, b += 8) {
2682
+ a_vec.u64 = sz_u64_load(a).u64;
2683
+ b_vec.u64 = sz_u64_load(b).u64;
2684
+ match_vec = _sz_u64_each_byte_equal(a_vec, b_vec);
2685
+ distance += sz_u64_popcount((~match_vec.u64) & 0x8080808080808080ull);
2686
+ }
2687
+ }
2688
+ #endif
2689
+
2690
+ for (; a != a_end && distance < bound; ++a, ++b) { distance += (*a != *b); }
2691
+ return sz_min_of_two(distance, bound);
2692
+ }
2693
+
2694
+ SZ_PUBLIC sz_size_t sz_hamming_distance_utf8_serial( //
2695
+ sz_cptr_t a, sz_size_t a_length, //
2696
+ sz_cptr_t b, sz_size_t b_length, //
2697
+ sz_size_t bound) {
2698
+
2699
+ sz_cptr_t const a_end = a + a_length;
2700
+ sz_cptr_t const b_end = b + b_length;
2701
+ sz_size_t distance = 0;
2702
+
2703
+ sz_rune_t a_rune, b_rune;
2704
+ sz_rune_length_t a_rune_length, b_rune_length;
2705
+
2706
+ if (bound) {
2707
+ for (; a < a_end && b < b_end && distance < bound; a += a_rune_length, b += b_rune_length) {
2708
+ _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
2709
+ _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
2710
+ distance += (a_rune != b_rune);
2711
+ }
2712
+ // If one string has more runes, we need to go through the tail.
2713
+ if (distance < bound) {
2714
+ for (; a < a_end && distance < bound; a += a_rune_length, ++distance)
2715
+ _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
2716
+
2717
+ for (; b < b_end && distance < bound; b += b_rune_length, ++distance)
2718
+ _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
2719
+ }
2720
+ }
2721
+ else {
2722
+ for (; a < a_end && b < b_end; a += a_rune_length, b += b_rune_length) {
2723
+ _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
2724
+ _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
2725
+ distance += (a_rune != b_rune);
2726
+ }
2727
+ // If one string has more runes, we need to go through the tail.
2728
+ for (; a < a_end; a += a_rune_length, ++distance) _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
2729
+ for (; b < b_end; b += b_rune_length, ++distance) _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
2730
+ }
2731
+ return distance;
2732
+ }
2733
+
2600
2734
  /**
2601
2735
  * @brief Largest prime number that fits into 31 bits.
2602
2736
  * @see https://mersenneforum.org/showthread.php?t=3471
@@ -2842,10 +2976,11 @@ SZ_INTERNAL sz_u8_t sz_u8_toupper(sz_u8_t c) {
2842
2976
  * @brief Uses two small lookup tables (768 bytes total) to accelerate division by a small
2843
2977
  * unsigned integer. Performs two lookups, one multiplication, two shifts, and two accumulations.
2844
2978
  *
2845
- * @param divisor Integral value larger than one.
2979
+ * @param divisor Integral value @b larger than one.
2846
2980
  * @param number Integral value to divide.
2847
2981
  */
2848
2982
  SZ_INTERNAL sz_u8_t sz_u8_divide(sz_u8_t number, sz_u8_t divisor) {
2983
+ sz_assert(divisor > 1);
2849
2984
  static sz_u16_t const multipliers[256] = {
2850
2985
  0, 0, 0, 21846, 0, 39322, 21846, 9363, 0, 50973, 39322, 29790, 21846, 15124, 9363, 4370,
2851
2986
  0, 57826, 50973, 44841, 39322, 34329, 29790, 25645, 21846, 18351, 15124, 12137, 9363, 6780, 4370, 2115,
@@ -2933,18 +3068,21 @@ SZ_PUBLIC sz_bool_t sz_isascii_serial(sz_cptr_t text, sz_size_t length) {
2933
3068
  return sz_true_k;
2934
3069
  }
2935
3070
 
2936
- SZ_PUBLIC void sz_generate(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
2937
- sz_random_generator_t generator, void *generator_user_data) {
3071
+ SZ_PUBLIC void sz_generate_serial(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
3072
+ sz_random_generator_t generator, void *generator_user_data) {
2938
3073
 
2939
3074
  sz_assert(alphabet_size > 0 && alphabet_size <= 256 && "Inadequate alphabet size");
2940
3075
 
2941
- if (alphabet_size == 1)
2942
- for (sz_cptr_t end = result + result_length; result != end; ++result) *result = *alphabet;
3076
+ if (alphabet_size == 1) sz_fill(result, result_length, *alphabet);
2943
3077
 
2944
3078
  else {
2945
3079
  sz_assert(generator && "Expects a valid random generator");
2946
- for (sz_cptr_t end = result + result_length; result != end; ++result)
2947
- *result = alphabet[sz_u8_divide(generator(generator_user_data) & 0xFF, (sz_u8_t)alphabet_size)];
3080
+ sz_u8_t divisor = (sz_u8_t)alphabet_size;
3081
+ for (sz_cptr_t end = result + result_length; result != end; ++result) {
3082
+ sz_u8_t random = generator(generator_user_data) & 0xFF;
3083
+ sz_u8_t quotient = sz_u8_divide(random, divisor);
3084
+ *result = alphabet[random - quotient * divisor];
3085
+ }
2948
3086
  }
2949
3087
  }
2950
3088
 
@@ -3849,7 +3987,6 @@ SZ_INTERNAL __mmask64 _sz_u64_mask_until(sz_size_t n) {
3849
3987
  }
3850
3988
 
3851
3989
  SZ_PUBLIC sz_ordering_t sz_order_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr_t b, sz_size_t b_length) {
3852
- sz_ordering_t ordering_lookup[2] = {sz_greater_k, sz_less_k};
3853
3990
  sz_u512_vec_t a_vec, b_vec;
3854
3991
  __mmask64 a_mask, b_mask, mask_not_equal;
3855
3992
 
@@ -3862,7 +3999,7 @@ SZ_PUBLIC sz_ordering_t sz_order_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr
3862
3999
  int first_diff = _tzcnt_u64(mask_not_equal);
3863
4000
  char a_char = a[first_diff];
3864
4001
  char b_char = b[first_diff];
3865
- return ordering_lookup[a_char < b_char];
4002
+ return _sz_order_scalars(a_char, b_char);
3866
4003
  }
3867
4004
  a += 64, b += 64, a_length -= 64, b_length -= 64;
3868
4005
  }
@@ -3881,12 +4018,12 @@ SZ_PUBLIC sz_ordering_t sz_order_avx512(sz_cptr_t a, sz_size_t a_length, sz_cptr
3881
4018
  int first_diff = _tzcnt_u64(mask_not_equal);
3882
4019
  char a_char = a[first_diff];
3883
4020
  char b_char = b[first_diff];
3884
- return ordering_lookup[a_char < b_char];
4021
+ return _sz_order_scalars(a_char, b_char);
3885
4022
  }
3886
4023
  else
3887
4024
  // From logic perspective, the hardest cases are "abc\0" and "abc".
3888
4025
  // The result must be `sz_greater_k`, as the latter is shorter.
3889
- return a_length != b_length ? ordering_lookup[a_length < b_length] : sz_equal_k;
4026
+ return _sz_order_scalars(a_length, b_length);
3890
4027
  }
3891
4028
  else
3892
4029
  return sz_equal_k;
@@ -4370,10 +4507,10 @@ SZ_PUBLIC void sz_hashes_avx512(sz_cptr_t start, sz_size_t length, sz_size_t win
4370
4507
  chars_vec.zmm = _mm512_add_epi8(chars_vec.zmm, shift_vec.zmm);
4371
4508
 
4372
4509
  // ... and prefetch the next four characters into Level 2 or higher.
4373
- _mm_prefetch(text_fourth + 1, _MM_HINT_T1);
4374
- _mm_prefetch(text_third + 1, _MM_HINT_T1);
4375
- _mm_prefetch(text_second + 1, _MM_HINT_T1);
4376
- _mm_prefetch(text_first + 1, _MM_HINT_T1);
4510
+ _mm_prefetch((sz_cptr_t)text_fourth + 1, _MM_HINT_T1);
4511
+ _mm_prefetch((sz_cptr_t)text_third + 1, _MM_HINT_T1);
4512
+ _mm_prefetch((sz_cptr_t)text_second + 1, _MM_HINT_T1);
4513
+ _mm_prefetch((sz_cptr_t)text_first + 1, _MM_HINT_T1);
4377
4514
 
4378
4515
  // 3. Add the incoming characters.
4379
4516
  hash_vec.zmm = _mm512_add_epi64(hash_vec.zmm, chars_vec.zmm);
@@ -4839,32 +4976,74 @@ SZ_PUBLIC sz_cptr_t sz_find_neon(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, s
4839
4976
  if (h_length < n_length || !n_length) return SZ_NULL_CHAR;
4840
4977
  if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
4841
4978
 
4842
- // Pick the parts of the needle that are worth comparing.
4843
- sz_size_t offset_first, offset_mid, offset_last;
4844
- _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
4845
-
4846
- // Broadcast those characters into SIMD registers.
4847
- sz_u64_t matches;
4848
- sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
4849
- n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
4850
- n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
4851
- n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
4852
-
4853
4979
  // Scan through the string.
4854
- for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
4855
- h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
4856
- h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
4857
- h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
4858
- matches_vec.u8x16 = vandq_u8( //
4859
- vandq_u8( //
4860
- vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
4861
- vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
4862
- vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
4863
- matches = vreinterpretq_u8_u4(matches_vec.u8x16);
4864
- while (matches) {
4865
- int potential_offset = sz_u64_ctz(matches) / 4;
4866
- if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
4867
- matches &= matches - 1;
4980
+ // Assuming how tiny the Arm NEON registers are, we should avoid internal branches at all costs.
4981
+ // That's why, for smaller needles, we use different loops.
4982
+ if (n_length == 2) {
4983
+ // Broadcast needle characters into SIMD registers.
4984
+ sz_u64_t matches;
4985
+ sz_u128_vec_t h_first_vec, h_last_vec, n_first_vec, n_last_vec, matches_vec;
4986
+ // Dealing with 16-bit values, we can load 2 registers at a time and compare 31 possible offsets
4987
+ // in a single loop iteration.
4988
+ n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
4989
+ n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
4990
+ for (; h_length >= 17; h += 16, h_length -= 16) {
4991
+ h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 0));
4992
+ h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 1));
4993
+ matches_vec.u8x16 =
4994
+ vandq_u8(vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
4995
+ matches = vreinterpretq_u8_u4(matches_vec.u8x16);
4996
+ if (matches) return h + sz_u64_ctz(matches) / 4;
4997
+ }
4998
+ }
4999
+ else if (n_length == 3) {
5000
+ // Broadcast needle characters into SIMD registers.
5001
+ sz_u64_t matches;
5002
+ sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
5003
+ // Comparing 24-bit values is a bumer. Being lazy, I went with the same approach
5004
+ // as when searching for string over 4 characters long. I only avoid the last comparison.
5005
+ n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
5006
+ n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
5007
+ n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[2]);
5008
+ for (; h_length >= 18; h += 16, h_length -= 16) {
5009
+ h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 0));
5010
+ h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 1));
5011
+ h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + 2));
5012
+ matches_vec.u8x16 = vandq_u8( //
5013
+ vandq_u8( //
5014
+ vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
5015
+ vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
5016
+ vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
5017
+ matches = vreinterpretq_u8_u4(matches_vec.u8x16);
5018
+ if (matches) return h + sz_u64_ctz(matches) / 4;
5019
+ }
5020
+ }
5021
+ else {
5022
+ // Pick the parts of the needle that are worth comparing.
5023
+ sz_size_t offset_first, offset_mid, offset_last;
5024
+ _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
5025
+ // Broadcast those characters into SIMD registers.
5026
+ sz_u64_t matches;
5027
+ sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
5028
+ n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
5029
+ n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
5030
+ n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
5031
+ // Walk through the string.
5032
+ for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
5033
+ h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
5034
+ h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
5035
+ h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
5036
+ matches_vec.u8x16 = vandq_u8( //
5037
+ vandq_u8( //
5038
+ vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
5039
+ vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
5040
+ vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
5041
+ matches = vreinterpretq_u8_u4(matches_vec.u8x16);
5042
+ while (matches) {
5043
+ int potential_offset = sz_u64_ctz(matches) / 4;
5044
+ if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
5045
+ matches &= matches - 1;
5046
+ }
4868
5047
  }
4869
5048
  }
4870
5049
 
@@ -4976,81 +5155,6 @@ SZ_PUBLIC void sz_hashes_fingerprint(sz_cptr_t start, sz_size_t length, sz_size_
4976
5155
  sz_hashes(start, length, window_length, 1, _sz_hashes_fingerprint_pow2_callback, &fingerprint_buffer);
4977
5156
  }
4978
5157
 
4979
- SZ_PUBLIC sz_size_t sz_hamming_distance( //
4980
- sz_cptr_t a, sz_size_t a_length, //
4981
- sz_cptr_t b, sz_size_t b_length, //
4982
- sz_size_t bound) {
4983
-
4984
- sz_size_t const min_length = sz_min_of_two(a_length, b_length);
4985
- sz_size_t const max_length = sz_max_of_two(a_length, b_length);
4986
- sz_cptr_t const a_end = a + min_length;
4987
- bound = bound == 0 ? max_length : bound;
4988
-
4989
- // Walk through both strings using SWAR and counting the number of differing characters.
4990
- sz_size_t distance = max_length - min_length;
4991
- #if SZ_USE_MISALIGNED_LOADS && !SZ_DETECT_BIG_ENDIAN
4992
- if (min_length >= SZ_SWAR_THRESHOLD) {
4993
- sz_u64_vec_t a_vec, b_vec, match_vec;
4994
- for (; a + 8 <= a_end && distance < bound; a += 8, b += 8) {
4995
- a_vec.u64 = sz_u64_load(a).u64;
4996
- b_vec.u64 = sz_u64_load(b).u64;
4997
- match_vec = _sz_u64_each_byte_equal(a_vec, b_vec);
4998
- distance += sz_u64_popcount((~match_vec.u64) & 0x8080808080808080ull);
4999
- }
5000
- }
5001
- #endif
5002
-
5003
- for (; a != a_end && distance < bound; ++a, ++b) { distance += (*a != *b); }
5004
- return sz_min_of_two(distance, bound);
5005
- }
5006
-
5007
- SZ_PUBLIC sz_size_t sz_hamming_distance_utf8( //
5008
- sz_cptr_t a, sz_size_t a_length, //
5009
- sz_cptr_t b, sz_size_t b_length, //
5010
- sz_size_t bound) {
5011
-
5012
- sz_cptr_t const a_end = a + a_length;
5013
- sz_cptr_t const b_end = b + b_length;
5014
- sz_size_t distance = 0;
5015
-
5016
- sz_rune_t a_rune, b_rune;
5017
- sz_rune_length_t a_rune_length, b_rune_length;
5018
-
5019
- if (bound) {
5020
- for (; a < a_end && b < b_end && distance < bound; a += a_rune_length, b += b_rune_length) {
5021
- _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
5022
- _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
5023
- distance += (a_rune != b_rune);
5024
- }
5025
- // If one string has more runes, we need to go through the tail.
5026
- if (distance < bound) {
5027
- for (; a < a_end && distance < bound; a += a_rune_length, ++distance)
5028
- _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
5029
-
5030
- for (; b < b_end && distance < bound; b += b_rune_length, ++distance)
5031
- _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
5032
- }
5033
- }
5034
- else {
5035
- for (; a < a_end && b < b_end; a += a_rune_length, b += b_rune_length) {
5036
- _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
5037
- _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
5038
- distance += (a_rune != b_rune);
5039
- }
5040
- // If one string has more runes, we need to go through the tail.
5041
- for (; a < a_end; a += a_rune_length, ++distance) _sz_extract_utf8_rune(a, &a_rune, &a_rune_length);
5042
- for (; b < b_end; b += b_rune_length, ++distance) _sz_extract_utf8_rune(b, &b_rune, &b_rune_length);
5043
- }
5044
- return distance;
5045
- }
5046
-
5047
- SZ_PUBLIC sz_size_t sz_edit_distance_utf8( //
5048
- sz_cptr_t a, sz_size_t a_length, //
5049
- sz_cptr_t b, sz_size_t b_length, //
5050
- sz_size_t bound, sz_memory_allocator_t *alloc) {
5051
- return _sz_edit_distance_wagner_fisher_serial(a, a_length, b, b_length, bound, sz_true_k, alloc);
5052
- }
5053
-
5054
5158
  #if !SZ_DYNAMIC_DISPATCH
5055
5159
 
5056
5160
  SZ_DYNAMIC sz_bool_t sz_equal(sz_cptr_t a, sz_cptr_t b, sz_size_t length) {
@@ -5167,6 +5271,20 @@ SZ_DYNAMIC sz_cptr_t sz_rfind_charset(sz_cptr_t text, sz_size_t length, sz_chars
5167
5271
  #endif
5168
5272
  }
5169
5273
 
5274
+ SZ_DYNAMIC sz_size_t sz_hamming_distance( //
5275
+ sz_cptr_t a, sz_size_t a_length, //
5276
+ sz_cptr_t b, sz_size_t b_length, //
5277
+ sz_size_t bound) {
5278
+ return sz_hamming_distance_serial(a, a_length, b, b_length, bound);
5279
+ }
5280
+
5281
+ SZ_DYNAMIC sz_size_t sz_hamming_distance_utf8( //
5282
+ sz_cptr_t a, sz_size_t a_length, //
5283
+ sz_cptr_t b, sz_size_t b_length, //
5284
+ sz_size_t bound) {
5285
+ return sz_hamming_distance_utf8_serial(a, a_length, b, b_length, bound);
5286
+ }
5287
+
5170
5288
  SZ_DYNAMIC sz_size_t sz_edit_distance( //
5171
5289
  sz_cptr_t a, sz_size_t a_length, //
5172
5290
  sz_cptr_t b, sz_size_t b_length, //
@@ -5178,6 +5296,13 @@ SZ_DYNAMIC sz_size_t sz_edit_distance( //
5178
5296
  #endif
5179
5297
  }
5180
5298
 
5299
+ SZ_DYNAMIC sz_size_t sz_edit_distance_utf8( //
5300
+ sz_cptr_t a, sz_size_t a_length, //
5301
+ sz_cptr_t b, sz_size_t b_length, //
5302
+ sz_size_t bound, sz_memory_allocator_t *alloc) {
5303
+ return _sz_edit_distance_wagner_fisher_serial(a, a_length, b, b_length, bound, sz_true_k, alloc);
5304
+ }
5305
+
5181
5306
  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,
5182
5307
  sz_error_cost_t const *subs, sz_error_cost_t gap,
5183
5308
  sz_memory_allocator_t *alloc) {
@@ -5229,6 +5354,11 @@ SZ_DYNAMIC sz_cptr_t sz_rfind_char_not_from(sz_cptr_t h, sz_size_t h_length, sz_
5229
5354
  return sz_rfind_charset(h, h_length, &set);
5230
5355
  }
5231
5356
 
5357
+ SZ_DYNAMIC void sz_generate(sz_cptr_t alphabet, sz_size_t alphabet_size, sz_ptr_t result, sz_size_t result_length,
5358
+ sz_random_generator_t generator, void *generator_user_data) {
5359
+ sz_generate_serial(alphabet, alphabet_size, result, result_length, generator, generator_user_data);
5360
+ }
5361
+
5232
5362
  #endif
5233
5363
  #pragma endregion
5234
5364
 
@@ -458,8 +458,9 @@ class range_matches {
458
458
  return temp;
459
459
  }
460
460
 
461
- bool operator!=(iterator const &other) const noexcept { return remaining_.begin() != other.remaining_.begin(); }
462
- bool operator==(iterator const &other) const noexcept { return remaining_.begin() == other.remaining_.begin(); }
461
+ // Assumes both iterators point to the same underlying string.
462
+ bool operator!=(iterator const &other) const noexcept { return remaining_.data() != other.remaining_.data(); }
463
+ bool operator==(iterator const &other) const noexcept { return remaining_.data() == other.remaining_.data(); }
463
464
  bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
464
465
  bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
465
466
  };
@@ -550,8 +551,14 @@ class range_rmatches {
550
551
  return temp;
551
552
  }
552
553
 
553
- bool operator!=(iterator const &other) const noexcept { return remaining_.end() != other.remaining_.end(); }
554
- bool operator==(iterator const &other) const noexcept { return remaining_.end() == other.remaining_.end(); }
554
+ // Assumes both iterators point to the same underlying string.
555
+ // This has to be `.data() + .size()`, to be compatible with `std::string_view` on MSVC.
556
+ bool operator!=(iterator const &other) const noexcept {
557
+ return remaining_.data() + remaining_.size() != other.remaining_.data() + other.remaining_.size();
558
+ }
559
+ bool operator==(iterator const &other) const noexcept {
560
+ return remaining_.data() + remaining_.size() == other.remaining_.data() + other.remaining_.size();
561
+ }
555
562
  bool operator!=(end_sentinel_type) const noexcept { return !remaining_.empty(); }
556
563
  bool operator==(end_sentinel_type) const noexcept { return remaining_.empty(); }
557
564
  };
@@ -3160,7 +3167,8 @@ class basic_string {
3160
3167
  * @param alphabet A string of characters to choose from.
3161
3168
  */
3162
3169
  basic_string &randomize(string_view alphabet = "abcdefghijklmnopqrstuvwxyz") noexcept {
3163
- return randomize(&std::rand, alphabet);
3170
+ auto generator = []() { return static_cast<sz_u64_t>(std::rand()); };
3171
+ return randomize(generator, alphabet);
3164
3172
  }
3165
3173
 
3166
3174
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stringzilla",
3
- "version": "3.2.0",
3
+ "version": "3.5.0",
4
4
  "description": "SIMD-accelerated string search, sort, hashes, fingerprints, & edit distances",
5
5
  "author": "Ash Vardanian",
6
6
  "license": "Apache 2.0",