Counting the number of matching characters in two ASCII strings
lemire.me
lemire.me
In all cases, obviously the real magic is in the 64-bit word comparison routine (which doesn’t get more than a passing mention); converting the former problem into the latter is extremely straightforward and obvious - if you know that optimized solutions for the latter exist (I personally didn’t know of this bit twiddling hack (aside from doing it via SIMD, obviously) and am grateful to learn about the approach in Muła’s code).
And if we’re already down the “write it understandable and trust the compiler” path isn’t that the better way to go?
It’s interesting to me that C is now effectively a “higher level” language than it may appear on the surface because of optimization by compilers.
Fuzzy matching is another aspect that comes to mind.
The « slow way » of the post would also count matching code units which is utterly useless, really.
In my experience, the performance bottlenecks in bioinformatics is usually IO, parsing, de/compression and needlessly slow algorithms. You rarely get meaningful speed improvements by internally encoding the sequences one way or another (storing small fixed-size sequences in machine integers is an exception). There can be other advantages, though, like reduced memory consumption.
Partly for that reason, most DNA data is just stored as gzipped text files.
Heck, if your usual encoding is UTF-8, the flag bit can be purely advisory and ignored when it doesn't matter.
Yeah the last line had me scratching my head for a bit. Cool stuff.
"Here is some little known data which may be of interest to computer hackers. The items and examples are so sketchy that to decipher them may require more sincerity and curiosity than a non-hacker can muster. Doubtless, little of this is new, but nowadays it's hard to tell."
https://w3.pppl.gov/~hammett/work/2009/AIM-239-ocr.pdf - see e.g. page 79
"""
This is a display hack (that is, it makes pretty patterns) with the low 9 bits = Y and the 9 next higher a X; also, it makes interesting, related noises with a stereo amplifier hooked to the X and Y signals.
"""
Unless this is a significant bottleneck for your application, the latter does not seem practical, and a lot of profiling would have to go into justifying the latter code (which would only be worthwhile if, as I said, this were a significant bottleneck.)
There was an old lady 6 about unoptimized behavior in VB6 and why is wasn't necessary to do so. I had to write a wrapper for it which was optimized in my code. Was it harder to read? Yes. Did it produce a significant performance improvement? Also yes.
Software development is about tradeoffs. Demoing one way to improve a simple function helps others trunk how they write code, and more importantly why they wrote specific code
They succeed, and that's the problem.
Well, as long as it works, what difference does it make? As long as it's prefixed with a
// This compares the matching number of bytes
comment, you should never have to mess with it.
Sure, there are many cases where this is a premature optimization. This blog never said otherwise. But that doesn't mean there is never a time for optimization. And if profiling points at this, then this speedup can be valuable.
c1 = 0x8080808080808080
c2 = 0x7F7F7F7F7F7F7F7F
c3 = 0x0101010101010101
e = ~(x ^ y)
return popcount((e & c1) & ((e & c2) + c3))
For those wondering how this works. If two bytes are identical, then the XOR will yield 0x00 and the negation will yield 0xFF. Masking out the seven least significant bits with 0x80 will just yield 0x80 again. Masking out the most significant bit with 0x7F will just yield 0x7F again and adding 0x01 will yield 0x80. Finally combing 0x80 and 0x80 with AND just yields 0x80, i.e. exactly the most significant bit set.On the other hand if two bytes are not identical, then at least one of the two following things happens. The two bytes differ in the most significant bit and therefore the most significant bit of the XNOR is zero. Then masking out the seven least significant bits with 0x80 yields 0x00 and therefore the final AND yields 0x00.
Or the two bytes differ in at least one of the seven least significant bits and therefore at least one of the seven least significant bits of the XNOR is zero. Then masking out the most significant bit with 0x7F yields something smaller than 0x7F and adding 0x01 yields something smaller than 0x80, i.e. the most significant bit is zero, and therefore the final AND again yields 0x00 because the first operand is either 0x00 or 0x80.
The remaining work is to count the number of set bit, either with popcount or some other construction like the one in the article.
return popcount((e & c2) + c3);
(Thanks for writing this out, as otherwise I wouldn't have spotted it.) c = x ^ y;
return popcount(((c >> 1) | 0x8080808080808080) - c) & 0x8080808080808080);