/* Copyright (c) 1992-1999 by David Moore. All rights reserved. */
/* $Id: hashtab.c,v 1.14 2001/01/21 19:27:15 dmoore Exp $ */
/* #include "config.h" */
#include <stdlib.h> /* NULL */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include "hashtab.h"
#define log_info printf
struct hash_tab {
const char *id; /* ''Name'' of hashtable, for stat display. */
compare_function cmpfn; /* Function to compare two entries. */
key_function keyfn; /* Function to generate a key for an entry. */
delete_function delfn; /* Function called when freeing an entry. */
unsigned long size; /* Size of the hashtable. */
struct hash_entry **table; /* Actual hashtable array. */
unsigned long walk_key; /* Current bucket when walking hash table. */
struct hash_entry *walk_loc; /* Current entry when walking hash table. */
int inside_walk; /* Boolean set true when walking hash table. */
unsigned long entries; /* Total number of entries. */
unsigned long max_entries; /* Max # of entries ever stored. */
unsigned long hits; /* Total hits. */
unsigned long hits_visited; /* Total number of entries visited on hits. */
unsigned long misses; /* total misses. */
unsigned long misses_visited; /* Total number of entried visited misses. */
unsigned long total_insert; /* Total number of entries ever inserted. */
struct hash_tab *next; /* Keep all hashtables in linked list. */
struct hash_tab **prev;
};
struct hash_entry {
struct hash_entry *next; /* Linked list buckets for collisions. */
const void *data; /* Actual entry we are storing. */
};
#ifndef MALLOC
#define MALLOC(r, t, c) ((r) = (t *) malloc(sizeof(t)*(c)))
#define FREE(r) do { free(r); (r) = 0; } while (0)
#endif
#ifndef MAKE_ALLOCATOR
#define MAKE_ALLOCATOR(NAME) \
union allocator_##NAME##_un { \
struct NAME info; \
union allocator_##NAME##_un *next; \
}; \
static union allocator_##NAME##_un *available_##NAME##s = NULL; \
static long total_##NAME##s = 0; \
static void generate_##NAME##s(long count) \
{ \
union allocator_##NAME##_un *hunk; \
long block_size, num, i; \
while (count > 0) { \
block_size = 4096; \
while ((count * sizeof(union allocator_##NAME##_un)) >= \
(2*block_size) - 4) { \
block_size *= 2; \
} \
num = (block_size - 4) / sizeof(union allocator_##NAME##_un); \
MALLOC(hunk, union allocator_##NAME##_un, num); \
if (!hunk) { \
available_##NAME##s = NULL; \
return; \
} \
for (i = 0; i < num; i++) { \
hunk->next = available_##NAME##s; \
available_##NAME##s = hunk; \
hunk++; \
} \
count -= num; \
} \
} \
static struct NAME *alloc_##NAME(void) \
{ \
struct NAME *result; \
if (!available_##NAME##s) generate_##NAME##s(1); \
if (!available_##NAME##s) return NULL; \
result = (struct NAME *) available_##NAME##s; \
available_##NAME##s = available_##NAME##s->next; \
total_##NAME##s++; \
return result; \
} \
static void free_##NAME(struct NAME *i) \
{ \
union allocator_##NAME##_un *x = (union allocator_##NAME##_un *) i; \
x->next = available_##NAME##s; \
available_##NAME##s = x; \
total_##NAME##s--; \
}
#endif
MAKE_ALLOCATOR(hash_entry)
/* Linked list of all hash tables we are managing. */
static struct hash_tab *hash_table_list = NULL;
/* These are the generic hashtable routines. They _require_ that the first
member of the entry be a string, and that string is taken as the key.
The generic delete function simply free's it's argument. */
int compare_generic_hash(const void *entry1, const void *entry2)
{
const char *str1 = (const char *) entry1;
const char *str2 = (const char *) entry2;
if (str1 == str2) return 0; /* Same string. */
if (!str1) str1 = "";
if (!str2) str2 = "";
while (*str1 && tolower(*str1) == tolower(*str2)) {
str1++;
str2++;
}
return ((unsigned char) tolower(*str1)) -
((unsigned char) tolower(*str2));
}
unsigned long make_key_generic_hash(const void *entry)
{
const char *s = (const char *) entry;
unsigned long hashval;
if (!s) return 0;
for (hashval = 0; *s != '\0'; s++)
hashval = (((unsigned int) *s) | 0x20) + 31 * hashval;
return hashval;
}
void delete_generic_hash(void *entry)
{
FREE(entry);
}
static unsigned long compute_hash(hash_tab *table, const void *data)
{
return (table->keyfn(data) % table->size);
}
static struct hash_entry *find_hash_internal(hash_tab *table, const unsigned long hashval, const void *match, struct hash_entry ***return_prev)
{
struct hash_entry *curr;
struct hash_entry **prev;
compare_function cmpfn;
int visits = 0;
if (!table) return NULL;
cmpfn = table->cmpfn;
prev = &(table->table[hashval]);
for (curr = *prev; curr; prev = &(curr->next), curr = curr->next) {
visits++;
if (cmpfn(match, curr->data) == 0) {
if (!table->inside_walk) {
/* Move this entry to the head of the bucket. */
*prev = curr->next;
curr->next = table->table[hashval];
table->table[hashval] = curr;
prev = &(table->table[hashval]);
}
/* Mark this hit for statistics. */
table->hits_visited += visits;
table->hits++;
if (return_prev) *return_prev = prev;
return curr;
}
}
table->misses_visited += visits + 1;
table->misses++;
return NULL; /* not found */
}
void *find_hash_entry(hash_tab *table, const void *match)
{
struct hash_entry *entry;
entry = find_hash_internal(table, compute_hash(table, match), match, NULL);
return (entry) ? (void*)(entry->data) : NULL;
}
int add_hash_entry(hash_tab *table, const void *data)
{
struct hash_entry *entry;
unsigned long hashval;
if (!table) return 1;
hashval = compute_hash(table, data);
entry = find_hash_internal(table, hashval, data, NULL);
if (entry) {
/* Free old entry. */
table->delfn((void *) entry->data);
} else {
entry = alloc_hash_entry();
if (!entry) {
fprintf(stderr, "Error adding hash entry: %s\n", strerror(errno));
return 1;
}
entry->next = table->table[hashval];
table->table[hashval] = entry;
table->entries++;
if (table->entries > table->max_entries)
table->max_entries = table->entries;
table->total_insert++;
}
/* Either we just created it, or we already found a valid one. */
entry->data = data;
return 0;
}
void clear_hash_entry(hash_tab *table, const void *data)
{
struct hash_entry *entry;
struct hash_entry **prev;
unsigned long hashval;
if (!table) return;
hashval = compute_hash(table, data);
entry = find_hash_internal(table, hashval, data, &prev);
if (!entry) return;
/* Found one. Remove from linked list. */
*prev = entry->next;
/* If entry was the next location in a hash walk, skip it. */
if (table->walk_loc == entry) {
table->walk_loc = entry->next;
}
table->entries--;
table->delfn((void *) entry->data);
free_hash_entry(entry);
}
void free_hash_table(hash_tab *table)
{
struct hash_entry **temp; /* In the table array. */
struct hash_entry *entry, *next;
unsigned long i, size;
if (!table) return;
size = table->size;
temp = table->table;
for (i = 0; i < size; i++, temp++) {
for (entry = *temp; entry; entry = next) {
next = entry->next; /* Save it, since we free next. */
table->delfn((void *) entry->data);
free_hash_entry(entry);
}
}
/* Remove from our linked list of tables. */
*(table->prev) = table->next;
if (table->next)
table->next->prev = table->prev;
FREE(table->table);
FREE(table);
}
void clear_hash_table(hash_tab *table)
{
struct hash_entry **temp; /* In the table array. */
struct hash_entry *entry, *next;
unsigned long i, size;
if (!table) return;
size = table->size;
temp = table->table;
for (i = 0; i < size; i++, temp++) {
for (entry = *temp; entry; entry = next) {
next = entry->next; /* Save it, since we free next. */
table->delfn((void *) entry->data);
free_hash_entry(entry);
}
}
/* Clear out the actual table. */
temp = table->table;
for (i = 0; i < size; i++, temp++)
*temp = NULL;
table->entries = 0;
}
hash_tab *init_hash_table(const char *id, compare_function cmpfn, key_function keyfn, delete_function delfn, unsigned long size)
{
hash_tab *table;
struct hash_entry **temp;
unsigned long i;
/* Malloc everything. */
MALLOC(table, hash_tab, 1);
if (!table) {
fprintf(stderr, "malloc(%u): %s\n", sizeof(hash_tab), strerror(errno));
return 0;
}
MALLOC(table->table, struct hash_entry *, size);
if (!table->table) {
fprintf(stderr, "malloc(%lu): %s\n", sizeof(struct hash_entry*) * size,
strerror(errno));
return 0;
}
/* Clear out the actual table. */
temp = table->table;
for (i = 0; i < size; i++, temp++)
*temp = NULL;
/* Fill in everything. */
table->id = id;
table->cmpfn = cmpfn;
table->keyfn = keyfn;
table->delfn = delfn;
table->size = size;
table->walk_key = 0;
table->walk_loc = NULL;
table->inside_walk = 0;
table->entries = 0;
table->max_entries = 0;
table->hits = 0;
table->hits_visited = 0;
table->misses = 0;
table->misses_visited = 0;
table->total_insert = 0;
/* Stick in our linked list of hash tables. */
if (hash_table_list)
hash_table_list->prev = &(table->next);
table->next = hash_table_list;
table->prev = &hash_table_list;
hash_table_list = table;
return table;
}
/* Can do lookups during a walk, but insertions and deletions may
cause problems. */
void init_hash_walk(hash_tab *table)
{
table->walk_key = 0;
table->walk_loc = NULL;
table->inside_walk = 1;
}
void *next_hash_walk(hash_tab *table)
{
struct hash_entry **temp;
const void *result;
unsigned long i, size;
if (table->walk_loc) {
result = table->walk_loc->data;
table->walk_loc = table->walk_loc->next;
return (void*)result;
}
size = table->size;
temp = table->table + table->walk_key;
for (i = table->walk_key; i < size; i++, temp++) {
if (*temp) break;
}
if (i == table->size) {
table->inside_walk = 0;
return NULL;
}
result = (*temp)->data;
table->walk_loc = (*temp)->next;
table->walk_key = i + 1;
return (void*)result;
}
void dump_hashtab_stats(hash_tab *table)
{
int doing_all = 0;
long total_ratio, hit_ratio, miss_ratio;
double t;
if (table == NULL) {
/* Dump information on all of the hashtables. */
doing_all = 1;
table = hash_table_list;
}
for (; table; table = table->next) {
if (table->hits_visited + table->misses_visited) {
t = 1000 * (((double) table->hits_visited + (double) table->misses_visited) / ((double) table->hits + (double) table->misses));
total_ratio = t;
} else total_ratio = 0;
if (table->hits_visited) {
t = 1000 * ((double) table->hits_visited / (double) table->hits);
hit_ratio = t;
} else hit_ratio = 0;
if (table->misses_visited) {
t = 1000 * ((double) table->misses_visited / (double) table->misses);
miss_ratio = t;
} else miss_ratio = 0;
log_info("\n");
log_info("--- Statistics for: %s\n",
table->id);
log_info("Table size: %ld. Entries: %ld.\n",
table->size, table->entries);
log_info("Total accesses: %ld.\n",
table->hits + table->misses);
log_info("Hit accesses: %ld. Miss accesses: %ld.\n",
table->hits, table->misses);
log_info("Bucket probes requiring comparison: %ld\n",
table->hits_visited + table->misses_visited);
log_info("Hit compares: %ld. Miss compares: %ld.\n",
table->hits_visited, table->misses_visited);
/* the closer this is to 1000 the better the hash fxn */
log_info("Total access ratio: %ld (/1000)\n",
total_ratio);
log_info("Hit ratio: %ld. Miss ratio: %ld. (/1000)\n",
hit_ratio, miss_ratio);
log_info("Max entries stored: %ld.\n",
table->max_entries);
if (!doing_all) break;
}
}
long total_hash_entries(void)
{
/* #### MAKE_ALLOCATOR can't pluralize well :) */
return total_hash_entrys;
}
void init_hash_entries(const long count)
{
/* #### MAKE_ALLOCATOR can't pluralize well :) */
generate_hash_entrys(count);
}
extern long num_hash_entries(hash_tab *table)
{
return table->entries;
}
syntax highlighted by Code2HTML, v. 0.9.1