Why GNU grep is fast (2010)
lists.freebsd.org
lists.freebsd.org
https://news.ycombinator.com/item?id=1626305, 1193 days ago, 115 comments
https://news.ycombinator.com/item?id=2393587, 972 days ago, 68 comments
https://news.ycombinator.com/item?id=2860759, 842 days ago, 43 comments
( ionelm also pointed out a previous submission: https://news.ycombinator.com/item?id=6814153 )
Also worth mentioning is "The Treacherous Optimization" ( http://ridiculousfish.com/blog/posts/old-age-and-treachery.h... ), although previous submissions of that provoked no discussion at all.
https://news.ycombinator.com/item?id=1624402
https://news.ycombinator.com/item?id=5257874
ADDED IN EDIT: More rigorous searching has turned up substantial discussion of the Treacherous Optimization: https://news.ycombinator.com/item?id=1627367
I would also prefer to see this happen on every story.
Standard "wisdom" for start-ups includes "If you're not embarrassed by your first product then you didn't launch early enough," and "Try the minimal viable product before investing too much time." I both launched early, and made sure the "product" was absolutely minimal. It got slated by the people it was trying to help, so I didn't bother refining it.
The experience was educational and instructive.
(minor edit to remove some inappropriate snark - apologies)
http://swtch.com/~rsc/regexp/regexp1.html
http://swtch.com/~rsc/regexp/regexp2.html
http://swtch.com/~rsc/regexp/regexp3.html
http://en.wikipedia.org/wiki/Thompson%27s_construction_algor...
I implemented a sort of micro-grep in the past using the Thompson's algorithm, it was a great exercise in practical applications of heavily-theoretical CS stuff, with recursive-descent parsing of the regular expression, then using Thompsons algorithm to create a NFA out of the parse tree, then a simulation of an NFA with a DFA using two stacks. I used the Dragon Book for reference on this, all pretty awesome stuff.
There's nothing wrong with good ole grep, but on boxen I spend any significant time on, I always install ag. The integration with Emacs provided by ag.el[2] is awesome too!
[1] https://github.com/ggreer/the_silver_searcher#how-is-it-so-f...
The classic Boyer-Moore algorithm suffers from the phenomenon that it tends not to work so efficiently on small alphabets like DNA.
The skip distance tends to stop growing with the pattern length because substrings re-occur frequently.
By remembering more of what has already been matched, one can get larger skips through the text.
One can even arrange 'perfect memory' and thus look at each character at most once, whereas the Boyer-Moore algorithm, while linear, may inspect a character from the text multiple times.
1: http://stackoverflow.com/questions/12656160/what-are-the-mai...
[0] https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_sea...
[1]: http://www.cs.utexas.edu/users/moore/best-ideas/string-searc...
Also reminds me of Kent Beck's quip when he was asked to optimize Chrysler's C3 system. He asked for validated sets of input and output. The programmers on site said the the system wasn't producing correct results yet. His response: In that case, I can make this real fast!
Engineers: Oh your system can only do a thousand cards a minute? Ours can do ten thousand.
Weinberg: Yeah but your system doesn't work! If mine doesn't have to work I can do a million cards a minute.
I thought I got it from one of Weinberg's books.
I try hard, but I'm not smart enough to write programs that do nothing. :)
Haskell gives you some constructs that makes this easier but there exists no program whose Haskell implementations will run faster because of this, only programs that are easier to make fast.
That said, the downside is that it then becomes very difficult to reason about the performance characteristics of your library, because its performance depends on how it's used. Simon Peyton-Jones is on record as saying that laziness is probably the wrong default for a language - the big benefit for Haskell was that it "kept them honest" wrt purity, but in a production system you probably want strictness.
Good luck pulling this off in Chrome.
I see the same in some of our software. One of our senior is rolling out a number of very, very fast collections based on compare-and-swap-operations, but you always end up thinking an hour or two about five lines of code. Most problem domains don't need this kind of performance, so most software teams don't pay the maintenance price and I think they are correct about that judgement call.
$ touch foo
$ chmod +x foo
$ ./foo
$ echo $?
0 $ touch empty
$ chmod +x empty
$ time ./empty
real 0m0.002s
user 0m0.000s
sys 0m0.000s
$ echo "int main(){return 0;}" > trivial.c
$ gcc trivial.c -o trivial
$ time ./trivial
real 0m0.001s
user 0m0.000s
sys 0m0.000s
Timing results are consistent over several repetitions (provided everything's in cache from disk). Linux x86_64. `mov` takes ten thousand to a million times less than a millisecond ( https://gist.github.com/jboner/2841832 ), so I can't find out this way whether removing 'return 0' changes anything.(If I use my default zsh shell to execute ./empty, it gives me
zsh: exec format error: ./empty
./empty 0.00s user 0.00s system 0% cpu 0.008 total
So I used bash for this.)Comparing to all this, a move instruction in userland won't really make a difference.
Edit: List on system calls required to execute trivial.c (on my Linux):
execve, brk, access, mmap, access, open, open, open, open, open, stat, open, stat, open, stat, open, stat, open, fstat, mmap, close, access, open, read, fstat, mmap, mprotect, mmap, mmap, close, mmap, mmap, mmap, arch_prctl, mprotect, mprotect, munmap, exit_group
$ touch empty
$ chmod +x empty
$ time ./empty
real 0m0.005s
user 0m0.001s
sys 0m0.001s
$ echo "int main(){return 0;}" > trivial.c
$ gcc trivial.c -o trivial
$ time ./trivial
real 0m0.002s
user 0m0.001s
sys 0m0.002sThis is the normal pattern, just in case you forget to put "#!/bin/bash" at the top of the script, so that the script can be run anyway. This is also a source of confusion for some sysadmins, when a script works from the command line but not from something like a cron script.
Yeah, but try telling your boss that :)
You might not believe that these are real programs but they are. You can find the binaries using `whereis`. For example on my linux install true is /bin/true.
The command line is pretty awesome.
http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob_p...
/bin/true source is a bit longer than expected but /bin/false implementation is really interesting : http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob_p...
Also because "it's how it's done around here": the site is about news so it makes sense to at least tag possible re-posts to flag them as "not quite news yet still interesting and recommended reading".
But this story is actually an email (from 2010) written by the guy WHO ORIGINALLY WROTE GREP, making it a pretty damn interesting source in its own right. In that sense, the 1991 article is very much an aside.
In conclusion, no.
Just a pedantic remark: He (Mike Haertel) originally wrote GNU grep. Grep, on the other hand, was originally written by Ken Thompson.
Of course, you can also just reduce the Unicode pattern to bytes, so your alphabet is never larger than 256. This will run slower, but not as much as you'd think: Boyer-Moore does benefit from larger alphabets, but only to the extent that the alphabet is actually used.
Ah, right. I was under the impression this was unsafe, since you could end up with spurious byte matches that are not on character boundaries. But it seems the keyword is "self synchronizing", and UTF-8 (but not UTF-16) is safe to do byte-oriented searching on.
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.13....
brew tap homebrew/dupes
brew install --default-names homebrew/dupes/grep
following http://www.heystephenwood.com/2013/09/install-gnu-grep-on-ma... --default-names do not prepend 'g' to the binary
On OS X (a BSD derivative with BSD-grep), --default-names will put GNU grep in your path. A lot of things in OS X depend on BSD sed/awk/grep. Omitting --default-names links then GNU variants to ggrep/gsed/gawk. The key to making programs fast is to make them do practically nothing.
-- Mike Haertel1) make it do less
2) make it do more at a time.
The first corresponds to using more efficient algorithms and data structures. The second is parallelism.
Yes, but less in a particular way. It's important to skip processing stuff that can't matter in the end. In this example, skipping over bytes that can't matter because the end doesn't match. In DBMS, it's important to skip over records/columns/values that can't contribute to the answer.
The surprising realization in optimization is that procrastination is a virtue. Lazy beats eager.
Or vectorization, or avoiding stalling, or filling all execution units, or...
It is never a good idea to rush any large coding project. By rush, I mean just sit down and start churning out code just so you can have something tangible within a day or two. And by large, I mean anything requiring at least a few thousand LOC. Anything less can be okay to rush though; i.e., small projects where maintainability and scalability aren't as important; e.g., some MVP to test some market.
* We end up finding more crap to remove then we'd thought at the start.
* We can cut down misguided arguments from wannabe architects of "what if we'll need x" by saying "we'll add this back when someone asks for x". It's much easier to point out that x is currently pointless with concrete code than at the demiurge phase where everything is possible and timelines are ignored.
* We can chop at the problem as a team, without having a long sequential and solitary step of "designing the best system". Amdahl's law applies to dev teams as well.
It's like Fred Brooks said:
...plan to throw one away; you will, anyhow."Sketching in code" is a great analogy. Similar to how you'd trim off the rough edges in a sketched drawing, I've found myself cutting a lot of cruft when refactoring.
For example, programmer requests a multiplication, compiler generates a shift; programmer requests a division, compiler generates a multiplication; programmer specifies a switch, compiler generates a jump table.
Also, in modern x86 processors, an entire cache line (64 bytes) is read whenever a single byte is needed. So for strings smaller than 64, even algorithms that don't check every byte will read every byte from disk to RAM and from RAM to the processor cache.
Nope. Even DDR-3 can perform sequential transfers at speeds well exceeding one byte per CPU clock cycle.
And yes, you can ignore the time to read from disk. Think a SAN over a 10-gig link. That gets you close to byte-per-cycle territory as well. It takes on the order of a dozen cycles (more or less depending on architecture and algorithm) to perform one "step" of a search. So yes, these algorithms very much matter.
Also, in modern x86 processors, an entire cache line (64 bytes) is read whenever a single byte is needed. So for strings smaller than 64, even algorithms that don't check every byte will read every byte from disk to RAM and from RAM to the processor cache.
Yep. But this is not necessarily the bottleneck; see my above comments about the CPU.
This means that the simplest handcrafted loop that loads a byte, compares its value to the first byte of the searched string and then does a conditional jump should (if unrolled) take about 1 clock cycle per byte examined!
This means that if our DDR3 memory can read 6GB of data per second and our CPU core is clocked at 3Gh, this completely naive algorithm will run at half of the theoretical maximum speed.
Using XMM instructions (that work on 16 bytes at a time), should probably get us to the limit of the RAM speed.
Regarding SAN-over-10-gig link. I don't know about you, but the computer I'm typing this on has an SSD that can read only a few hundred megabytes per second.
Deleted code is debugged code. - Jeff Sickel
It's just that the end result is then restricted by the GPL, and they don't want that.
This is what most people mean when they say two FOSS licenses are "compatible."
It also means that having tools where you can quickly apply different techniques, ahem, composable functions, that you can search for more efficient solutions with a lot less effort. That doesn't solve the smartness problem but it makes it a lot more tractable.
The key word there is composable, and not functions.
I've seen 90% performance boosts in code where access to low-level data formats really wasn't possible. Though at times, using appropriate data structures / methods also helped markedly.
"The fastest method to execute is an empty method."
The fastest method to execute is an empty method that was never called.
The fastest method to execute is an empty method that was never called and never written.
The fastest method to execute is an empty method that was never called and never written and never planned.
(If anyone is interested, I want to add more operators, like intersection or difference of regular languages.)
[1] Intersection: grep regex1 file1 | grep regex2
[2] Difference: grep regex1 file1 | grep -v regex2
$ grep --mmap "fleagle" *
grep: the --mmap option has been a no-op since 2010
...
Coincidentally, the article is from 2010.It's something along the lines of a mantra, not a joke.