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.
- package/CITATION.cff +1 -1
- package/CONTRIBUTING.md +10 -2
- package/Cargo.toml +1 -1
- package/README.md +12 -15
- package/VERSION +1 -1
- package/c/test.c +58 -50
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +20 -10
- package/javascript/types/usearch.d.ts.map +1 -1
- package/javascript/usearch.js +4 -1
- package/package.json +1 -1
- 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
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
#define STRINGZILLA_H_
|
|
25
25
|
|
|
26
26
|
#define STRINGZILLA_VERSION_MAJOR 3
|
|
27
|
-
#define STRINGZILLA_VERSION_MINOR
|
|
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
|
-
|
|
501
|
-
|
|
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
|
-
|
|
768
|
-
|
|
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
|
-
|
|
787
|
-
|
|
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
|
-
|
|
839
|
-
|
|
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
|
|
1696
|
-
b_vec
|
|
1697
|
-
if (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
|
|
1702
|
-
|
|
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
|
|
2937
|
-
|
|
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
|
-
|
|
2947
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
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
|
-
|
|
462
|
-
bool operator
|
|
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
|
-
|
|
554
|
-
|
|
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
|
|
3170
|
+
auto generator = []() { return static_cast<sz_u64_t>(std::rand()); };
|
|
3171
|
+
return randomize(generator, alphabet);
|
|
3164
3172
|
}
|
|
3165
3173
|
|
|
3166
3174
|
/**
|