From ed4f4103f4f407ed99dd6cd25b6c35d3aa9f3479 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 14 Jan 2014 01:01:54 +0000 Subject: [PATCH] st: use power-of-two sizes to avoid slow modulo ops Prime number-sized hash tables are only needed to compensate for bad hash functions. Ruby has good hash functions nowadays, so reduce our code size with power-of-two-sized hash tables which allows us to avoid the slow modulo operation. I expected numhash performance to be worse, but it seems most of those hashes are either too-small-to-matter or well-distributed anyways. If we find problems with some existing numhashes we should start using a proper hash function (Murmur via st_hash_uint) on those. This consistently speeds up the bm_hash_flatten and bm_vm2_bighash. target 0: trunk (ruby 2.2.0dev (2014-01-17 trunk 44631) [x86_64-linux]) at "/home/ew/rrrr/i/bin/ruby --disable=gems" target 1: st-noprime (ruby 2.2.0dev (2014-01-17 trunk 44631) [x86_64-linux]) at "/home/ew/ruby/i/bin/ruby --disable=gems" Benchmarks on a Xeon E3-1230 v3 CPU: minimum results in each 10 measurements. Execution time (sec) name trunk st-noprime hash_flatten 0.500 0.345 hash_keys 0.191 0.192 hash_shift 0.019 0.018 hash_values 0.201 0.200 loop_whileloop2 0.090 0.090 vm2_bighash* 4.457 3.578 Speedup ratio: compare with the result of `trunk' (greater is better) name st-noprime hash_flatten 1.451 hash_keys 0.998 hash_shift 1.046 hash_values 1.003 loop_whileloop2 1.000 vm2_bighash* 1.246 Somewhat less impressive on an AMD FX 8320: minimum results in each 10 measurements. Execution time (sec) name trunk st-noprime hash_flatten 0.633 0.596 hash_keys 0.236 0.232 hash_shift 0.031 0.032 hash_values 0.234 0.238 loop_whileloop2 0.135 0.135 vm2_bighash* 8.198 6.982 Speedup ratio: compare with the result of `trunk' (greater is better) name st-noprime hash_flatten 1.063 hash_keys 1.020 hash_shift 0.976 hash_values 0.982 loop_whileloop2 1.000 vm2_bighash* 1.174 --- st.c | 100 +++++++++++++++++-------------------------------------------------- 1 file changed, 25 insertions(+), 75 deletions(-) diff --git a/st.c b/st.c index 2335fdd..6d70a1b 100644 --- a/st.c +++ b/st.c @@ -33,8 +33,8 @@ typedef struct st_packed_entry { #define STATIC_ASSERT(name, expr) typedef int static_assert_##name##_check[(expr) ? 1 : -1]; #define ST_DEFAULT_MAX_DENSITY 5 -#define ST_DEFAULT_INIT_TABLE_SIZE 11 -#define ST_DEFAULT_SECOND_TABLE_SIZE 19 +#define ST_DEFAULT_INIT_TABLE_SIZE 16 +#define ST_DEFAULT_SECOND_TABLE_SIZE 32 #define ST_DEFAULT_PACKED_TABLE_SIZE 18 #define PACKED_UNIT (int)(sizeof(st_packed_entry) / sizeof(st_table_entry*)) #define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry)) @@ -85,7 +85,8 @@ static void rehash(st_table *); #define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0) #define do_hash(key,table) (st_index_t)(*(table)->type->hash)((key)) -#define do_hash_bin(key,table) (do_hash((key), (table))%(table)->num_bins) +#define HMASK(tbl) ((tbl)->num_bins-1) +#define do_hash_bin(key,table) (do_hash((key), (table))&(HMASK(table))) /* preparation for possible allocation improvements */ #define st_alloc_entry() (st_table_entry *)malloc(sizeof(st_table_entry)) @@ -139,69 +140,18 @@ remove_safe_packed_entry(st_table *table, st_index_t i, st_data_t never) PHASH_SET(table, i, 0); } -/* - * MINSIZE is the minimum size of a dictionary. - */ - -#define MINSIZE 8 - -/* -Table of prime numbers 2^n+a, 2<=n<=30. -*/ -static const unsigned int primes[] = { - ST_DEFAULT_INIT_TABLE_SIZE, - ST_DEFAULT_SECOND_TABLE_SIZE, - 32 + 5, - 64 + 3, - 128 + 3, - 256 + 27, - 512 + 9, - 1024 + 9, - 2048 + 5, - 4096 + 3, - 8192 + 27, - 16384 + 43, - 32768 + 3, - 65536 + 45, - 131072 + 29, - 262144 + 3, - 524288 + 21, - 1048576 + 7, - 2097152 + 17, - 4194304 + 15, - 8388608 + 9, - 16777216 + 43, - 33554432 + 35, - 67108864 + 15, - 134217728 + 29, - 268435456 + 3, - 536870912 + 11, - 1073741824 + 85, - 0 -}; - static st_index_t new_size(st_index_t size) { - int i; + st_index_t i; -#if 0 for (i=3; i<31; i++) { - if ((1< size) return 1< size) return primes[i]; + if ((st_index_t)(1< size) return 1<num_bins))) + ((ptr) = find_entry((table), key, (hash_val), ((bin_pos) = (hash_val)&(HMASK(table))))) static st_table_entry * find_entry(st_table *table, st_data_t key, st_index_t hash_val, st_index_t bin_pos) @@ -422,7 +372,7 @@ st_lookup(st_table *table, register st_data_t key, st_data_t *value) return 0; } - ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); + ptr = find_entry(table, key, hash_val, hash_val & HMASK(table)); if (ptr == 0) { return 0; @@ -450,7 +400,7 @@ st_get_key(st_table *table, register st_data_t key, st_data_t *result) return 0; } - ptr = find_entry(table, key, hash_val, hash_val % table->num_bins); + ptr = find_entry(table, key, hash_val, hash_val & HMASK(table)); if (ptr == 0) { return 0; @@ -486,7 +436,7 @@ add_direct(st_table *table, st_data_t key, st_data_t value, register st_table_entry *entry; if (table->num_entries > ST_DEFAULT_MAX_DENSITY * table->num_bins) { rehash(table); - bin_pos = hash_val % table->num_bins; + bin_pos = hash_val & HMASK(table); } entry = new_entry(table, key, value, hash_val, bin_pos); @@ -527,7 +477,7 @@ unpack_entries(register st_table *table) st_data_t val = packed_bins[i].val; st_index_t hash = packed_bins[i].hash; entry = new_entry(&tmp_table, key, val, hash, - hash % ST_DEFAULT_INIT_TABLE_SIZE); + hash & (ST_DEFAULT_INIT_TABLE_SIZE - 1)); *chain = entry; entry->back = preventry; preventry = entry; @@ -550,7 +500,7 @@ add_packed_direct(st_table *table, st_data_t key, st_data_t value, st_index_t ha } else { unpack_entries(table); - add_direct(table, key, value, hash_val, hash_val % table->num_bins); + add_direct(table, key, value, hash_val, hash_val & HMASK(table)); } } @@ -631,7 +581,7 @@ st_add_direct(st_table *table, st_data_t key, st_data_t value) return; } - add_direct(table, key, value, hash_val, hash_val % table->num_bins); + add_direct(table, key, value, hash_val, hash_val & HMASK(table)); } static void @@ -647,7 +597,7 @@ rehash(register st_table *table) if ((ptr = table->head) != 0) { do { - hash_val = ptr->hash % new_num_bins; + hash_val = ptr->hash & (new_num_bins - 1); ptr->next = new_bins[hash_val]; new_bins[hash_val] = ptr; } while ((ptr = ptr->fore) != 0); @@ -690,7 +640,7 @@ st_copy(st_table *old_table) return 0; } *entry = *ptr; - hash_val = entry->hash % num_bins; + hash_val = entry->hash & (num_bins - 1); entry->next = new_table->bins[hash_val]; new_table->bins[hash_val] = entry; entry->back = prev; @@ -741,7 +691,7 @@ st_delete(register st_table *table, register st_data_t *key, st_data_t *value) return 0; } - prev = &table->bins[hash_val % table->num_bins]; + prev = &table->bins[hash_val & HMASK(table)]; for (;(ptr = *prev) != 0; prev = &ptr->next) { if (EQUAL(table, *key, ptr->key)) { *prev = ptr->next; @@ -777,7 +727,7 @@ st_delete_safe(register st_table *table, register st_data_t *key, st_data_t *val return 0; } - ptr = table->bins[hash_val % table->num_bins]; + ptr = table->bins[hash_val & HMASK(table)]; for (; ptr != 0; ptr = ptr->next) { if ((ptr->key != never) && EQUAL(table, ptr->key, *key)) { @@ -811,7 +761,7 @@ st_shift(register st_table *table, register st_data_t *key, st_data_t *value) return 1; } - prev = &table->bins[table->head->hash % table->num_bins]; + prev = &table->bins[table->head->hash & HMASK(table)]; while ((ptr = *prev) != table->head) prev = &ptr->next; *prev = ptr->next; if (value != 0) *value = ptr->record; @@ -910,7 +860,7 @@ st_update(st_table *table, st_data_t key, st_update_callback_func *func, st_data switch (retval) { case ST_CONTINUE: if (!existing) { - add_direct(table, key, value, hash_val, hash_val % table->num_bins); + add_direct(table, key, value, hash_val, hash_val & HMASK(table)); break; } ptr->record = value; @@ -986,7 +936,7 @@ st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t do { if (ptr->key == never) goto unpacked_continue; - i = ptr->hash % table->num_bins; + i = ptr->hash & HMASK(table); retval = (*func)(ptr->key, ptr->record, arg, 0); unpacked: switch (retval) { @@ -1007,7 +957,7 @@ st_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t case ST_STOP: return 0; case ST_DELETE: - last = &table->bins[ptr->hash % table->num_bins]; + last = &table->bins[ptr->hash & HMASK(table)]; for (; (tmp = *last) != 0; last = &tmp->next) { if (ptr == tmp) { tmp = ptr->fore; @@ -1063,7 +1013,7 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) if (ptr != 0) { do { - i = ptr->hash % table->num_bins; + i = ptr->hash & HMASK(table); retval = (*func)(ptr->key, ptr->record, arg, 0); unpacked: switch (retval) { @@ -1074,7 +1024,7 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) case ST_STOP: return 0; case ST_DELETE: - last = &table->bins[ptr->hash % table->num_bins]; + last = &table->bins[ptr->hash & HMASK(table)]; for (; (tmp = *last) != 0; last = &tmp->next) { if (ptr == tmp) { tmp = ptr->fore; @@ -1218,7 +1168,7 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) retval = (*func)(ptr->key, ptr->record, arg, 0); switch (retval) { case ST_CHECK: /* check if hash is modified during iteration */ - i = ptr->hash % table->num_bins; + i = ptr->hash & HMASK(table)); for (tmp = table->bins[i]; tmp != ptr; tmp = tmp->next) { if (!tmp) { /* call func with error notice */ @@ -1233,7 +1183,7 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) case ST_STOP: return 0; case ST_DELETE: - last = &table->bins[ptr->hash % table->num_bins]; + last = &table->bins[ptr->hash & HMASK(table)]; for (; (tmp = *last) != 0; last = &tmp->next) { if (ptr == tmp) { tmp = ptr->back; -- 1.8.5.2.193.g2394e94