kmod/libkmod/libkmod-hash.c
Lucas De Marchi 82fc7d986c libkmod-hash: always align n_buckets to power of 2
By aligning n_buckets to power of 2 we can turn the "bucket = hashval %
n_buckets" into a less expensive bucket = hashval & (n_buckets - 1).
This removes the DIV instruction as shown below.

Before:
	xor    %edx,%edx
	divl   0x8(%rbx)
	mov    %edx,%eax
	add    $0x1,%rax
	shl    $0x4,%rax
	add    %rbx,%rax

After:
	lea    -0x1(%rdi),%edx
	and    %edx,%eax
	add    $0x1,%rax
	shl    $0x4,%rax
	add    %rbx,%rax

With a microbenchmark, measuring the time to locate the bucket (i.e.
time_to_calculate_hashval + time_to_calculate_bucket_position) we have
the results below (time in clock cycles):

	keylen      before   after
	2-10          79.0    61.9 (-21.65%)
	11-17         81.0    64.4 (-20.48%)
	18-25         90.0    73.2 (-18.69%)
	26-32        104.7    87.0 (-16.82%)
	33-40        108.4    89.6 (-17.37%)
	41-48        111.2    91.9 (-17.38%)
	49-55        120.1   102.1 (-15.04%)
	56-63        134.4   115.7 (-13.91%)

As expected the gain is constant, regardless of the key length.
The time to clculate the hashval varies with the key length, which
explains the bigger gains for short keys.
2013-09-20 01:10:37 -05:00

339 lines
8.0 KiB
C

/*
* libkmod - interface to kernel module operations
*
* Copyright (C) 2011-2013 ProFUSION embedded systems
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libkmod.h"
#include "libkmod-hash.h"
#include "libkmod-util.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
struct hash_entry {
const char *key;
const void *value;
};
struct hash_bucket {
struct hash_entry *entries;
unsigned int used;
unsigned int total;
};
struct hash {
unsigned int count;
unsigned int step;
unsigned int n_buckets;
void (*free_value)(void *value);
struct hash_bucket buckets[];
};
struct hash *hash_new(unsigned int n_buckets,
void (*free_value)(void *value))
{
struct hash *hash;
n_buckets = ALIGN_POWER2(n_buckets);
hash = calloc(1, sizeof(struct hash) +
n_buckets * sizeof(struct hash_bucket));
if (hash == NULL)
return NULL;
hash->n_buckets = n_buckets;
hash->free_value = free_value;
hash->step = n_buckets / 32;
if (hash->step == 0)
hash->step = 4;
else if (hash->step > 64)
hash->step = 64;
return hash;
}
void hash_free(struct hash *hash)
{
struct hash_bucket *bucket, *bucket_end;
if (hash == NULL)
return;
bucket = hash->buckets;
bucket_end = bucket + hash->n_buckets;
for (; bucket < bucket_end; bucket++) {
if (hash->free_value) {
struct hash_entry *entry, *entry_end;
entry = bucket->entries;
entry_end = entry + bucket->used;
for (; entry < entry_end; entry++)
hash->free_value((void *)entry->value);
}
free(bucket->entries);
}
free(hash);
}
static inline unsigned int hash_superfast(const char *key, unsigned int len)
{
/* Paul Hsieh (http://www.azillionmonkeys.com/qed/hash.html)
* used by WebCore (http://webkit.org/blog/8/hashtables-part-2/)
* EFL's eina and possible others.
*/
unsigned int tmp, hash = len, rem = len & 3;
len /= 4;
/* Main loop */
for (; len > 0; len--) {
hash += get_unaligned((uint16_t *) key);
tmp = (get_unaligned((uint16_t *)(key + 2)) << 11) ^ hash;
hash = (hash << 16) ^ tmp;
key += 4;
hash += hash >> 11;
}
/* Handle end cases */
switch (rem) {
case 3:
hash += get_unaligned((uint16_t *) key);
hash ^= hash << 16;
hash ^= key[2] << 18;
hash += hash >> 11;
break;
case 2:
hash += get_unaligned((uint16_t *) key);
hash ^= hash << 11;
hash += hash >> 17;
break;
case 1:
hash += *key;
hash ^= hash << 10;
hash += hash >> 1;
}
/* Force "avalanching" of final 127 bits */
hash ^= hash << 3;
hash += hash >> 5;
hash ^= hash << 4;
hash += hash >> 17;
hash ^= hash << 25;
hash += hash >> 6;
return hash;
}
/*
* add or replace key in hash map.
*
* none of key or value are copied, just references are remembered as is,
* make sure they are live while pair exists in hash!
*/
int hash_add(struct hash *hash, const char *key, const void *value)
{
unsigned int keylen = strlen(key);
unsigned int hashval = hash_superfast(key, keylen);
unsigned int pos = hashval & (hash->n_buckets - 1);
struct hash_bucket *bucket = hash->buckets + pos;
struct hash_entry *entry, *entry_end;
if (bucket->used + 1 >= bucket->total) {
unsigned new_total = bucket->total + hash->step;
size_t size = new_total * sizeof(struct hash_entry);
struct hash_entry *tmp = realloc(bucket->entries, size);
if (tmp == NULL)
return -errno;
bucket->entries = tmp;
bucket->total = new_total;
}
entry = bucket->entries;
entry_end = entry + bucket->used;
for (; entry < entry_end; entry++) {
int c = strcmp(key, entry->key);
if (c == 0) {
if (hash->free_value)
hash->free_value((void *)entry->value);
entry->value = value;
return 0;
} else if (c < 0) {
memmove(entry + 1, entry,
(entry_end - entry) * sizeof(struct hash_entry));
break;
}
}
entry->key = key;
entry->value = value;
bucket->used++;
hash->count++;
return 0;
}
/* similar to hash_add(), but fails if key already exists */
int hash_add_unique(struct hash *hash, const char *key, const void *value)
{
unsigned int keylen = strlen(key);
unsigned int hashval = hash_superfast(key, keylen);
unsigned int pos = hashval & (hash->n_buckets - 1);
struct hash_bucket *bucket = hash->buckets + pos;
struct hash_entry *entry, *entry_end;
if (bucket->used + 1 >= bucket->total) {
unsigned new_total = bucket->total + hash->step;
size_t size = new_total * sizeof(struct hash_entry);
struct hash_entry *tmp = realloc(bucket->entries, size);
if (tmp == NULL)
return -errno;
bucket->entries = tmp;
bucket->total = new_total;
}
entry = bucket->entries;
entry_end = entry + bucket->used;
for (; entry < entry_end; entry++) {
int c = strcmp(key, entry->key);
if (c == 0)
return -EEXIST;
else if (c < 0) {
memmove(entry + 1, entry,
(entry_end - entry) * sizeof(struct hash_entry));
break;
}
}
entry->key = key;
entry->value = value;
bucket->used++;
hash->count++;
return 0;
}
static int hash_entry_cmp(const void *pa, const void *pb)
{
const struct hash_entry *a = pa;
const struct hash_entry *b = pb;
return strcmp(a->key, b->key);
}
void *hash_find(const struct hash *hash, const char *key)
{
unsigned int keylen = strlen(key);
unsigned int hashval = hash_superfast(key, keylen);
unsigned int pos = hashval & (hash->n_buckets - 1);
const struct hash_bucket *bucket = hash->buckets + pos;
const struct hash_entry se = {
.key = key,
.value = NULL
};
const struct hash_entry *entry = bsearch(
&se, bucket->entries, bucket->used,
sizeof(struct hash_entry), hash_entry_cmp);
if (entry == NULL)
return NULL;
return (void *)entry->value;
}
int hash_del(struct hash *hash, const char *key)
{
unsigned int keylen = strlen(key);
unsigned int hashval = hash_superfast(key, keylen);
unsigned int pos = hashval & (hash->n_buckets - 1);
unsigned int steps_used, steps_total;
struct hash_bucket *bucket = hash->buckets + pos;
struct hash_entry *entry, *entry_end;
const struct hash_entry se = {
.key = key,
.value = NULL
};
entry = bsearch(&se, bucket->entries, bucket->used,
sizeof(struct hash_entry), hash_entry_cmp);
if (entry == NULL)
return -ENOENT;
if (hash->free_value)
hash->free_value((void *)entry->value);
entry_end = bucket->entries + bucket->used;
memmove(entry, entry + 1,
(entry_end - entry) * sizeof(struct hash_entry));
bucket->used--;
hash->count--;
steps_used = bucket->used / hash->step;
steps_total = bucket->total / hash->step;
if (steps_used + 1 < steps_total) {
size_t size = (steps_used + 1) *
hash->step * sizeof(struct hash_entry);
struct hash_entry *tmp = realloc(bucket->entries, size);
if (tmp) {
bucket->entries = tmp;
bucket->total = (steps_used + 1) * hash->step;
}
}
return 0;
}
unsigned int hash_get_count(const struct hash *hash)
{
return hash->count;
}
void hash_iter_init(const struct hash *hash, struct hash_iter *iter)
{
iter->hash = hash;
iter->bucket = 0;
iter->entry = -1;
}
bool hash_iter_next(struct hash_iter *iter, const char **key,
const void **value)
{
const struct hash_bucket *b = iter->hash->buckets + iter->bucket;
const struct hash_entry *e;
iter->entry++;
if (iter->entry >= b->used) {
iter->entry = 0;
for (iter->bucket++; iter->bucket < iter->hash->n_buckets;
iter->bucket++) {
b = iter->hash->buckets + iter->bucket;
if (b->used > 0)
break;
}
if (iter->bucket >= iter->hash->n_buckets)
return false;
}
e = b->entries + iter->entry;
if (value != NULL)
*value = e->value;
if (key != NULL)
*key = e->key;
return true;
}