Why GNU grep is fast (2010)
lists.freebsd.org
lists.freebsd.org
$ homebrew info grep
GNU grep, egrep and fgrep
https://www.gnu.org/software/grep/
/usr/local/Cellar/grep/3.3 (21 files, 885.3KB) *
Poured from bottle on 2019-01-03 at 12:23:37
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/grep.rb
...
This is a very arbitrary example, but I've got 1.5Gb application log sitting on my machine right now that'll suffice. I'll even do this in a way that might give a slight performance advantage to BSD, by using gnu grep first: $ time /usr/local/bin/ggrep "foobarbaz" application.log
real 0m1.319s
user 0m0.948s
sys 0m0.345s
vs OSX's grep: $ time /usr/bin/grep "foobarbaz" application.log
real 0m37.225s
user 0m31.036s
sys 0m1.286s
1s vs 37s. Same file.There's nothing odd about the file, straight boring application logs, and the line starts with the logging level in capital letters. So I can use a regex that is pinned to the start of the line, about as optimal as you can get:
first gnugrep:
$ time /usr/local/bin/ggrep -c "^INFO" application.log
1527786
real 0m0.622s
user 0m0.323s
sys 0m0.292s
then OS X's native bsd grep: $ time /usr/bin/grep -c "^INFO" application.log
1527786
real 0m3.588s
user 0m3.206s
sys 0m0.349s
BSD grep was significantly better than the prior search, but it is still notably slower than the gnu grep.In my experience, this isn't the most pathological example, but it's close. Note that this also applies to other tools that leverage grep under the surface, like zgrep etc.
I've specifically aliased grep over to ggrep in my shell so that I'm avoiding bsd grep whenever I can:
$ alias grep
alias grep='/usr/local/bin/ggrep'try something like tr:
time gtr a b < application.log > /dev/null
time tr a b < application.log > /dev/null
if you set LC_ALL=c it might be a little faster. $ time gtr a b < http_10x.log > /dev/null
real 0m0.061s
user 0m0.032s
sys 0m0.028s
$ time tr a b < http_10x.log > /dev/null
real 0m2.284s
user 0m2.267s
sys 0m0.014s $ shuf /var/log/syslog > shuf-syslog
$ time sort shuf-syslog > /dev/null
real 0m0.536s
user 0m0.870s
sys 0m0.031s
$ time LC_ALL=C sort shuf-syslog > /dev/null
real 0m0.094s
user 0m0.109s
sys 0m0.024s
It's more dramatic with a bigger file, of course.I once got annoyed with sort's speed, threw together a parallel external sort program that dramatically outperformed it, then realized the difference was not as dramatic with LC_ALL=C...oh well, it was a fun afternoon program anyway.
I really appreciate this property of UTF-8, and I can highly recommend to do a pen&paper exercise to see why it preserves order :-)
It wouldn't work if you want any type of normalisation and especially solving composition/decomposition.
~ % which grep
/Users/daniel/.nix-profile/bin/grep
~ % which sed
/Users/daniel/.nix-profile/bin/sed
~ % which ls
/Users/daniel/.nix-profile/bin/lshttps://stackoverflow.com/questions/40183218/why-does-sed-be...
Now, getting it to report line numbers will kill it, since you then have to scan every character.
Or, roughly, grepping for '^x' instead of 'x' should result in less work if the line does not contain x in the first character. One fewer comparison for each character after the first.
Instead, treat the file as a collection of characters and just look for patterns. Which may include the line break character.
That make sense?
Edit: I should also add that you should look for burntsushi's posts. Turns out some of this had become out of date. And largely depends on size of what you are searching.
Way back when, the first thing I'd do on any non-GNU host was to install a full GNU userland. I could write a book on my issues with GNU tools, but they are on balance preferable to the alternatives. All IMHO, of course.
I'm uncertain if it's the patent clause or the fact that apple prevents you from modifying and running changed software (such as on the iphone).
What's funny is that I think they are technicall in violation of the GPL (their modifications to the GPL v2 bash are not entirely distributed)
Is there much else they could do? Don't see how Apple could reasonably ship GPL code...
If anyone knows other tools where the gnu/home brew alternative is much better, please chime in (already saw tr on another comment).
In this case isn't the "already in RAM" test a more accurate reflection of performance anyway, as we are talking about the performance of grep and not the IO subsystem?
There are many cases where grep's performance won't be bottlenecked by IO, or at least not impacted directly by a bottleneck there. Anywhere when the input is coming from another program, essentially, and even if that task is IO bound it might be sending output to grep much faster than it is consuming input (perhaps gunzip <file | grep searchtext).
And in the case of searching a log file interactively, it is common that you won't just run grep of the file just once in a short space of time, instead doing it a couple of times as you refine your search, so for most of the runs it will be in cache (assuming the file is not to large).
Nearly ever SSD listed achieves well over 1GB/s in an actual benchmark, not just on a spec sheet. And these are just boring old off the shelf consumer drives. Nothing crazy.
So yeah maybe not over 400MB/s, but all of them are over 200MB/s. Sequential speeds really spiked as densities kept increasing.
Note that you're not going to get this with SATA SSDs, you need NVMe, it's a 5x difference in throughput and IOPS.
That's a very common use-case with grep. Either grepping a file you recently wrote, or running grep multiple times as you refine the regex, at which point the files will be in the FS cache.
> #1 trick: GNU grep is fast because it AVOIDS LOOKING AT EVERY INPUT BYTE.
TFA is incredibly short, and will explain it much better than I can.
This would not help, since the backing storage doesn't provide support for this kind of resolution. It would end up reading in the entire file anyways, unless your input string is on the order of an actual block.
As the other reply mentioned though, it's just that MacBook SSDs are that fast.
Somewhat confusing since it has to look at every byte to find the newlines. They are using a pretty specific definition of "look".
> Moreover, GNU grep AVOIDS BREAKING THE INPUT INTO LINES. Looking for newlines would slow grep down by a factor of several times, because to find the newlines it would have to look at every byte!
I assume the Boyer Moore preprocessor reads a lot of bytes also.
Not disputing it's more efficient, but there's no magic. It avoids reading some bytes when and if it can.
It can whenever you don't ask for line numbers, can't it?
> It probably looks for newlines after a match is found
Probably, yeah. Counting number of newlines in a range, without caring just where they fall, can probably be pretty darned fast with vector instructions. Idk if that's worth the additional pass, though.
Another way of looking at is just considering the ^ another character (plus one-off special handling for start of file).
It's also possible that the file is cached in memory (I ran grep a few times through the file before I carried out the specific measurements).
If we assume something like 100 MB/s sustained for spinning disks, that's a lot of disks to get to 2.41 GB/s even ignoring overheads.
This test was hitting the OS disk cache.
Samsung's PM1725b (https://www.samsung.com/semiconductor/ssd/enterprise-ssd/MZP...) has a Seq. Read of 6300 MB/s and Seq. Write of 3300 MB/s.
Even for multi-file scenarios, the difference is nowhere near close the difference between GNU grep and BSD grep. This means that compatibility with GNU grep takes priority for me and it's not worth switching over to ripgrep.
On equivalent tasks, ripgrep is not orders of magnitude faster than GNU grep, outside of pathological cases that involve Unicode support. (I can provide evidence for that if you like.)
For example, in my checkout of the Linux kernel, here's a recursive grep that searches everything:
$ time LC_ALL=C grep -ar PM_RESUME | wc -l
17
real 1.176
user 0.758
sys 0.407
maxmem 7 MB
faults 0
Now compare that with ripgrep, with a command that uses the same amount of
parallelism and searches the same amount of data: $ time rg -j1 -uuu PM_RESUME | wc -l
17
real 0.581
user 0.187
sys 0.384
maxmem 7 MB
faults 0
Which is 2x faster, but not "order of magnitude." Now compare it with how long
ripgrep takes using the default command: $ time rg PM_RESUME | wc -l
17
real 0.125
user 0.646
sys 0.654
maxmem 19 MB
faults 0
At 10x faster, this is where you start to get to "order of magnitude" faster
claims. But for someone who cares about precise claims with respect to
performance, this is uninteresting because ripgrep is 1) using parallelism and
2) skipping some files due to `.gitignore` and other such rules.You can imagine that if your directory has a lot of large binary files, or if you're searching in a directory with high latency (a network mount), then you might see even bigger differences from ripgrep without generally seeing a difference in search results because ripgrep tends to skip things you don't care about anyway.
In summary, there is an impedance mismatch when talking about performance because most people don't have a good working mental model of how these tools work internally. Many people report on their own perceived performance improvements and compare that directly to how they used to use grep. They aren't wrong in a certain light, because ultimately, the user experience is what matters. But of course, they are wrong in another light if you're interpreting it as a precise technical claim about the performance characteristics of a program.
so you bring the trick #1 from the grep author to a new level:
> #1 trick: GNU grep is fast because it AVOIDS LOOKING AT EVERY INPUT BYTE.
You stop looking at entire files, which can avoid a lot of bytes to go through.
Also parallelisms surely helps with a lot of files, or big ones really big ones which could be processed in chunks.
TIMEFMT=$'\nreal\t%*E\nuser\t%*U\nsys\t%*S\nmaxmem\t%M MB\nfaults\t%F'That situation changes when you have very short patterns, very long patterns, many patterns, or small alphabets (eg DNA). As burnsushi notes, ripgrep’s performance difference for common feel usage comes more from being smarter about its input than from algorithms (its algorithms are solid, of course).
This is like saying "if I remove wings from a plane, then it won't go much faster than my car. So a plane is not technically faster than a car"
Of course, users should know the difference between grep and ripgrep (especially with tweaks like .gitignore, which could be confusing if you don't know).
git ls-files -z | xargs -0 grep -nHE <regexp>
can be almost as fast.Well, it's a base-2 order of magnitude!
If you’re talking about the core algorithm, it’s not that much faster but the clever adjustments for common patterns and modern CPU architectures also counts for real world results. It’s just important to be precise so people know there hasn’t been some fundamental breakthrough in searching.
You are measuring the wrong thing or a case where rg can skip most files.
> ripgrep is built on top of Rust's regex engine. Rust's regex engine uses finite automata, SIMD and aggressive literal optimizations to make searching very fast. Rust's regex library also maintains performance with full Unicode support by building UTF-8 decoding directly into its deterministic finite automaton engine.
It does also help that there is a certain type of developer that sees such things as a personal challenge and will go through hell and high water to come up with more and more efficient ways to do things.
Or, there's the old joke about GNU echo: https://www.gnu.org/fun/jokes/echo-msg.en.html
Also, best practices have shifted to a continuous development model which keeps everybody on the upgrade treadmill. There's less concern with maintaining backwards compatibility and catering to those not running the latest environments. So if you make use of some newer Linux kernel API there's only a short window where people will put in the effort to maintain a compatibility mode, assuming they bother at all.
Lastly, containers mean people often develop and ship static environments that can be maintained independently, sometimes never upgraded at all.
What I find interesting is how people have begun to ditch autoconf in favor of even more complex (but newer and therefore cooler) build systems when ironically there's less need than ever for these things. Autoconf doesn't need replacing; such layers can often be left out entirely.
That said, when feature detection and backwards compatibility truly matters there's no good alternative. CMake, for example, effectively requires the end user to install the latest version of CMake, and if you already expect someone to install the latest version of something then why the contrivance at all? I always sigh aloud whenever I download a project that relies on CMake because I know that I now have two problems on my hand, not just one. (But better CMake than the other alternatives--I just won't even bother.)
[1] All that's left are AIX and Solaris. HP-UX/PA-RISC will be officially dead next year, and EOL for HP-UX/Itanium is 2025. From a commercial perspective Solaris seems to be deader than AIX, however Solaris still seems to see more development--especially improved POSIX and Linux compatibility. It's much easier to port to Solaris than AIX. It's a real shame Solaris is disappearing because on big machines with heavy workloads the OOM killer is a fscking nightmare on Linux. Solaris and Windows (and maybe AIX?) are the only operating systems that do proper and thorough memory accounting, permitting you to write reliable software.[2] The cloud services principle that says individual processes are expendable doesn't work when your job takes hours or days to run. (Or even just minutes, because workloads accumulate when the OOM killer starts shooting things down, and even in the cloud you run into hard limits on resource usage--i.e. cap on numbers of nodes. Memory overcommit is just like network buffer bloat--intended to improve things at the small scale but which results in catastrophic degradation at the macro level.)
[2] You can disable overcommit on Linux but the model of overcommit is baked too deeply into the kernel's design. A machine can still end up with the OOM killer shooting down processes if, for example, the rate of dirty page generation outpaces the patience of the allocator trying to reclaim and access memory that is technically otherwise available.
./configure is a sad thing, and the cluster of madness around it is even worse (autoconf, automake, and the ridiculous libtool). However, most gnu stuff is excellent, including gnu make. Fortunately, today most unix systems are sufficiently posix-compliant so that you can ship a gnumakefile that builds your program directly without much fuss.
[1] If you think about it, things like colorization belong in grep about as much as SVG generation belongs in systemd.
Alternatively, you could have grep take a list of colors from a config file or environment variable and blindly interpolate those strings to make colored output[1]. This also allows color to show up automatically for interactive use.
[1] this is in fact how grep works.
Think of converting to HTML, as one example.
Of course, ripgrep handles coloring like GNU grep does.
Is it really that, or do new people just come along and rebuild the same thing again and again without firm understanding of the older thing?
One approach would be to call GNU tools very exceptional and marvel at them (keep in mind its origins as a clone of older Unix stuff), but perhaps it's more appropriate to ask tougher questions of people operating in this other modality, that you are calling normal expectations.
If it does many things it becomes a subordinate with an intelligence of its own, something you need to communicate with or talk to, as opposed to something you can simply use
https://www.gnu.org/software/coreutils/rejected_requests.htm...
https://news.ycombinator.com/item?id=1626305
https://news.ycombinator.com/item?id=2393587
https://news.ycombinator.com/item?id=2860759
https://news.ycombinator.com/item?id=6814153
https://news.ycombinator.com/item?id=12350890
https://news.ycombinator.com/item?id=9153203
https://news.ycombinator.com/item?id=6813937
(edited)
Just so everybody knows: the links are for curiosity purposes. Reposts are ok after about a year, as the FAQ explains: https://news.ycombinator.com/newsfaq.html
For short patterns, which are imho by far the most common use, any algorithm that tries to be smart and skip a couple bytes wastes cycles on being smart where a simpler brute force algorithm has already fetched the next 16 bytes and started to compare them while the prefetcher already went off to get the next line from L2.
1 cycle per byte is not a good result for single string search; you can practically do a shift-and comparison of your input in that speed using general purpose registers.
https://github.com/rust-lang/regex/blob/master/src/literal/t... might be of interest to casual readers (not necessarily you, considering you were probably very involved in developing that algorithm ;)
I've often thought of making sure my IDs are uncommon characters to exploit the ability to skip a lot.
In particular, the advice in the OP is generally out of date. The "secret" sauce to ripgrep's speed in simple literal searches is a simple heuristic: choose the rarest byte in the needle and feed that to memchr. (The "heuristic" is that you can't actually know the optimal choice, but it turns out that a guess works pretty well most of time since most things you search have a similar frequency distribution.)
The SSSE3 optimizations come from Hyperscan, and are only applicable when searching a small number of small patterns. e.g., `Holmes|Watson|Moriarty|Adler`.
In other words, for common searches (which are short strings), it is much better to spend more time in a vectorized routine than to try to skip bytes.
Complexity analysis of substring search focuses on the number of comparisons – at least those I saw –, much like sorting, and of course that's not an accurate model at all.
This kind of thing is an pattern unless the benefit over Boyer-Moore huge.
A small performance gain in the common-case is not worth the pain of introducing pathological cases that only bite you once you are deeply committed.
Presumably this is not too bad for ripgrep itself, as long as it falls back to something sensible when the assumption fails.
And yes, the performance difference can be very large. Here's an example on a ~9.3GB file:
$ time rg --no-mmap 'Sherlock ' OpenSubtitles2016.raw.en | wc -l
6698
real 3.006
user 1.658
sys 1.345
maxmem 8 MB
faults 0
$ time grep 'Sherlock ' OpenSubtitles2016.raw.en | wc -l
6698
real 9.023
user 7.921
sys 1.092
maxmem 8 MB
faults 0
Notice that the pattern is `Sherlock `. The last byte is an ASCII space character, which is incredibly common. Boyer-Moore blindly picks this as the skip byte, but ripgrep uses a simple pre-computed frequency table to select a better byte as the skip byte.> Presumably this is not too bad for ripgrep itself, as long as it falls back to something sensible when the assumption fails.
It does. That's why I said, "ripgrep does not use Boyer-Moore in most searches."
Even Boyer-Moore is not superior to a naive search in literally every case, e.g. short needle or large alphabet.
I typically search for the largest string I can nowadays. Though, I suspect even those are not large enough to tip the needle, since I'm usually just talking about a uuid.
Here are a few aliases I use:
alias ack="ack --color" # color output
alias ackl="ack -l" # show file names only
acksubl () { ack -l -i "${1}" | xargs subl } # Do case-insensitive search and open files in sublime.
Turns out in practice I don't use regular expressions very often when searching text, and the most frequent question is -- where is this function/variable might have been used?
Seems the articles are free, if you don't want to pay the €6085 yearly instituational subscription.
Other than Adrian's https://blog.acolyer.org/ what else is there worth watching that is closer to engineering than theory? I wish we still had DDJ or C/C++ User Journal, but today's periodicals are things like Rasperry Pi enthusiast or Monthly Minecraft Tips.
http://ridiculousfish.com/blog/posts/old-age-and-treachery.h...
Has been replaced by `ag` https://github.com/ggreer/the_silver_searcher , ag is instant in a big repo compared to find|grep
echo -P > $HOME/.ripgreprc
and add this to your .bashrc or equivalent: export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"grep has a -R option which allows you to avoid the first problem, but avoiding the second one is a bit more difficult.
Also there is ripgrep which I've heard is effectively a faster version of ag written in Rust.
I've tried it but had it crashing on me in some directories so I'm sticking with ag
That being said, matching literals is always going to be faster, especially if you decompose the pattern to get more use out of your literal matcher - the downside of filtration is that if the literal is always present, you are just doing strictly more work. At least with decomposition you've taken the literal out of the picture. See https://branchfree.org/2019/02/28/paper-hyperscan-a-fast-mul... for those who don't know what I'm talking about (I know you've read it).
Am flirting with doing another regex engine that gets some of the benefit of decomposition and literal matching without taking on the nosebleed complexity of Hyperscan...
Is there a fast glushkov implementation that isn't bit parallel? I've never been able to figure out how to use bit parallel approaches with large Unicode classes. Just using a single Unicode aware \w puts it into the weeds pretty quickly. That's where the lazy DFA shines, because it doesn't need to build the full DFA for \w (which is quite large, even after the standard DFA compression tricks).
I've always thought that a better job of doing NFAs (Gluskov or otherwise) and staying bit-parallel would be done with having character reachability on codepoints, not bytes, generally remapping down to 'which codepoints make an actual difference'. This sounds ugly/terrifying, but the nice thing is that remapping a long stream of codepoints could be done in parallel (as it's not hard to find boundaries) and with SIMD. Step by step NFA or DFA work is more ponderous as every state depends on previous states.
And of course, one doesn't need to bet the farm on a lazy DFA if you have one, although it is quite robust in a large number of practical scenarios. (I think RE2 does bet the farm, to be fair.)
I think both Glushkov and Thompson can be done fast, but I agree that they are both going to be Really Big for UCP stuff. Idle discussions among the ex-Hyperscan folks generally leans towards 'NFA over codepoints' being the right way of doing things.
Occam's razor suggests if you do only 1 thing in a regex system (i.e. designing for simplicity/elegance, which would be an interesting change after Hyperscan) it must be NFA, as not all patterns determinize. If you are OK with a lazy DFA system that can be made to create a new state per byte of input (in the worst case) then I guess you can do that too.
I am not sure how to solve the problem of "NFA over codepoints", btw. Having no more than 256 distinct characters was easy, but even with remapping, the prospect of having to handle arbitrary Unicode is... unnerving.
Also BSD grep has other advantages, primarily it's not GNU grep.
The other commenter in this thread pointed out that this is a very old version and the newer bsd version is better.
real 0m2.044s
user 0m1.932s
sys 0m0.085s
wgl@pondera:~$ time grep LiteonTe *.text | wc -l
11020
real 0m1.939s
user 0m1.905s
sys 0m0.038s
wgl@pondera:~$ time ggrep LiteonTe *.text | wc -l
11020
real 0m0.130s
user 0m0.087s
sys 0m0.037s
wgl@pondera:~$ time ggrep LiteonTe *.text | wc -l
11020
real 0m0.119s
user 0m0.088s
sys 0m0.035s
wgl@pondera:~$ du -h -s *.text
128M Kismetkismet-kali-pondera-20190325-08-46-27-1.pcapdump.text
First one was done to cache then the number discarded. Thus the 'grep' you see above is the second run over the 128 mb pcap file expanded with tshark.Dramatic.
I'll stay with the gnu grep and not update the regular ones for now.
# /usr/bin/grep -V
grep (BSD grep) 2.6.0-FreeBSD
root@m6600:~ # /usr/local/bin/grep -V
grep (GNU grep) 3.3
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Mike Haertel and others; see
<https://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>.
root@m6600:~ # /usr/bin/time /usr/bin/grep X-User-Agent packetdump.pcap -c
60
0.54 real 0.45 user 0.07 sys
root@m6600:~ # /usr/bin/time /usr/bin/grep X-User-Agent packetdump.pcap -c
60
0.54 real 0.44 user 0.08 sys
root@m6600:~ # /usr/bin/time /usr/bin/grep X-User-Agent packetdump.pcap -c
60
0.54 real 0.41 user 0.11 sys
root@m6600:~ # /usr/bin/time /usr/local/bin/grep X-User-Agent packetdump.pcap -c
60
0.58 real 0.49 user 0.08 sys
root@m6600:~ # /usr/bin/time /usr/local/bin/grep X-User-Agent packetdump.pcap -c
60
0.60 real 0.48 user 0.11 sys
root@m6600:~ # /usr/bin/time /usr/local/bin/grep X-User-Agent packetdump.pcap -c
60
0.59 real 0.50 user 0.08 sys
root@m6600:~ # du -h -s packetdump.pcap
225M packetdump.pcap wgl:$ /usr/bin/grep --version
/usr/bin/grep --version
grep (BSD grep) 2.5.1-FreeBSD
wgl:$ /usr/local/bin/ggrep --version
/usr/local/bin/ggrep --version
ggrep (GNU grep) 3.3
Packaged by Homebrew
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Mike Haertel and others; see
<https://git.sv.gnu.org/cgit/grep.git/tree/AUTHORS>.
wgl:$ /usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text | wc -l
/usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text | wc -l
2.30 real 1.04 user 0.67 sys
1228
wgl:$ /usr/bin/time /usr/bin/grep LiteonTe really-big.text | wc -l
/usr/bin/time /usr/bin/grep LiteonTe really-big.text | wc -l
5.65 real 5.30 user 0.33 sys
1228
wgl:$ /usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text >/dev/null
/usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text >/dev/null
0.05 real 0.03 user 0.01 sys
wgl:$ /usr/bin/time /usr/bin/grep LiteonTe really-big.text >/dev/null
/usr/bin/time /usr/bin/grep LiteonTe really-big.text >/dev/null
6.50 real 5.71 user 0.58 sys
wgl:$ /usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text -c
/usr/bin/time /usr/local/bin/ggrep LiteonTe really-big.text -c
1228
2.33 real 1.05 user 0.69 sys
wgl:$ /usr/bin/time /usr/bin/grep LiteonTe really-big.text -c
/usr/bin/time /usr/bin/grep LiteonTe really-big.text -c
1228
5.37 real 5.05 user 0.31 sys
The wc -l is clearly polluting the result. However, I suspect that the >/dev/null is as well. But in the worst case, I see a halving of time over the old grep (edited), which correlates with my most common use of grep in looking through source files.FreeBSD clang version 6.0.1 as well as -O2
I suspect there are still edge cases where BSD grep is quite a bit slower or not compatible with GNU grep. However with a closer apples to apples comparison there isn't much difference anymore for my usage. Which is a lot of grep use but that is pretty vanilla.
There may also be other OS differences in our comparison. My tests where run against a fairly recent FreeBSD 12-STABLE.
Also key to making them small, stable, intuitive, and usable
If you want case insensitive matching by default, then use:
alias grep="grep -i"Of course, if your regex has no required literals at all, then this optimization can't be performed. GNU grep can slow down quite a bit in these cases, especially if you have a non-C locale enabled and use some Unicode aware features such as \w.
Not sure if any actual search tools other than Scalyr use that specific implementation, but it makes for an interesting read anyway if you are into substring search algorithms (the algo described improves on Boyer-Moore).
1: http://volnitsky.com/project/str_search/
2: https://www.scalyr.com/blog/searching-1tb-sec-systems-engine...
But, perhaps it could be improved further via SSE instructions, or by sending the data to a GPGPU and parallelizing the algorithm...
[0] https://git.savannah.gnu.org/cgit/grep.git/tree/src/kwset.c
[1] https://git.savannah.gnu.org/cgit/grep.git/tree/src/kwsearch...
Also, note that there were some quite fundamental optimizations in FreeBSD 12, such as introduction of the “epoch” mechanism - basically RCU. 11 still only uses old-fashioned locks.
I do wonder about the later messages discussing the other string matching algorithms (on of them using a Trie to easily track back), while they are considered in the discussion, I didn't find any search/regex implementing them.
Mac builtin:
yes | pv > /dev/null
139MiB 0:00:05 [28.4MiB/s]
GNU:
gyes | pv > /dev/null
3.31GiB 0:00:06 [ 584MiB/s]
Unix was pretty much "do a quick loop using terse code" and "see what fits in this static small buffer, and if it doesn't, quietly truncate". GNU would reallocate buffers to fit, and take reasonable precautions on performing well on tiny/huge inputs.
GNU was actually considered bloated by the Unix crowd ("why so many more kilobytes of code?" ). It's an approach that scaled better for the future though.
[0] https://www.gnu.org/prep/standards/standards.html#Reading-No...