git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: I'm a total push-over..

From
Linus Torvalds <torvalds@linux-foundation.org>
Date
Jan 24, 2008, 17:15 UTC
Message-ID
<alpine.LFD.1.00.0801240839590.2803@woody.linux-foundation.org>
In-Reply-To
<37fcd2780801240828vac82e6ds4da5aecde56e8d2f@mail.gmail.com>
On Thu, 24 Jan 2008, Dmitry Potapov wrote:
Show 5 quoted lines
> 
> I don't think you can any meaningful unicode-juggling without converting
> symbols to UCS-4, and after that it makes much more sense to operate
> with uint32 than bytes. So, Jenkins' hash is still relevant, just because
> it does not operate on single bytes, but using uint32.
No, no, no, NO!

Egads! Why do people constantly do these totally idiotic things for Unicode?

You can do a perfectly fine 8-bytes-at-a-time hash for almost 100% of all source code projects in UTF-8, without *ever* doing any format changes at all. Admittedly, it's a lot easier if the hash is a pure in-memory one (ie we don't care about byte-order or size of integers or anything like that), but that's the common case for most hashes that aren't used for BTree lookup on disk or something like that.

Here, let me show you:
	unsigned int name_hash(const char *name, int size)
	{
		hash = HASH_INIT;
		do {
			unsigned char c;
			if (size >= sizeof(long)) {
				unsigned long val = get_unaligned_long(name);
				if (!(val & 0x8080808080808080)) {
					/* Make it equivalent in case */
					val &= ~0x2020202020202020;
					hash = hash_long(hash, val);
					name += sizeof(long);
					size -= sizeof(long);
					continue;
				}
			}
			c = *name;
			if (!(c & 0x80)) {
				hash = hash_long(hash, c & ~0x20);
				name++;
				size--;
				continue;
			}
			/* This is the unusual and slowish case */
			hash = hash_utf8_char(hash, c, &name, &size);
		} while (size);
		return hassh;
	}

and then the only point you ever do that actual UTF8->codepoint conversion is for that "high bit set" case.

A few things to note on the above:
 - the hash obviously has "odd" characteristics. We're not necessarily 
   hashing characters at a time at all, and the alignment of characters 
   with high bits *within*the*string* will make a difference to how we 
   hash them.
   But it's also important that the "get_unaligned_long()" means that the 
   alignment of the string itself doesn't matter, so its' purely a 
   "chunking within the string" thing, and the alignment of the string 
   itself won't affect the hash value
 - I'm not writing out hash_utf8_char(), because it's certainly not 
   totally trivial, but it's not *really* complex either. The 
   nontriviality isn't so much the decoding into a codepoint (which is 
   pretty simple), but the fact that when you have the codepoint you 
   should then decompose it and turn it into lower case, which is 
   generally two table lookups. Then, you just do
	for_each_decomposed_uppercased_codepoint(c)
		hash = hash_long(hash, c);
   and one thing to note is that for the hashing, the decomposition and 
   uppercasing doesn't even have to be "exact" (the same way I didn't do 
   an "exact" upper-casing for US-ASCII, just a "good enough" one!)

Similarly, when you actually do a unicode *compare* function, you should never *ever* actually convert to any unicode codepoints or do any expensive decomposition AT ALL by default! What you do is to compare things byte-by-byte, and only convert to unicode/decompse if there are any differences, and only for those parts of the sequence that differ!

So if you have two UTF-8 strings (even if they have "complex" characters, ie with the high bit set), the *common* case is that you'll match them byte for byte, and they'll match without any Unicode conversion needed at all! This is common because:

 - normally, even if you don't ever normalize, people tend to input things 
   in a *similar* manner (again, OS X is the odd man out), so even 
   non-normalized strings are often non-normalized the same way!
 - we're only going to compare things that have hashed to the same thing 
   anyway, so the common case is that it's the same string, and most 
   likely had the same source. And if it's a collision, it's often totally 
   different. And even if it's different only in case, the *common* case 
   is going to be (for source code trees, at least) that the different 
   point is a US-ASCII letter, and the case-comparison will again be done 
   without any Unicode knowledge at all!

This is why normalization of strings before-hand is generally so utterly stupid. It doesn't buy you anything. It complicates things a lot (you don't want to normalize in-place, so you have memory management issues), and it actually SLOWS THINGS DOWN.

It's much better to do UTF-8 comparisons and hashing char-by-char. At least if you know that the common case is not going to be the complex part of the character set (which is undoubtedly true for source code filenames).

Normalizing things ahead of time *only* makes sense if:
 - you expect complex characters to be a big part of your load
and
 - you're going to do literally *thousands* of comparisons against the 
   *same* strings over and over (so that the cost of normalization is
   literally up-front)

For example, for a filesystem, it's true that you're going to compare against the *target* (on-disk) multiple times, but that doesn't actually mean that normalizing it makes any sense - because the data you're going to compare against comes from user space and isn't guaranteed to be normalized, so you still cannot do a simple memcmp() without the expense of normalizing that.

And since you're going to hash the filenames anyway, you will not have "thousands of comparisons" per source lookup, you'll generally only have a couple, so now your normalization actually cost you *more* than doing the above on-the-fly comparison!

See?

Basically, it's almost always a stupid thing to actually normalize a whole string. You do those things character-by-character, and only lazily when you actually need to!

			Linus
Previous: Dmitry PotapovNext: Dmitry Potapov
Message 34 of 51 in “I'm a total push-over..”
  1. Linus TorvaldsJan 22, 2008
  2. Kevin BallardJan 23, 2008
  3. Junio C HamanoJan 23, 2008
  4. Junio C HamanoJan 23, 2008
  5. Johannes SchindelinJan 23, 2008
  6. David KastrupJan 23, 2008
  7. Theodore TsoJan 23, 2008
  8. Linus TorvaldsJan 23, 2008
  9. Linus TorvaldsJan 23, 2008
  10. Junio C HamanoJan 25, 2008
  11. Linus TorvaldsJan 25, 2008
  12. Junio C HamanoJan 23, 2008
  13. Johannes SchindelinJan 23, 2008
  14. Linus TorvaldsJan 23, 2008
  15. Johannes SchindelinJan 23, 2008
  16. Linus TorvaldsJan 23, 2008
  17. Linus TorvaldsJan 23, 2008
  18. Jeremy Maitin-ShepardJan 25, 2008
  19. Johannes SchindelinJan 25, 2008
  20. Jeremy Maitin-ShepardJan 25, 2008
  21. Johannes SchindelinJan 25, 2008
  22. Junio C HamanoJan 25, 2008
  23. Andreas EricssonJan 23, 2008
  24. Dmitry PotapovJan 23, 2008
  25. Andreas EricssonJan 23, 2008
  26. Marko KreenJan 23, 2008
  27. Andreas EricssonJan 23, 2008
  28. Luke LuJan 24, 2008
  29. Andreas EricssonJan 24, 2008
  30. Marko KreenJan 24, 2008
  31. Andreas EricssonJan 24, 2008
  32. Marko KreenJan 24, 2008
  33. Dmitry PotapovJan 24, 2008
  34. Linus TorvaldsJan 24, 2008
  35. Dmitry PotapovJan 24, 2008
  36. Linus TorvaldsJan 24, 2008
  37. Marko KreenJan 25, 2008
  38. Linus TorvaldsJan 25, 2008
  39. Linus TorvaldsJan 25, 2008
  40. Marko KreenJan 26, 2008
  41. Linus TorvaldsJan 27, 2008
  42. Dmitry PotapovJan 27, 2008
  43. Johannes SchindelinJan 27, 2008
  44. Dmitry PotapovJan 27, 2008
  45. Marko KreenJan 27, 2008
  46. Dmitry PotapovJan 27, 2008
  47. Marko KreenJan 26, 2008
  48. Marko KreenJan 25, 2008
  49. Dmitry PotapovJan 23, 2008
  50. Andreas EricssonJan 24, 2008
  51. Linus TorvaldsJan 23, 2008

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.