breaking up regular expressions

Adam Rice <[email protected]> Tue, 8 Jul 2003 21:28:01 +0100
Newsgroups gmane.network.ubh
Message-ID <[email protected]>
If you're like me, you probably have a line in your ubhrc like this:

OPT_X = stupid|boring|offensive

and if you're like me, it probably gets longer and longer and makes
inclusion/exclusion processing take a very long time.

It turns out that in Perl, splitting it up into three regular expressions
makes it a lot faster. ie.

if (/stupid/ || /boring/ || /offensive/) { ... }

runs over ten times faster than

if (/stupid|boring|offensive/) { ... }

and has the same result. To demonstrate this, I've attached a short benchmark
program. Run it with a UBH2 cache file as the command-line argument.

Here's my results:
Benchmark: timing 20 iterations of plain, split, splitstudy...
     plain: 25 wallclock secs (23.26 usr +  0.27 sys = 23.53 CPU) @  0.85/s (n=20)
     split:  2 wallclock secs ( 1.82 usr +  0.01 sys =  1.83 CPU) @ 10.93/s (n=20)
splitstudy:  4 wallclock secs ( 3.46 usr +  0.06 sys =  3.52 CPU) @  5.68/s (n=20)

I ran it with a cache file around 9M in size--if your cache file is a lot
smaller or bigger, you'll need to change the number of iterations.

You can of course modify this benchmark to use your actual OPT_X or OPT_I
regular expression so you can see how much breaking it up would benefit you.

The benchmark also tests the effect of using study(); I found it gave around
4% benefit for my own regular expression when broken up, but makes things a
lot slower when there's only a few strings to match.

The problem, of course, is that there's no way to specify a broken-up regular
expression in the config file. So I'm posting to gather some ideas before I go
ahead and hack up my own implementation.

Here are my ideas:

a) Automatically break regular expressions where we see a | that is not within
brackets. This would be easiest for users if it worked, but I think it would
go wrong a lot of the time.

OPT_X = stupid|boring|offensive

or b) Break regular expressions if the user puts || inside them. This would
mean existing config files would continue to work, but users could get a speed
boost by modifying the expression.

OPT_X = stupid||boring||offensive

The downside is that it's a totally non-standard syntax, and there's a small
chance an existing regular expression could contain something like
\|stupid\||\[dumb\] and break.

or c) Change the syntax to be a Perl expression rather than a regular
expression, ie.

OPT_X = /stupid/ || /boring/ || /offensive/

This would mean everyone who's using OPT_X or OPT_I would have to change their
config files. It's also a lot more typing than b). The benefit is that it
looks very familiar to people who know Perl, and you could even do things
other than regexps, eg.

OPT_X = /\"(.*\.mp3)/i && -e "group/$1"

or if you found your expression benefitted from study:

OPT_X = (study&&0) || /stupid/ || /boring/ || /offensive/ || ...


I initially favoured option b), but the more I think about it, the more I
prefer c), since UBH 3 means everyone has to change their config anyway.

Regardless of the config file format, the simplest implementation (avoiding
using eval every time), would be to replace

            if ((defined $opt{'I'} && $title !~ /$opt{'I'}/o) ||
	                    (defined $opt{'X'} && $title =~ /$opt{'X'}/o)) {
			    
with

if (should_be_excluded($title)) {

and then generate the function 'should_be_excluded' at runtime using eval. The
disadvantage is that the function call itself has significant overhead. The
alternative is to replace the entire surrounding loop with a function
generated at runtime. That would be a lot faster, but make the code
considerably more complex and hard to maintain.

Since some people have been talking about moving the regular expressions into
SQL, I investigated this too. I discovered:

1) MySQL regexps are really ugly. It took me about 10 minutes to rewrite my
OPT_X expression for MySQL.
2) They are around 4 times slower than Perl's (even when Perl is getting the
data to match from MySQL!)
3) MySQL executes:

select count(*) from headers where subject regexp binary 'stupid' or subject
regexp binary 'boring' or subject regexp binary 'offensive';

around 5 times quicker than:

select count(*) from headers where subject regexp binary 'stupid|boring|offensive';

Adam

-- 
Adam Rice -- [email protected] -- Blackburn, Lancashire, England

------------------------ Yahoo! Groups Sponsor ---------------------~-->
Save up to 80% on top-quality inkjet cartridges and get your order fast!
FREE shipping on orders $50 or more to the US & Canada. Shop at Myinks.com!
http://www.c1tracking.com/l.asp?cid=5511
http://us.click.yahoo.com/v2G7ND/KfUGAA/ySSFAA/IHFolB/TM
---------------------------------------------------------------------~->

To unsubscribe from this group, send an email to:
[email protected]

 

Your use of Yahoo! Groups is subject to http://docs.yahoo.com/info/terms/
short-speed-test (text/plain, 451 B)
#!/usr/local/bin/perl -w

use strict;
use Benchmark;

my(@subjects);

while (<>) {
    chomp;
    s/^.*\|//;
    push(@subjects, $_);
}

timethese(20, {
	      "plain" => mek(q{/stupid|boring|offensive/}),
	      "split" => mek(q{/stupid/ || /boring/ || /offensive/}),
	      "splitstudy" => mek(q{(study&&0)||/stupid/ || /boring/ || /offensive/}),
	     });

sub mek {
    my($code) = @_;
    return eval "sub { scalar(grep($code, \@subjects)) }";
}