Skip to content

Commit

Permalink
Add SIMD accelerated multiple pattern search.
Browse files Browse the repository at this point in the history
This uses the "Teddy" algorithm, as learned from the Hyperscan regular
expression library: https://01.org/hyperscan

This support optional, subject to the following:

1. A nightly compiler.
2. Enabling the `simd-accel` feature.
3. Adding `RUSTFLAGS="-C target-feature=+ssse3"` when compiling.
  • Loading branch information
BurntSushi committed May 18, 2016
1 parent 426e131 commit 203c509
Show file tree
Hide file tree
Showing 12 changed files with 879 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ script:
- cargo build --verbose
- cargo build --verbose --manifest-path=regex-debug/Cargo.toml
- if [ "$TRAVIS_RUST_VERSION" = "nightly" ]; then
travis_wait cargo test --verbose --features pattern;
RUSTFLAGS="-C target-feature=+ssse3" cargo test --verbose --features 'simd-accel pattern';
else
travis_wait cargo test --verbose;
fi
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ memchr = "0.1.9"
thread_local = "0.2.4"
# For parsing regular expressions.
regex-syntax = { path = "regex-syntax", version = "0.3.1" }
# For accelerating text search.
simd = { version = "0.1.0", optional = true }
# For compiling UTF-8 decoding into automata.
utf8-ranges = "0.1.3"

Expand All @@ -35,6 +37,8 @@ rand = "0.3"
[features]
# Enable to use the unstable pattern traits defined in std.
pattern = []
# Enable to use simd acceleration.
simd-accel = ["simd"]

[lib]
# There are no benchmarks in the library code itself
Expand Down
2 changes: 1 addition & 1 deletion bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ libc = "0.2"
onig = { version = "0.4", optional = true }
libpcre-sys = { version = "0.2", optional = true }
memmap = "0.2"
regex = { version = "0.1", path = ".." }
regex = { version = "0.1", path = "..", features = ["simd-accel"] }
regex_macros = { version = "0.1", path = "../regex_macros", optional = true }
regex-syntax = { version = "0.3", path = "../regex-syntax" }
rustc-serialize = "0.3"
Expand Down
3 changes: 3 additions & 0 deletions bench/compile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/sh

# Enable SIMD.
export RUSTFLAGS="-C target-feature=+ssse3"

exec cargo build \
--release \
--features 're-onig re-pcre1 re-pcre2 re-re2 re-rust re-rust-bytes re-tcl' \
Expand Down
3 changes: 3 additions & 0 deletions bench/run
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ if [ $# = 0 ] || [ $1 = '-h' ] || [ $1 = '--help' ]; then
usage
fi

# Enable SIMD.
export RUSTFLAGS="-C target-feature=+ssse3"

which="$1"
shift
case $which in
Expand Down
1 change: 0 additions & 1 deletion regex-syntax/src/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
// except according to those terms.

use quickcheck::{Arbitrary, Gen, Testable, QuickCheck, StdGen};
use rand::Rng;

use {
Expr, ExprBuilder,
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(feature = "pattern", feature(pattern))]
#![cfg_attr(feature = "simd-accel", feature(cfg_target_feature))]
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://doc.rust-lang.org/regex/")]
Expand All @@ -458,6 +459,7 @@ extern crate memchr;
extern crate thread_local;
#[cfg(test)] extern crate quickcheck;
extern crate regex_syntax as syntax;
#[cfg(feature = "simd-accel")] extern crate simd;
extern crate utf8_ranges;

pub use error::Error;
Expand Down Expand Up @@ -582,6 +584,11 @@ mod re_plugin;
mod re_set;
mod re_trait;
mod re_unicode;
#[cfg(feature = "simd-accel")]
mod simd_accel;
#[cfg(not(feature = "simd-accel"))]
#[path = "simd_fallback/mod.rs"]
mod simd_accel;
mod sparse;

/// The `internal` module exists to support the `regex!` macro and other
Expand Down
50 changes: 40 additions & 10 deletions src/literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use memchr::{memchr, memchr2, memchr3};
use syntax;

use freqs::BYTE_FREQUENCIES;
use simd_accel::teddy128::Teddy;

/// A prefix extracted from a compiled regular expression.
///
Expand Down Expand Up @@ -51,6 +52,8 @@ enum Matcher {
Single(SingleSearch),
/// An Aho-Corasick automaton.
AC(FullAcAutomaton<syntax::Lit>),
/// A simd accelerated multiple string matcher.
Teddy128(Teddy),
}

impl LiteralSearcher {
Expand Down Expand Up @@ -100,6 +103,7 @@ impl LiteralSearcher {
Bytes(ref sset) => sset.find(haystack).map(|i| (i, i + 1)),
Single(ref s) => s.find(haystack).map(|i| (i, i + s.len())),
AC(ref aut) => aut.find(haystack).next().map(|m| (m.start, m.end)),
Teddy128(ref ted) => ted.find(haystack).map(|m| (m.start, m.end)),
}
}

Expand Down Expand Up @@ -136,6 +140,9 @@ impl LiteralSearcher {
Matcher::Bytes(ref sset) => LiteralIter::Bytes(&sset.dense),
Matcher::Single(ref s) => LiteralIter::Single(&s.pat),
Matcher::AC(ref ac) => LiteralIter::AC(ac.patterns()),
Matcher::Teddy128(ref ted) => {
LiteralIter::Teddy128(ted.patterns())
}
}
}

Expand All @@ -162,6 +169,7 @@ impl LiteralSearcher {
Bytes(ref sset) => sset.dense.len(),
Single(_) => 1,
AC(ref aut) => aut.len(),
Teddy128(ref ted) => ted.len(),
}
}

Expand All @@ -173,6 +181,7 @@ impl LiteralSearcher {
Bytes(ref sset) => sset.approximate_size(),
Single(ref single) => single.approximate_size(),
AC(ref aut) => aut.heap_bytes(),
Teddy128(ref ted) => ted.approximate_size(),
}
}
}
Expand All @@ -190,23 +199,34 @@ impl Matcher {

fn new(lits: &syntax::Literals, sset: SingleByteSet) -> Self {
if lits.literals().is_empty() {
Matcher::Empty
} else if sset.dense.len() >= 26 {
return Matcher::Empty;
}
if sset.dense.len() >= 26 {
// Avoid trying to match a large number of single bytes.
// This is *very* sensitive to a frequency analysis comparison
// between the bytes in sset and the composition of the haystack.
// No matter the size of sset, if its members all are rare in the
// haystack, then it'd be worth using it. How to tune this... IDK.
// ---AG
Matcher::Empty
} else if sset.complete {
Matcher::Bytes(sset)
} else if lits.literals().len() == 1 {
Matcher::Single(SingleSearch::new(lits.literals()[0].to_vec()))
} else {
let pats = lits.literals().to_owned();
Matcher::AC(AcAutomaton::new(pats).into_full())
return Matcher::Empty;
}
if sset.complete {
return Matcher::Bytes(sset);
}
if lits.literals().len() == 1 {
let lit = lits.literals()[0].to_vec();
return Matcher::Single(SingleSearch::new(lit));
}
// Only try Teddy if Aho-Corasick can't use memchr.
// Also, in its current form, Teddy doesn't scale well to lots of
// literals.
if sset.dense.len() > 1 && lits.literals().len() <= 32 {
if let Some(ted) = Teddy::new(lits) {
return Matcher::Teddy128(ted);
}
}
let pats = lits.literals().to_owned();
Matcher::AC(AcAutomaton::new(pats).into_full())
}
}

Expand All @@ -215,6 +235,7 @@ pub enum LiteralIter<'a> {
Bytes(&'a [u8]),
Single(&'a [u8]),
AC(&'a [syntax::Lit]),
Teddy128(&'a [Vec<u8>]),
}

impl<'a> Iterator for LiteralIter<'a> {
Expand Down Expand Up @@ -250,6 +271,15 @@ impl<'a> Iterator for LiteralIter<'a> {
Some(&**next)
}
}
LiteralIter::Teddy128(ref mut lits) => {
if lits.is_empty() {
None
} else {
let next = &lits[0];
*lits = &lits[1..];
Some(&**next)
}
}
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/simd_accel/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[cfg(target_feature = "ssse3")]
pub mod teddy128;
#[cfg(not(target_feature = "ssse3"))]
#[path = "../simd_fallback/teddy128.rs"]
pub mod teddy128;
Loading

0 comments on commit 203c509

Please sign in to comment.