NAME
Text::Prefix::XS - Fast prefix searching
SYNOPSIS
use Text::Prefix::XS;
my @haystacks = qw(
garbage
blarrgh
FOO
meh
AA-ggrr
AB-hi!
);
my @needles = qw(AAA AB FOO FOO-BAR);
my $search = prefix_search_create( map uc($_), @needles );
my %seen_hash;
foreach my $haystack (@haystacks) {
if(my $prefix = prefix_search($search, $haystack)) {
$seen_hash{$prefix}++;
}
}
$seen_hash{'FOO'} == 1;
#Compare to:
my $re = join('|', map quotemeta $_, @needles);
$re = qr/^($re)/;
foreach my $haystack (@haystacks) {
my ($match) = ($haystack =~ $re);
if($match) {
$seen_hash{$match}++;
}
}
$seen_hash{'FOO'} == 1;
DESCRIPTION
This module implements something of an trie algorithm for matching (and extracting) prefixes from text strings.
A common application I had was to pre-filter lots and lots of text for a small amount of preset prefixes.
Interestingly enough, the quickest solution until I wrote this module was to use a large regular expression (as in the synopsis)
FUNCTIONS
The interface is relatively simple. This is alpha software and the API is subject to change
prefix_search_create(@prefixes)
Create an opaque prefix search handle. It returns a thingy, which you should keep around.
Internally it will order the elements in the list, with the longest prefix being first.
It will then construct a search trie using a variety of caching and lookup layers.
prefix_search($thingy, $haystack)
Will check $haystack
for any of the prefixes in @needles
passed to "prefix_search_create". If $haystack
has a prefix, it will be returned by this function; otherwise, the return value is undef
PERFORMANCE
In most normal use cases, Text::Prefix::XS
will outperform any other module or search algorithm.
Specifically, this module is intended for a pessimistic search mechanism, where most of the input is assumed not to match (which is usually the case anyway).
The ideal position of Text::Prefix::XS
would reside between raw but delimited user input, and more complex searching and processing algorithms. This module acts as a layer between those.
In addition to a trie, this module also uses a very fast sparse array to check characters in the input against an index of known characters at the given position. This is much quicker than a hash lookup.
See the trie.pl
script included with this distribution for detailed benchmark comparison methods
Here are a bunch of numbers. The entries are in the format of
[capture (Y/N)] NAME DURATION MATCHES
Where capture
means whether the test was also able to return the prefix which matched. MATCHES
is the amount of matches returned.
Additionally, each test has a few parameters defining the input. These are:
TERMS
-
The amount of search terms
TERM_MIN
-
The minimum length of a term
TERM_MAX
-
The maximum length of a term
- INPUT
-
The count of input strings which will be checked to see if they are prefixed with any of the
TERMS
. The strings are each exactly one character longer thanTERM_MAX
Sample input is taken by making a sha1_hex
string of each number from 0 until TERMS
, and then encoding that output into Base64, ensuring that both the terms and the input get a diversity of the ASCII charset.
A few methods were benchmarked, and are listed as keys:
Perl-Trie
-
A generic implementation of a search trie in pure perl
TMFA
-
Text::Match::FastAlternatives
match_at
function perl-re
-
Generic perl regex. The capturing version is
qr/^(term1|term2)/
, and the non-capturing version isqr/^(?:term1|term2)/
, where the terms are joined together in alist2re
fashion. RE2
-
Same as
perl-re
, except using re::engine::RE2 TXS
-
This module.
Generated INPUT=2000000 TERMS=20 TERM_MIN=3 TERM_MAX=6
CAP NAME DUR MATCH
[Y] Perl-Trie 2.42s M=34768
[N] TMFA 1.11s M=34768
[N] perl-re 1.27s M=34768
[N] RE2 0.96s M=34768
[Y] perl-re 1.45s M=34768
[Y] RE2 2.95s M=34768
[Y] TXS 0.53s M=34768
Generated INPUT=2000000 TERMS=50 TERM_MIN=10 TERM_MAX=16
CAP NAME DUR MATCH
[Y] Perl-Trie 2.32s M=50
[N] TMFA 1.07s M=50
[N] perl-re 1.27s M=50
[N] RE2 0.93s M=50
[Y] perl-re 1.47s M=50
[Y] RE2 1.14s M=50
[Y] TXS 0.55s M=50
Generated INPUT=2000000 TERMS=49 TERM_MIN=2 TERM_MAX=16
CAP NAME DUR MATCH
[Y] Perl-Trie 17.70s M=420699
[N] TMFA 1.10s M=420699
[N] perl-re 1.35s M=420699
[N] RE2 0.97s M=420699
[Y] perl-re 1.70s M=420699
[Y] RE2 4.98s M=420699
[Y] TXS 1.62s M=420699
Generated INPUT=2000000 TERMS=10 TERM_MIN=5 TERM_MAX=10
CAP NAME DUR MATCH
[Y] Perl-Trie 1.99s M=265
[N] TMFA 1.07s M=265
[N] perl-re 1.20s M=265
[N] RE2 0.90s M=265
[Y] perl-re 1.43s M=265
[Y] RE2 2.70s M=265
[Y] TXS 0.45s M=265
Generated INPUT=2000000 TERMS=100 TERM_MIN=3 TERM_MAX=25
CAP NAME DUR MATCH
[Y] Perl-Trie 10.35s M=22269
[N] TMFA 1.15s M=22269
[N] perl-re 1.40s M=22269
[N] RE2 1.06s M=22269
[Y] perl-re 1.58s M=22269
[Y] RE2 2.06s M=22269
[Y] TXS 1.10s M=22269
Generated INPUT=2000000 TERMS=200 TERM_MIN=5 TERM_MAX=25
CAP NAME DUR MATCH
[Y] Perl-Trie 3.81s M=1325
[N] TMFA 1.16s M=1325
[N] perl-re 1.30s M=1325
[N] RE2 1.07s M=1325
[Y] perl-re 1.56s M=1325
[Y] RE2 1.38s M=1325
[Y] TXS 0.63s M=1325
Generated INPUT=2000000 TERMS=8 TERM_MIN=2 TERM_MAX=5
CAP NAME DUR MATCH
[Y] Perl-Trie 1.79s M=22168
[N] TMFA 1.15s M=22168
[N] perl-re 1.26s M=22168
[N] RE2 1.01s M=22168
[Y] perl-re 1.56s M=22168
[Y] RE2 2.48s M=22168
[Y] TXS 0.49s M=22168
I've mainly tested this on Debian's 5.10 - for newer perls, this module performs better, and for el5 5.8, The differences are a bit lower. TBC
SEE ALSO
There are quite a few modules out there which aim for a Trie-like search, but they are all either not written in C, or would not be performant enough for this application.
These two modules are implemented in pure perl, and are not part of the comparison.
CAVEATS
I have yet to figure out a way to test this properly with threads. Currently the trie data structure is stored as a private perl HV
, and I'm not sure what happens when it's cloned across threads.
This algorithm performs quite poorly when matches are more likely. HOWEVER, in the case where there is a desire to extract the matched prefix, the overhead in doing so with Regular Expressions outweighs the performance hit of Text::Prefix::XS
, making Text::Prefix::XS
still effectively faster.
Search prefixes and search input is currently restricted to printable ASCII characters
Search terms may not exceed 256 characters. You can increase this limit (at the cost of more memory) by changing the #define
of CHARTABLE_MAX
in the XS code and recompiling.
AUTHOR AND COPYRIGHT
Copyright (C) 2011 M. Nunberg
You may use and distribute this software under the same terms, conditions, and licensing as Perl itself.