Crit-bit trees
cr.yp.to
cr.yp.to
(Note: self promotion.)
http://hackage.haskell.org/package/critbit
Contribution information is available on the github page
https://github.com/bos/critbit
Also, as always, Edward Kmett weighs in with some particularly insightful comparisons of critbit trees, PATRICIA trees, and other variants
http://www.reddit.com/r/haskell/comments/1e1ywq/critbit_tree...
https://github.com/colmmacc/nutrient
still a work in progress, but it made it considerably easier for me to fully understand what's going on. May help others.
cache miss = 100 cycles
cache miss + tlb miss = 200 cycles
memcmp compares many bytes per cycle, it's clear to see that the cache misses will dominate the runtime, and trees involve O(log(N)) cache misses. A hash table is typically two cache misses. For interned python strings it's only one.
This is not always true...if you have to hash strings when executing queries, data structures like TRIEs or naive 256-ary trees may be faster (where the O(strlen) time it takes to hash the string is instead used to walk the tree).
Memoizing a hash code for a string is pretty cheap also, especially if you only dynamically allocate on the heap for long strings.
So a very common scenario for TRIEs/K-ary trees is for high-speed parsing - basically every byte read off the input transfers you to another state in the state machine.
Cache is moot as the state machine/tree is almost always in cache. The key (ha!) is input doesn't need to be cached, nor is any precomputed data required on the input side to be performant.
Any scenario where you decode {k,v} pairs benefits from this.
EDIT: Here you go. Some work I contributed a while ago when we were using Mongo. Replace hash table with a TRIE, deserialization performance goes up 40%. Because you don't need to decode a string, hash it, and then hit a hash table.
https://github.com/mongodb/mongo-csharp-driver/commit/0b7879...
EDIT (reply threshold):
"That's not how big-O notation works. It only applies to asymptotic behavior. It makes sense to say that iterating over the whole list 5 times is slower than just doing it once, but it's O(m) either way."
Now you're just being pedantic.
The advantages of trees lay elsewhere, such as persistence and ordering.
To get a comparable binary representation for arbitrary types, you need to use the hash method. So now you get the worst of both worlds - a hash call, and a tree lookup, and you need buckets for collisions.
You can't special case strings either - non-string objects can be equal to strings.
An explicit language construct for radix trees is an interesting idea, but once you really need trees you might be closer to needing a real RDBMS, or an in process extension such as sqlite.
Sure, but remember it also takes time to compute the actual hash value. That process itself is O(n) where n is length of the key. For large keys sizes and small set sizes the tree probably wins (for some definition of "large" and "small").
The critbit algorithm walks the tree while simultaneously moving through the key bytes/bits. It seems to me that for most modestly sized sets it has the advantage.
I always wondered why. In my coding experience, for most generic coding tasks, key-value dictionary backed by a hash was a better go-to construct. I wonder if it has something to do with processor branch prediction; a random guess would be that it would be hard to do b-tree branch prediction on a well balanced tree, while hash is constant.
Its a very good point that when it comes to hashing a very long key string this crit-bit tree structure has interesting properties. I wonder if this structure could be used to implement a good(better?) average case implementation of the LCS problem. http://en.wikipedia.org/wiki/Longest_common_subsequence_prob...