ntdb: remove hash table trees.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 19 Jun 2012 03:13:04 +0000 (12:43 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 19 Jun 2012 03:38:07 +0000 (05:38 +0200)
commitdd42962878ab7c9ddfa79d7c32094fb6748017b8
treea614af427c5ad0d962db77a58f133cb39c9bd057
parentf986554b1e38d8dd40b4bf4748d4aeb470e27d2e
ntdb: remove hash table trees.

TDB2 started with a top-level hash of 1024 entries, divided into 128
groups of 8 buckets.  When a bucket filled, the 8 bucket group
expanded into pointers into 8 new 64-entry hash tables.  When these
filled, they expanded in turn, etc.

It's a nice idea to automatically expand the hash tables, but it
doesn't pay off.  Remove it for NTDB.

1) It only beats TDB performance when the database is huge and the
   TDB hashsize is small.  We are about 20% slower on medium-size
   databases (1000 to 10000 records), worse on really small ones.
2) Since we're 64 bits, our hash tables are already twice as expensive
   as TDB.
3) Since our hash function is good, it means that all groups tend to
   fill at the same time, meaning the hash enlarges by a factor of 128
   all at once, leading to a very large database at that point.
4) Our efficiency would improve if we enlarged the top level, but
   that makes our minimum db size even worse: it's already over 8k,
   and jumps to 1M after about 1000 entries!
5) Making the sub group size larger gives a shallower tree, which
   performs better, but makes the "hash explosion" problem worse.
6) The code is complicated, having to handle delete and reshuffling
   groups of hash buckets, and expansion of buckets.
7) We have to handle the case where all the records somehow end up with
   the same hash value, which requires special code to chain records for
   that case.

On the other hand, it would be nice if we didn't degrade as badly as
TDB does when the hash chains get long.

This patch removes the hash-growing code, but instead of chaining like
TDB does when a bucket fills, we point the bucket to an array of
record pointers.  Since each on-disk NTDB pointer contains some hash
bits from the record (we steal the upper 8 bits of the offset), 99.5%
of the time we don't need to load the record to determine if it
matches.  This makes an array of offsets much more cache-friendly than
a linked list.

Here are the times (in ns) for tdb_store of N records, tdb_store of N
records the second time, and a fetch of all N records.  I've also
included the final database size and the smbtorture local.[n]tdb_speed
results.

Benchmark details:
1) Compiled with -O2.
2) assert() was disabled in TDB2 and NTDB.
3) The "optimize fetch" patch was applied to NTDB.

10 runs, using tmpfs (otherwise massive swapping as db hits ~30M,
despite plenty of RAM).

Insert Re-ins Fetch Size dbspeed
(nsec) (nsec) (nsec) (Kb) (ops/sec)
TDB (10000 hashsize):
100 records:  3882  3320 1609    53 203204
1000 records:  3651  3281 1571   115 218021
10000 records:  3404  3326 1595   880 202874
100000 records:  4317  3825 2097  8262 126811
1000000 records: 11568 11578 9320 77005  25046

TDB2 (1024 hashsize, expandable):
100 records:  3867  3329 1699    17 187100
1000 records:  4040  3249 1639   154 186255
10000 records:  4143  3300 1695  1226 185110
100000 records:  4481  3425 1800 17848 163483
1000000 records:  4055  3534 1878   106386 160774

NTDB (8192 hashsize)
100 records:  4259  3376 1692    82 190852
1000 records:  3640  3275 1566   130 195106
10000 records:  4337  3438 1614   773 188362
100000 records:  4750  5165 1746  9001 169197
1000000 records:  4897  5180 2341 83838 121901

Analysis:
1) TDB wins on small databases, beating TDB2 by ~15%, NTDB by ~10%.
2) TDB starts to lose when hash chains get 10 long (fetch 10% slower
   than TDB2/NTDB).
3) TDB does horribly when hash chains get 100 long (fetch 4x slower
   than NTDB, 5x slower than TDB2, insert about 2-3x slower).
4) TDB2 databases are 40% larger than TDB1.  NTDB is about 15% larger
   than TDB1
36 files changed:
lib/ntdb/check.c
lib/ntdb/free.c
lib/ntdb/hash.c
lib/ntdb/io.c
lib/ntdb/lock.c
lib/ntdb/ntdb.c
lib/ntdb/ntdb.h
lib/ntdb/open.c
lib/ntdb/private.h
lib/ntdb/summary.c
lib/ntdb/test/api-12-store.c
lib/ntdb/test/api-13-delete.c
lib/ntdb/test/api-20-alloc-attr.c
lib/ntdb/test/api-82-lockattr.c
lib/ntdb/test/api-check-callback.c
lib/ntdb/test/api-missing-entries.c
lib/ntdb/test/helprun-layout.c
lib/ntdb/test/layout.h
lib/ntdb/test/run-001-encode.c
lib/ntdb/test/run-02-expand.c
lib/ntdb/test/run-03-coalesce.c
lib/ntdb/test/run-04-basichash.c
lib/ntdb/test/run-15-append.c
lib/ntdb/test/run-20-growhash.c [deleted file]
lib/ntdb/test/run-25-hashoverload.c
lib/ntdb/test/run-30-exhaust-before-expand.c
lib/ntdb/test/run-35-convert.c
lib/ntdb/test/run-50-multiple-freelists.c
lib/ntdb/test/run-64-bit-tdb.c
lib/ntdb/test/run-90-get-set-attributes.c
lib/ntdb/test/run-capabilities.c
lib/ntdb/test/run-expand-in-transaction.c
lib/ntdb/test/run-traverse.c
lib/ntdb/tools/speed.c
lib/ntdb/traverse.c
lib/ntdb/wscript