/// An architecture independent "packed pair" finder. /// /// This finder picks two bytes that it believes have high predictive power for /// indicating an overall match of a needle. At search time, it reports offsets /// where the needle could match based on whether the pair of bytes it chose /// match. /// /// This is architecture independent because it utilizes `memchr` to find the /// occurrence of one of the bytes in the pair, and then checks whether the /// second byte matches. If it does, in the case of [`Finder::find_prefilter`], /// the location at which the needle could match is returned. /// /// It is generally preferred to use architecture specific routines for a /// "packed pair" prefilter, but this can be a useful fallback when the /// architecture independent routines are unavailable. #[derive(Clone, Copy, Debug)] pubstruct Finder {
pair: Pair,
byte1: u8,
byte2: u8,
}
impl Finder { /// Create a new prefilter that reports possible locations where the given /// needle matches. #[inline] pubfn new(needle: &[u8]) -> Option<Finder> {
Finder::with_pair(needle, Pair::new(needle)?)
}
/// Create a new prefilter using the pair given. /// /// If the prefilter could not be constructed, then `None` is returned. /// /// This constructor permits callers to control precisely which pair of /// bytes is used as a predicate. #[inline] pubfn with_pair(needle: &[u8], pair: Pair) -> Option<Finder> { let byte1 = needle[usize::from(pair.index1())]; let byte2 = needle[usize::from(pair.index2())]; // Currently this can never fail so we could just return a Finder, // but it's conceivable this could change.
Some(Finder { pair, byte1, byte2 })
}
/// Run this finder on the given haystack as a prefilter. /// /// If a candidate match is found, then an offset where the needle *could* /// begin in the haystack is returned. #[inline] pubfn find_prefilter(&self, haystack: &[u8]) -> Option<usize> { letmut i = 0; let index1 = usize::from(self.pair.index1()); let index2 = usize::from(self.pair.index2()); loop { // Use a fast vectorized implementation to skip to the next // occurrence of the rarest byte (heuristically chosen) in the // needle.
i += memchr(self.byte1, &haystack[i..])?; let found = i;
i += 1;
// If we can't align our first byte match with the haystack, then a // match is impossible. let aligned1 = match found.checked_sub(index1) {
None => continue,
Some(aligned1) => aligned1,
};
// Now align the second byte match with the haystack. A mismatch // means that a match is impossible. let aligned2 = match aligned1.checked_add(index2) {
None => continue,
Some(aligned_index2) => aligned_index2,
}; if haystack.get(aligned2).map_or(true, |&b| b != self.byte2) { continue;
}
// We've done what we can. There might be a match here. return Some(aligned1);
}
}
/// Returns the pair of offsets (into the needle) used to check as a /// predicate before confirming whether a needle exists at a particular /// position. #[inline] pubfn pair(&self) -> &Pair {
&self.pair
}
}
/// A pair of byte offsets into a needle to use as a predicate. /// /// This pair is used as a predicate to quickly filter out positions in a /// haystack in which a needle cannot match. In some cases, this pair can even /// be used in vector algorithms such that the vector algorithm only switches /// over to scalar code once this pair has been found. /// /// A pair of offsets can be used in both substring search implementations and /// in prefilters. The former will report matches of a needle in a haystack /// where as the latter will only report possible matches of a needle. /// /// The offsets are limited each to a maximum of 255 to keep memory usage low. /// Moreover, it's rarely advantageous to create a predicate using offsets /// greater than 255 anyway. /// /// The only guarantee enforced on the pair of offsets is that they are not /// equivalent. It is not necessarily the case that `index1 < index2` for /// example. By convention, `index1` corresponds to the byte in the needle /// that is believed to be most the predictive. Note also that because of the /// requirement that the indices be both valid for the needle used to build /// the pair and not equal, it follows that a pair can only be constructed for /// needles with length at least 2. #[derive(Clone, Copy, Debug)] pubstruct Pair {
index1: u8,
index2: u8,
}
impl Pair { /// Create a new pair of offsets from the given needle. /// /// If a pair could not be created (for example, if the needle is too /// short), then `None` is returned. /// /// This chooses the pair in the needle that is believed to be as /// predictive of an overall match of the needle as possible. #[inline] pubfn new(needle: &[u8]) -> Option<Pair> {
Pair::with_ranker(needle, DefaultFrequencyRank)
}
/// Create a new pair of offsets from the given needle and ranker. /// /// This permits the caller to choose a background frequency distribution /// with which bytes are selected. The idea is to select a pair of bytes /// that is believed to strongly predict a match in the haystack. This /// usually means selecting bytes that occur rarely in a haystack. /// /// If a pair could not be created (for example, if the needle is too /// short), then `None` is returned. #[inline] pubfn with_ranker<R: HeuristicFrequencyRank>(
needle: &[u8],
ranker: R,
) -> Option<Pair> { if needle.len() <= 1 { return None;
} // Find the rarest two bytes. We make them distinct indices by // construction. (The actual byte value may be the same in degenerate // cases, but that's OK.) let (mut rare1, mut index1) = (needle[0], 0); let (mut rare2, mut index2) = (needle[1], 1); if ranker.rank(rare2) < ranker.rank(rare1) {
core::mem::swap(&mut rare1, &mut rare2);
core::mem::swap(&mut index1, &mut index2);
} let max = usize::from(core::u8::MAX); for (i, &b) in needle.iter().enumerate().take(max).skip(2) { if ranker.rank(b) < ranker.rank(rare1) {
rare2 = rare1;
index2 = index1;
rare1 = b;
index1 = u8::try_from(i).unwrap();
} elseif b != rare1 && ranker.rank(b) < ranker.rank(rare2) {
rare2 = b;
index2 = u8::try_from(i).unwrap();
}
} // While not strictly required for how a Pair is normally used, we // really don't want these to be equivalent. If they were, it would // reduce the effectiveness of candidate searching using these rare // bytes by increasing the rate of false positives.
assert_ne!(index1, index2);
Some(Pair { index1, index2 })
}
/// Create a new pair using the offsets given for the needle given. /// /// This bypasses any sort of heuristic process for choosing the offsets /// and permits the caller to choose the offsets themselves. /// /// Indices are limited to valid `u8` values so that a `Pair` uses less /// memory. It is not possible to create a `Pair` with offsets bigger than /// `u8::MAX`. It's likely that such a thing is not needed, but if it is, /// it's suggested to build your own bespoke algorithm because you're /// likely working on a very niche case. (File an issue if this suggestion /// does not make sense to you.) /// /// If a pair could not be created (for example, if the needle is too /// short), then `None` is returned. #[inline] pubfn with_indices(
needle: &[u8],
index1: u8,
index2: u8,
) -> Option<Pair> { // While not strictly required for how a Pair is normally used, we // really don't want these to be equivalent. If they were, it would // reduce the effectiveness of candidate searching using these rare // bytes by increasing the rate of false positives. if index1 == index2 { return None;
} // Similarly, invalid indices means the Pair is invalid too. if usize::from(index1) >= needle.len() { return None;
} if usize::from(index2) >= needle.len() { return None;
}
Some(Pair { index1, index2 })
}
/// Returns the first offset of the pair. #[inline] pubfn index1(&self) -> u8 { self.index1
}
/// Returns the second offset of the pair. #[inline] pubfn index2(&self) -> u8 { self.index2
}
}
/// This trait allows the user to customize the heuristic used to determine the /// relative frequency of a given byte in the dataset being searched. /// /// The use of this trait can have a dramatic impact on performance depending /// on the type of data being searched. The details of why are explained in the /// docs of [`crate::memmem::Prefilter`]. To summarize, the core algorithm uses /// a prefilter to quickly identify candidate matches that are later verified /// more slowly. This prefilter is implemented in terms of trying to find /// `rare` bytes at specific offsets that will occur less frequently in the /// dataset. While the concept of a `rare` byte is similar for most datasets, /// there are some specific datasets (like binary executables) that have /// dramatically different byte distributions. For these datasets customizing /// the byte frequency heuristic can have a massive impact on performance, and /// might even need to be done at runtime. /// /// The default implementation of `HeuristicFrequencyRank` reads from the /// static frequency table defined in `src/memmem/byte_frequencies.rs`. This /// is optimal for most inputs, so if you are unsure of the impact of using a /// custom `HeuristicFrequencyRank` you should probably just use the default. /// /// # Example /// /// ``` /// use memchr::{ /// arch::all::packedpair::HeuristicFrequencyRank, /// memmem::FinderBuilder, /// }; /// /// /// A byte-frequency table that is good for scanning binary executables. /// struct Binary; /// /// impl HeuristicFrequencyRank for Binary { /// fn rank(&self, byte: u8) -> u8 { /// const TABLE: [u8; 256] = [ /// 255, 128, 61, 43, 50, 41, 27, 28, 57, 15, 21, 13, 24, 17, 17, /// 89, 58, 16, 11, 7, 14, 23, 7, 6, 24, 9, 6, 5, 9, 4, 7, 16, /// 68, 11, 9, 6, 88, 7, 4, 4, 23, 9, 4, 8, 8, 5, 10, 4, 30, 11, /// 9, 24, 11, 5, 5, 5, 19, 11, 6, 17, 9, 9, 6, 8, /// 48, 58, 11, 14, 53, 40, 9, 9, 254, 35, 3, 6, 52, 23, 6, 6, 27, /// 4, 7, 11, 14, 13, 10, 11, 11, 5, 2, 10, 16, 12, 6, 19, /// 19, 20, 5, 14, 16, 31, 19, 7, 14, 20, 4, 4, 19, 8, 18, 20, 24, /// 1, 25, 19, 58, 29, 10, 5, 15, 20, 2, 2, 9, 4, 3, 5, /// 51, 11, 4, 53, 23, 39, 6, 4, 13, 81, 4, 186, 5, 67, 3, 2, 15, /// 0, 0, 1, 3, 2, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, /// 12, 2, 1, 1, 3, 1, 1, 1, 6, 1, 2, 1, 3, 1, 1, 2, 9, 1, 1, 0, /// 2, 2, 4, 4, 11, 6, 7, 3, 6, 9, 4, 5, /// 46, 18, 8, 18, 17, 3, 8, 20, 16, 10, 3, 7, 175, 4, 6, 7, 13, /// 3, 7, 3, 3, 1, 3, 3, 10, 3, 1, 5, 2, 0, 1, 2, /// 16, 3, 5, 1, 6, 1, 1, 2, 58, 20, 3, 14, 12, 2, 1, 3, 16, 3, 5, /// 8, 3, 1, 8, 6, 17, 6, 5, 3, 8, 6, 13, 175, /// ]; /// TABLE[byte as usize] /// } /// } /// // Create a new finder with the custom heuristic. /// let finder = FinderBuilder::new() /// .build_forward_with_ranker(Binary, b"\x00\x00\xdd\xdd"); /// // Find needle with custom heuristic. /// assert!(finder.find(b"\x00\x00\x00\xdd\xdd").is_some()); /// ``` pubtrait HeuristicFrequencyRank { /// Return the heuristic frequency rank of the given byte. A lower rank /// means the byte is believed to occur less frequently in the haystack. /// /// Some uses of this heuristic may treat arbitrary absolute rank values as /// significant. For example, an implementation detail in this crate may /// determine that heuristic prefilters are inappropriate if every byte in /// the needle has a "high" rank. fn rank(&self, byte: u8) -> u8;
}
/// The default byte frequency heuristic that is good for most haystacks. pub(crate) struct DefaultFrequencyRank;
/// This permits passing any implementation of `HeuristicFrequencyRank` as a /// borrowed version of itself. impl<'a, R> HeuristicFrequencyRank for &'a R where
R: HeuristicFrequencyRank,
{ fn rank(&self, byte: u8) -> u8 {
(**self).rank(byte)
}
}
#[cfg(test)] mod tests { usesuper::*;
#[test] fn forward_packedpair() { fn find(
haystack: &[u8],
needle: &[u8],
_index1: u8,
_index2: u8,
) -> Option<Option<usize>> { // We ignore the index positions requested since it winds up making // this test too slow overall. let f = Finder::new(needle)?;
Some(f.find_prefilter(haystack))
} crate::tests::packedpair::Runner::new().fwd(find).run()
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-23)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.