I've previously posted about Sasa's hash-array mapped trie, but I never posted any benchmarks. I recently came across this post on Stackoverflow which provided a decent basic benchmark between .NET's default Dictionary<TKey, TValue>, the C5 collection's hash dictionary, F#'s immutable map, and .NET's new immutable collections.
I slightly modified the file to remove the bench against the F# map and the new immutable collections since I'm still using VS 2010, and I added a simple warmup phase to ensure the methods have all been JIT compiled and the GC run to avoid introducing noise:
static void Warmup() { var x = Tree.Make<string, object>(); var y = new C5.HashDictionary<string, object>(); var z = new Dictionary<string, object>(); z.Add("foo", "bar"); for (var i = 0; i < 100; ++i) { x = x.Add("foo" + i, "bar"); y.Add("foo" + i, "bar"); z.Add("foo" + i, "bar"); var tmp1 = x["foo" + i]; var tmp2 = y["foo" + i]; var tmp3 = z["foo" + i]; } x = default(Tree<string, object>); y = null; z = null; GC.Collect(); }
The results are still somewhat representative. This is a sample of an average output, where "Imm" is Sasa's immutable HAMT:
# - 100 SCGD - 0 MS - 25 Ticks C5 - 0 MS - 887 Ticks Imm - 0 MS - 387 Ticks # - 1000 SCGD - 0 MS - 257 Ticks C5 - 0 MS - 294 Ticks Imm - 0 MS - 368 Ticks # - 10000 SCGD - 1 MS - 4084 Ticks C5 - 1 MS - 5182 Ticks Imm - 1 MS - 5436 Ticks # - 100000 SCGD - 28 MS - 85742 Ticks C5 - 32 MS - 99280 Ticks Imm - 32 MS - 97720 Ticks
Observations:
- C5's standard deviation was somewhat wider than both Sasa's HAMT and SCGD, so it's performance seems slightly less predictable
- Sasa's immutable HAMT appears to perform within 5% of the mutable C5 collection at all collection sizes
- Sasa's immutable HAMT appears to perform within 15% of the mutable SCGD for large collections where the hash table with higher load factors
- Small collections requiring a small load factor clearly advantage the mutable SCGD by up to an order of magnitude, an advantage not shared by C5 for some reason (possibly they maintain a higher load factor)
- C5's terrible performance on very small collections of 100 items was consistent on every test run, again possibly because they maintain a high load factor before resizing
- Sasa's HAMT takes just as much time to load 1000 items as it takes to load 100 items; this was consistent across every test run, and it's not clear why
Finally, while not exactly apples-to-apples, Sasa's HAMT is easily 3-4× faster than F#'s map given the numbers cited in the above Stackoverflow post. F# still has an advantage for very small collections though. Sasa's HAMT also appears to be at least 2× faster than the new immutable collections.
Also keep in mind that this benchmark only tests lookup performance. F#'s map would have an advantage over Sasa's HAMT in load performance because the HAMT does not yet include a "bulk-load" operation, which the F# map does appear to support.
Comments