Why GNU grep is Fast
lists.freebsd.org
lists.freebsd.org
But Boyer-Moore is the perfect choice for Grep! The likely inputs on Unix are all huge: log files, entire directory trees, output pipes from loud programs, etc. The cost of initializing a small skip table is overwhelmed by the cost of I/O and the potential volume of text. It's not surprising that they've gone to some lengths to optimize the core inner loops and the I/O in that context.
[0] http://www.jakevoytko.com/blog/2007/12/11/fun-with-string-se... I've declared bankruptcy on broken TeX and code examples... WordPress mangles them every few updates.
[1] http://www.lysium.de/blog/index.php?/archives/201-Fun-With-S... A few improvements to the code in my post
will do the job.
I think that line of thinking is actually symptomatic of producing the kind of software that eats up our present day powerhouses and makes them dog slow.
A HTTP header is several orders of magnitude shorter than Moby Dick.
> "Even long-winded users won't write Moby Dick into your <textarea>"
Which has most cases already much longer than those where the setup costs of BM are larger than the gain if the match is found on average halfway in to the text.
Typically you hit that point when the 'haystack' is about 2,000 characters and the 'needle' is longer than about 4 to 5, longer 'needles' or longer 'haystacks' would increase the advantage.
So the HTTP header one is probably one situation where you'd be quicker using brute force but in those other two instances it is very well possible that BM is already faster.
And anyway, what are you doing searching HTTP headers in anything more than a one-off script? More likely, you are parsing the whole header and sticking it in a hash table. So, not only aren't you searching, but even if you were, that's not the hard part. And even that is dwarfed by the application that's going to service the HTTP request. (Unless you are Google, in which case you don't need my advice.)
Searching HTTP headers is not your bottleneck. Use your language's built in string search. Premature optimization makes code slower.
I happen to agree with your preference, but most people don't seem to.
I'd prefer it your way too. One other thing, we're assuming good programmers and that's not something I'd bet on at most places.
A much better solution is to write things in the easiest way for coders to change - that way, when something is found to be the actual cause of slowness, anyone can easily go in and optimise it or move it to a background thread. Optimising EVERYTHING in the hopes of obtaining speed is a fool's errand - due to the 90/10 rule, 90% of the code you optimise will never be the bottleneck.
-- Ayjay on Fedang/coding
I remember this topic being discussed back in undergrad.
The most interesting thing is that most pattern matching algorithms up to then got slower with longer match strings, but Boyer-Moore actually got faster!
To quote Majikthise: "Bloody hell, now that is what I call thinking."...
Another interesting bit of trivia: In the first chapter of my thesis I present a string matching algorithm with almost exactly the same asymptotic running time as BM -- but where BM performs exact matching using no precomputed index, my algorithm performs matching with mismatches using an index.
With an index, of course, exact matching is O(log N) time -- in a peculiar way, the "cost" of inexact matching is one index worth of efficiency.
[EDIT: On second thought, this last comment meaningful at all? I'm not sure, but it's almost 4AM so I'm not going to figure it out now.]
I'd like to read that.
Yes, http://www.daemonology.net/papers/thesis.pdf
I'd like to read that.
I recommend skipping the proofs. They're not very informative and the calculus gets rather tedious.
> I recommend skipping the proofs. They're not very informative and the calculus gets rather tedious.
And besides that is well over my head anyway, but I should be able to follow your main line of reasoning.
"If a mathematician is a machine for turning coffee into theorems, a computer scientist is a machine for converting caffeine into algorithms."
For anyone else who skimmed the article and doesn't know the algorithm, check it out at http://en.wikipedia.org/wiki/Boyer-Moore. It's a very quick read to get the basic idea, and it's as good as jacquesm cracks it up to be.
http://www.ics.uci.edu/~goodrich/dsa/11strings/demos/pattern...
http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_sear...
What I wouldn't give to infuse that sense in to present day programmers. (writing this on a box with 12G ram, pretty much maxed out).
http://www.gnu.org/prep/standards/html_node/Reading-Non_002d...
Space and speed will always be conflicting goals during optimizations (unless you're very lucky), and for practical use the sweet spot is usually somewhere in between the two extremes.
To blindly optimize for speed will result in very wasteful behaviour when it comes to memory usage, to blindly optimize for space will result in terrible performance.
Smart software realizes when the space is available and free for the taking and will use it to find an increase in speed, it will also realize when space is at a premium and economize on it's usage.
On another note, accessing all that memory costs cycles and is almost certainly going to trash your cache, chances are that if you manage to reduce your memory footprint eventually you'll find that instead of losing speed you're gaining speed.
Open Office started up with a blank word processor document uses 83 Megs of RAM, something tells me that could be a whole lot less without sacrificing functionality or speed.
One of the more major reasons of GNU developing the habit of writing utilities that were significantly different in their implementation was to make sure no UNIX/BSD code could leak in, since at the time the project was underway, BSD was stuck in that whole USL vs BSDi suit, and BSD's code was tentatively deemed 'non-free'.
One also needs to account for the time period these utilities were written, and for what computer system they were originally written for. Both memory and speed were expensive. Writing these utilities probably required much more thought to the smaller details than one would think about today when writing say a word processor, even though one could benefit by doing so.
Serious question: what are you using 12G of RAM on? My workstation is pretty modest (core 2 duo, 4G RAM) and I'm rarely able to utilize more than 1G.
I'm not even testing stuff with VMs right now, as soon as I get in to that I have to shut down the IDE at a minimum or I'll hit the swap.
I also know people who work with fluid dynamics and the new workstations they bought for work have 48 gigs of RAM.
I'm not sure that either video editing or fluid dynamics count as bloatware in my book, because the problems are inherently complex. Word processing, however...
I haven't tried a lot of video editing software today (since I no longer pirate software like I used to), but what I have used is not huge progress from what I did in the late 90s. The only big step is that you can work with compressed streams directly, which is nice, but expected since even a modest machine today is expected to be able to decompress the latest MPEG spec in realtime.
More info: http://www.linuxatemyram.com/index.html
That's about it.
(BM is especially cute because the basic idea is so simple: do it backwards. Quicksort is very clever. Arithmetic coding is mindbendingly cool, but the algorithm isn't simple enough - for this old brain anyway.)
But BM stands out for me because it is taking the opposite approach where that was entirely non-obvious in spite of lots of people having looked at that problem for a very long time (and plenty of those people were anything but stupid).
> The key to making programs fast is
> to make them do practically nothing.
> ;-)
is a paraphrase of something I posted here a long time ago: > You can't make programs run faster,
> you can only make them do less.
While not entirely true (and Ph.D. theses have been written about the corners where it's wrong) it's an excellent start when you have to make a program run faster.One counter-example I can think of is that of Judy trees - http://judy.sourceforge.net/ which uses more instructions to try to keep the data in cache for as long as possible.
A (CPU) cache-line fill is additional time required to do a read reference from RAM when a word is not found in cache. In today's computers the time for a cache-line fill is in the range of 50..2000 machine instructions. Therefore a cache-line fill should be avoided when fewer than 50 instructions can do the same job.
More info at http://judy.sourceforge.net/doc/10minutes.htm
It's not a counter-example. The cost of running a program includes the cost to access memory as well as the cost of executing instructions. It also includes the cost to access disk/flash.
If you think Boyer-Moore is a trip, check out Self-Tuning Boyer-Moore. There's actually a really good writeup on it at http://www.grouse.com.au/ggrep/string.html
I'm curious, though, about what tricks are used in actual implementations to speed things up, and what modern Regex features necessitate climbing further up the Chomsky Hierarchy. (I seem to recall reading about features getting slipped into Regex engines that made them no longer finite state, but can't recall what they were, right now.)
Check out Shift-Or: used in agrep, and another example of a very clever algorithm. Rather than translating a non-deterministic finite state automata to a deterministic one, it uses the boolean operations of the hardware to simulate the NFA directly. Result: linear time regexp for patterns that have less than the bit-length of a machine's registers. I.e., 32 bytes on x86, 64 bytes on x86_64, etc.
Given a regex such as /foo.*bar/ grep can search on 'foo' and then apply the Boyer-Moore algorithm. (http://lists.freebsd.org/pipermail/freebsd-current/2010-Augu...).
Given a regex with multiple fixed-strings the longest will be used. (http://lists.freebsd.org/pipermail/freebsd-current/2010-Augu...)
In a prefect world, I would prefer many programs that did nothing and worked together, than a monolithic program that does anything (and even contains a kitchen sink!) but is slow.
So come on fellow developers, let's make a bunch of nothing!
Quoted from James Gosling, http://nighthacks.org/roller/jag/entry/quite_the_firestorm
Also, kudo's to the 15 years maintainer of GNU grep.
I think that's usually described as "the Unix philosophy." It's not limited to GNU (nor does it originate with GNU). See, for example, the Wikipedia article on "Unix philosophy"[1]:
Doug McIlroy, the inventor of Unix pipes and one of the founders of the Unix tradition, summarized the philosophy as follows:[2]
This is the Unix philosophy: Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
This is usually abridged to "Write programs that do one thing and do it well".
[1]http://en.wikipedia.org/wiki/Unix_philosophy#McIlroy:_A_Quar...
Why shouldn't 'grep' be automatically available as a routine once programmed?
I can see some of the charm of 'images' such as used by smalltalk.
Or maybe the appeal of glue languages like Bash or (one style of) Perl. It is very nice to have the ability to treat arbitrary programs like libraries for your program. (Unfortunately, because of the GNU/BSD split - among other things - this style of programming is also completely brittle. One non-standard flag, and boom.)
But that's not 'natural', that's an external process with a whole pile of start-up and shut-down overhead. Incidentally, some of the worst C code I've ever seen used piped unix shell commands all over the place, as if there is no penalty to doing this.
An external program communicating over pipes can let the OS handle buffering, run on another process core, be swapped out for another program that speaks the same protocol (perhaps in a faster language), won't crash the whole system, etc.
A program running as a library subroutine has a bit less overhead, and can use more context (library-native data structures, rather than piped text), but this is also usually more language-specific. Working within a "full environment" language like Smalltalk has a lot of advantages, but it also needs comprehensive libraries for your problem domain, or you're back to using external programs.
There's an insightful aside about this in Joe Armstrong's _Programming Erlang_, in the chapter about ports - Erlang code can load foreign code as linked-in libraries, but a buggy library will make the whole system unstable in a way that code running in a foreign process and communicating via message passing will not. He argues for running code in an external process (a "port") by default.
Of course, having a comprehensive (but low-level) library in C with wrappers in higher-level languages is an option. High-level languages' type systems / object models can be very different, though, and it takes experience to translate a C API to feel native to Python/Lua/Ruby/etc.
It also works to structure a program as a C library, but provide a small standalone program which gives it a command line interface. SQLite and Lua are good examples of the latter approach.
You could do this for a given function signature, perhaps. But it's not clear how it would work in the general case. Is there some universal function signature that makes sense for any kind of API, that makes sense both for pipes and for the semantics of the specific program?
Interesting to think about, though.
That would be one way to do it, but system calls are generally assumed to be atomic, promoting them to process status would be quite tricky.
Multi-threaded kernels effectively do some of this by allowing you to execute multiple system calls in parallel.
The typical way in which an 'image' based system works is that all your persistence (including compiled code) continues to reside in the image, over time you get the same kind of build-up that you typically see in file systems, only there there is no difference between 'programs' and 'subroutines'.
That gives the best of both worlds. I have my usable program. And later I can easily integrate it into any other library that can benefit.
One particular area where the external program bit is annoying is when you're querying or setting system/package parameters or defaults on a unix machine, there must be 100 incompatible ways of doing that.
Usually the only way to get the job done is to escape to a shell, which really feels kludgy.
"What... is your name?" "Sir Brian of Bell." "What... is your quest?" "I seek the Holy Grail." "What... are four lowercase letters that are not legal flag arguments to the Berkeley UNIX version of `ls'?" "I, er.... AIIIEEEEEE!"
( Starts at 34m10s -- http://twit.tv/sn203 )
#!/usr/local/plan9/bin/rc
fn 9grep { /usr/local/plan9/bin/grep '2010-[0-9][0-9]-23 02:01:57' /home/maht/lighttpd.error.log > /dev/null }
fn ggrep { /usr/bin/grep '2010-[0-9][0-9]-23 02:01:57' /home/maht/lighttpd.error.log > /dev/null }
fn mgrep { /usr/bin/grep -mmap '2010-[0-9][0-9]-23 02:01:57' /home/maht/lighttpd.error.log > /dev/null }
switch($1) {
case -9
9grep
case -g
ggrep
case -m
mgrep
case *
ls -l /home/maht/lighttpd.error.log
time /tmp/gtest -9
time /tmp/gtest -g
time /tmp/gtest -m
}
/tmp/gtest
-rw-r--r-- 1 www wheel 1113325534 Aug 23 18:51 /home/maht/lighttpd.error.log
23.67 real 3.88 user 3.74 sys
24.28 real 0.63 user 3.89 sys
23.09 real 0.56 user 3.87 sysI wouldn't call that "competitive", even if system-call overheads does knock that crippling slowdown down to less than a factor of two.
Also, what kind of kernel are you running there that would peg your CPU with a mere 300 megabytes per second of disk I/O? Is DMA disabled on your disk or something? Surely not, because there aren't any IDE PIO modes that are anywhere close to 50 megabytes per second.