Glibc's strlen implementation: Probably not what you'd guess
sources.redhat.com
sources.redhat.com
1 million strlens on the same random 100 byte string:
Atom 330, 1.6GHz, gcc 4.3.2 (Debian Lenny)
glibc: 1.3ns/char
easy: 3.4ns/char
obsd: 3.4ns/char
Core2 Duo, 2.8GHz, gcc version 4.0.1 (Apple Inc. build 5484)
libc: 0.12ns/char
glibc: 0.39ns/char
easy: 0.58ns/char
obsd: 0.60ns/char
easy: while ( *p++) c++;
obsd: openbsd, for (s = str; *s; ++s); return s-str;
The preliminary conclusions are:The glibc strlen is something like twice as fast as the naive implementation, but there is something else out there that knocks its socks off.
Secondary conclusion would be: remember not to compare GHz across different processors.
1 million strlens on the same random 100 byte string:
Pentium 4 3.0GHz, gcc 4.3.2 (Debian Lenny)
glibc: 0.52ns/char
libc: 0.60ns/char ???
easy: 0.94/char
obsd: 0.94/char
I would expect libc and glibc to match being a Debian machine, and they matched on the Atom 330. I can't account for the difference here, perhaps the difference in the Debian library compilation flags and my -O3 make a difference on this processor.Don't tell the Gentoo crowd, you'll only encourage them.
Also, http://funroll-loops.info/ for those who haven't seen it.
And I used to use Gentoo exclusively. :)
http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/arch/
The linked code is for the case when there's no arch-specific implementation.
(requires registration, here's gist link: http://gist.github.com/77178)
Author covers really clever techniques to count bits, count non zero bytes like this etc etc. Bit manipulation at its best.
I agree with some of the reviewers though, the title probably means less people manage to find it than if it was called "Bit manipulation bible" or something.
http://www.daemonology.net/blog/2008-06-05-faster-utf8-strle...
I used to use and love FreeBSD (Since switched to OS X as desktop os, still run FreeBSD servers), but OpenBSD source code just looked more approachable.
McKusicks' wonderful book and video course helped.
How do they stack up in benchmarks?
Edit: See other posts in this discussion for actual benchmarks. >_>
Don't in any circumstances refer to Unix source code for or during your work on GNU! (Or to any other proprietary programs.)
If you have a vague recollection of the internals of a Unix program, this does not absolutely mean you can't write an imitation of it, but do try to organize the imitation internally along different lines, because this is likely to make the details of the Unix version irrelevant and dissimilar to your results.
For example, Unix utilities were generally optimized to minimize memory use; if you go for speed instead, your program will be very different. You could keep the entire input file in memory and scan it there instead of using stdio. Use a smarter algorithm discovered more recently than the Unix program. Eliminate use of temporary files. Do it in one pass instead of two (we did this in the assembler).
http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/arch/i386...
http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/arch/amd6...
It uses repne scasb.
Comment in x86-65 directory says:
"change amd64's MACHINE_ARCH from x86_64 to amd64. There are many many reasons for this, quite a few of them technical, and not all of them in response to Intel's broken ia32e crud. The gcc toolchain stays at x86_64 for now."
The glibc version is written in such a way to minimize the jumps in the assembly and therefore produce faster code. Besides reading 4 bytes at a time, it does some clever (and very nontrivial) "magic." But again, all to reduce the number of jumps.
getLength()
{
return length;
}The "right" answer was just to use strlen()...
element.getElementsByTagName(tagName);
That was what he was looking for. ;-) Of course, then he had me do it out as if getElementsByTagName didn't exist.Will I employ similar technique in my own code? Absolutely not. It hinders readability and byte comparison instructions in consecutive memory addresses are so stupidly fast that I'll probably save less CPU time combined in all executions of my program than the total amount of time it took me to think this hack up.
If you're a maintainer of glibc though (which is known for its exceptionally clear and straight-forward code,) then I might consider accepting a patch from someone who thought it up.
edit: I thought similarly of DJB's loop unrolling when I saw it in qmail. It's cute, but will it make any noticeable difference today? Probably not.
I'm just curious if the speed-up of this hack was ever benchmarked. Or if anyone spent a while tracking down a source of bottlenecks, and found it to be an inefficient strlen implementation.
Unfortunately though, this is slower than comparing 4 bytes at a time yourself.
Working bytes 1 at a time is pretty expensive. 4 at a time is great on a 32 bit cpu (As long as they're aligned).
Most developers don't have to worry about such optimizations. But this is Hacker News, and a lot of hackers do.
The speed-up I was referring to howeverwasn't "strlen speedup" but "your entire app running with naive strlen implementation, vs your entire app with clever strlen."
I also wasn't saying this has no place in it. I was trying to add that these hacks aren't usually what makes your app execute twice as fast or feel more 'snappy', unless the bread and butter of your app is string processin (in which case there are better running time algorithms, not just low-level hacks).
I love these as much as the next person, but early and misappropriated optimization, in my opinion, is suboptimal as a practice.
My point is basically as follows:
1) Yes, it probably makes sense for GNU libc to use the optimized implementation.
2) Yes, it's really interesting to dissect when you're looking for clever implementations and hacks.
3) These "2x" speed-up numbers I feel are all nice metrics, but in the end, don't amount to much. Outside of this argument, I feel people misunderstand how long something takes in a computer. The relative time a network or hard drive read takes, a memory read takes, a CPU instruction cache miss takes, and a dumb comparing of bytes via a single instruction are all a magnitude of difference apart.
So let's say you're writing your http caching server and you're using the new strlen algorithm. The amount of time your code will spend fetching the item from memory and putting it on the wire will completely eclipse the speedup you get from this fancy strlen. Not to mention if you're writing this in a high-level language, the nanoseconds you save on a linear algorithm will simply not matter.
So you can make the argument that over the last few years, the total time and energy spent by all Linux machines saved by using the new algorithm is worth it. I don't exactly buy it because most of modern computers' lives are dictated by waiting for input, processing it in burst and then more waiting. Most CPUs are sitting around the world with single-digit utilization. If we all loaded up a bunch of work to do in 1990 and the world's CPU power spent time crunching it, we might arrive at an answer a few hours or days sooner. But all it means in realistic terms, is that your Linux box will arrive at answer a few nanoseconds sooner and get to finally start waiting for its next batch sooner (whether this entails serving http requests or waiting for your next key stroke)
I'm all for optimization, but I think it has to be appropriate and measured. It probably makes sense to spend time on nano-optimizations for maintainers of one of the most-used libraries in the world, but all I'm trying to say, that for most readers of HN, it's better to spend time working on algorithm run-time optimization or caching policies than looking for getting side-tracked with strlen implementations.
But per your main point, I think you're wrong in assuming that where your time is best spent is true for most HN readers. My research project is a compiler that generates code for the Cell. This kind of optimization - which in general is a vectorization - is directly applicable to what I do. And, yes, in the kinds of applications I target, the difference when this kind of optimization is applied is measurable and significant.
HN has different kinds of hackers. Something that is outside of your scope might be in someone else's scope.
An advantage of optimizing the hell out of the library version is that nobody will be tempted to roll their own string compare in their application code. Slow APIs are terrible because they force application developers to work around them. So the answer isn't just to write something slow, then measure. You'll find performance doesn't matter because everyone has avoided using it.
#ifdef NO_CLEVER_OPTIMIZATIONS
obvious version
#else
complicated fast version
#endif
It's good as documentation, good as a test case, and good for isolating weird problems like compiler optimization bugs.(Also, you're not going to be reading off the end of a malloc'ed block, because essentially all malloc implementations, including the one in glibc, return blocks whose start address and size are multiples of the architecture's word size.)
The ((longword - lomagic) & himagic) should be ((longword - lomagic) & ~longword & himagic)
It relies on certain assumptions that are not the part of the C standard, so while this code works on the majority of platforms, this is not a portable C code. In which case going all the way down to the assembly level makes more sense. Especially considering there are typically dedicated CPU instructions for the exact purpose of searching a zero in a contiguous block of memory. Something like "rep scasb" on x86.
Also, using strlen() on a 4k string at all borders on malpractice.
"friendlier to the micro-architecture" doesn't even make sense. Check the chip timings for rep scasb. It's not friendly.
We're not really discussing wether you should be using strlen on large strings or not, but even if it's used say a million times on strings of length 80 or so, you'd see an improvement worth having.
Check any assembly language forum, book, etc and there will be discussion on why rep scasb/movsb/cmpsb are lame.
Would you implement string copy with rep movsb as well?
You know that VC++ does implement copies with movsd/movsb, right?
Sorry, I don't read a lot of books and forums on assembly programming. Just the PRM. I'm just stuck reading/writing a lot of assembly on projects.
If it's less clock cycles to do branching and comparing by dword (which it is for medium to long strings) than doing rep scasb, then what else matters...?
>> You know that VC++ does implement copies with movsd/movsb, right?
I've stepped through VC++ string copy code in softice many a time thanks.
Notice how I was asking about 'movsb', and you replied with 'movsd/movdb'. Notice the difference?
You can trade per-byte cycle counts for lower cost to invoke the routine, and for not evicting cache and BTB entries.
On your second point, I assumed it was the "rep" part of the instruction that you were railing against. Apparently it's the "not knowing the difference between a byte and a dword" part. That's awesome. You can have the last word, if you'd like.
Don't know what you're talking about "lower cost to invoke the routine", and the cache/BTB entries would be negligible on a small routine like this.
You seem kinda angry and bitter whenever you reply to me :/ Chill out eh.
Sure, it would bloat the code a little to inline the optimized version, but it could be done in tight inner loops if required.