I had written earlier about the performance problems of Suse Linux Enterprise Server v 11 Service Pack 1 (SLES 11 SP1) under VMWare: http://drjohnstechtalk.com/blog/2011/06/performance-degradation-with-sles-11-sp1-under-vmware/. What I hadn’t fully appreciated at that time is that part of the problem could be with the command grep itself. Further investigation has convinced me that grep as implemented under SLES 11 SP 1 X86_64 is horrible. It is seriously broken. The following results are invariant under both a VM and a physical server.
Methodology 1
A cksum shows that grep has changed between SLES 10 SP 3 and SLES 11 SP 1. I’m not sure what the changes are. So I performed an strace while grep’ing a short file to see if there are any extra system calls which occur under SLES 11 SP 1. There are not.
I copied the grep binary from SLES 10 SP 3 to a SLES 11 SP 1 system. I was afraid this wouldn’t work because it might rely on dynamic libraries which also could have changed. However this appears to not be the case and the grep binary from the SLES 10 system is about 19 times faster, running on the same SLES 11 system!
Methodology 2
I figure that I am a completely amateur programmer. If with all my limitations I can implement a search utility that does considerably better than the shell command grep, I can fairly decisively conclude that grep is broken. Recall that we already have comparisons that show that grep under SLES 10 SP 3 is many times faster than under SLES 11 SP 1.
Results
The table summarizes the findings. All tests were on a 109 MB file which has 460,000 lines.
OS
| Type of Grep
| Time (s)
|
SLES 11 SP 1
| built-in
| 42.6
|
SLES 11 SP 1
| SLES 10 SP 3 grep binary
| 2.5
|
SLES 11 SP 1
| Perl grep
| 1.1
|
SLES 10 SP 3
| built-in
| 1.2
|
SLES 10 SP 3
| Perl grep
| 0.35 s
|
The Code for Perl Grep
Hey, I don’t know about you, but I only use a fraction of the features in grep. The switches i and v cover about 99% of what I do with it. Well, come to think of it I do use alternate expressions in egrep (w/ the “|” character), and the C switch (provides context by including surrounding lines) can sometimes be really helpful. The i (filenames only) and n (include line numbers) look useful on paper, but you almost never end up needing them. Anyways I simply didn’t program those things to keep it simple. Maybe later. To make it as fast as possible I avoided anything I thought the interpreter might trip over, at the expense of repeating code snippets multiple times. At some point (allowing another switch or two) my approach would be ludicrous as there would be too many combinations to consider. But at least in my testing it does function just like grep, only, as you see from the table above, it is much faster than grep. If I had written it in a compiled language like C it should go even faster still. Perl is an interpreted language so there should always be a performance penalty in using it. The advantage is of course that it is so darn easy to write useful code.
#!/usr/bin/perl
# J.Hilgart, 6/2011
# model grep implementation in Perl
# feel free to borrow or use this, but it will not be supported
use Getopt::Std;
$DEBUG = 0;
# Get the command line options.
getopts('iv');
# the search string has to be present
$mstr = shift @ARGV;
usage() unless $mstr;
$mstr =~ s/\./\\./g;
# the remaining arguments are the files to be searched
$nofiles = @ARGV;
print "nofiles: $nofiles\n" if $DEBUG;
$filePrefix = $nofiles > 1 ? "$_:" : "";
# call subroutine based on arguments present
optiv() if $opt_i && $opt_v;
opti() if $opt_i;
optv() if $opt_v;
normal();
################################
sub normal {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
# print filename if there is more than one file being searched
print "$filePrefix$_" if /$mstr/;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print if /$mstr/;
}
}
exit;
} # end sub normal
###############################
sub opti {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" if /$mstr/i;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print if /$mstr/i;
}
}
exit;
} # end sub opti
#################################
sub optv {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" unless /$mstr/;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print unless /$mstr/;
}
}
exit;
} # end sub optv
##############################
sub optiv {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" unless /$mstr/i;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print unless /$mstr/i;
}
}
exit;
} # end sub optiv
sub usage {
# I never did finish this...
} |
#!/usr/bin/perl
# J.Hilgart, 6/2011
# model grep implementation in Perl
# feel free to borrow or use this, but it will not be supported
use Getopt::Std;
$DEBUG = 0;
# Get the command line options.
getopts('iv');
# the search string has to be present
$mstr = shift @ARGV;
usage() unless $mstr;
$mstr =~ s/\./\\./g;
# the remaining arguments are the files to be searched
$nofiles = @ARGV;
print "nofiles: $nofiles\n" if $DEBUG;
$filePrefix = $nofiles > 1 ? "$_:" : "";
# call subroutine based on arguments present
optiv() if $opt_i && $opt_v;
opti() if $opt_i;
optv() if $opt_v;
normal();
################################
sub normal {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
# print filename if there is more than one file being searched
print "$filePrefix$_" if /$mstr/;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print if /$mstr/;
}
}
exit;
} # end sub normal
###############################
sub opti {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" if /$mstr/i;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print if /$mstr/i;
}
}
exit;
} # end sub opti
#################################
sub optv {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" unless /$mstr/;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print unless /$mstr/;
}
}
exit;
} # end sub optv
##############################
sub optiv {
foreach (@ARGV) {
open(FILE,"$_") || die "Cannot open $_!!\n";
while(<FILE>) {
print "$filePrefix$_" unless /$mstr/i;
}
close(FILE);
}
if (! $nofiles) {
# no files specified, use STDIN
while(<STDIN>) {
print unless /$mstr/i;
}
}
exit;
} # end sub optiv
sub usage {
# I never did finish this...
}
Conclusion
So built-in grep performs horribly on SLES 11 SP 1, about 17 times slower than the SLES 10 SP 3 grep. I wonder what an examination of the source code would reveal? But who has time for that? So I’ve shown a way to avoid it entirely, by using a perl grep instead – modify to suit your needs. It’s considerably faster than what the system provides, which is really sad since it’s an amateur, two-hour effort compared to the decade+ (?) of professional development on Posix grep. What has me more concerned is what haven’t I found, yet, that also performs horribly under SLES 11 SP 1? It’s like deer on the side of the road in New Jersey – where there’s one there’s likely to be more lurking nearby : ) .
Follow Up
We will probably open a support case with Novell. I am not very optimistic about our prospects. This will not be an easy problem for them to resolve – the code may be contributed, for instance. So, this is where it gets interesting. Is the much-vaunted rapid bug-fixing of open source really going to make a substantial difference? I would have to look to OpenSUSE to find out (where I suppose the fixed code would first be released), which I may do. I am skeptical this will be fixed this year. With luck, in a year’s time.
7/15 Update
There is a newer version of grep available. Old version: grep-2.5.2-90.18.41; New version: grep-2.6.3-90.18.41 Did it fix the problem? Depends how low you want to lower the bar. It’s a lot better, yes. But it’s still three times slower than grep from SLES 10 SP3. So…still a long ways to go.
9/7 Update – The Solution
Novell came through today, three months later. I guess that’s better than I pessimistically predicted, but hardly anything to brag about.
Turns out that things get dramatically better if you simple define the environment variable LC_ALL=POSIX. They do expect a better fix with SLES 11 SP 2, but there’s no release date for that yet. Being a curious sort, I revisited SLES 10 SP3 with this environment variable defined and it also considerably improved performance there as well! This variable has to do with the Locale and language support. Here’s a table with some recent results. Unfortunately the SLES 11 SP 1 is a VM, and SLES 10 SP3 is a physical server, although the same file was used. So the thing to concentrate on is the improvement in performance of grep with vs without LC_ALL defined.
OS
| LC_ALL=POSIX defined?
| Time (s)
|
SLES 11 SP 1
| no
| 6.9
|
SLES 11 SP 1
| yes
| 0.36
|
SLES 10 SP 3
| no
| 0.35
|
SLES 10 SP 3
| yes
| 0.19 s
|
So if you use SLES 10/11, make sure you have a
export LC_ALL=POSIX
defined somewhere in your profile if you plan to use grep very often. It makes a 19x performance improvement in SLES 11 and almost a 2x performance improvement under SLES 10 SP3.
Related
If you like the idea of grep but want a friendlier interface, I was thinking I ought to mention Splunk. A Google search will lead you to it. It started with a noble concept – all the features of grep, plus a convenient web interface so you never have to get yuor hands dirty and actually log into a Linux/unix system. It was like a grep on steroids. But then in my opinion they ruined a simple utility and blew it up with so many features that it’ll take hours to just scratch the surface of its capabilities. And I’m not even sure a free version is still available. Still, it might be worth a look in some cases. In my case it also slowed down searching though supposedly it should have sped them up.
And to save for last what should have come first, grep is a search utility that’s great for looking at unstructured (not in a relational database) data.