Merge remote-tracking branch 'kblees/kb/fscache-v4-tentative-1.8.5' into thicket-1.8.5.2

This commit is contained in:
Johannes Schindelin
2014-02-17 12:42:40 -06:00
21 changed files with 1707 additions and 90 deletions

View File

@@ -624,6 +624,12 @@ relatively high IO latencies. With this set to 'true', Git will do the
index comparison to the filesystem data in parallel, allowing
overlapping IO's.
core.fscache::
Enable additional caching of file system data for some operations.
+
Git for Windows uses this to bulk-read and cache lstat data of entire
directories (instead of doing lstat file by file).
core.createObject::
You can set this to 'link', in which case a hardlink followed by
a delete of the source are used to make sure that object creation

View File

@@ -0,0 +1,235 @@
hashmap API
===========
The hashmap API is a generic implementation of hash-based key-value mappings.
Data Structures
---------------
`struct hashmap`::
The hash table structure.
+
The `size` member keeps track of the total number of entries. The `cmpfn`
member is a function used to compare two entries for equality. The `table` and
`tablesize` members store the hash table and its size, respectively.
`struct hashmap_entry`::
An opaque structure representing an entry in the hash table, which must
be used as first member of user data structures. Ideally it should be
followed by an int-sized member to prevent unused memory on 64-bit
systems due to alignment.
+
The `hash` member is the entry's hash code and the `next` member points to the
next entry in case of collisions (i.e. if multiple entries map to the same
bucket).
`struct hashmap_iter`::
An iterator structure, to be used with hashmap_iter_* functions.
Types
-----
`int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key, const void *keydata)`::
User-supplied function to test two hashmap entries for equality. Shall
return 0 if the entries are equal.
+
This function is always called with non-NULL `entry` / `entry_or_key`
parameters that have the same hash code. When looking up an entry, the `key`
and `keydata` parameters to hashmap_get and hashmap_remove are always passed
as second and third argument, respectively. Otherwise, `keydata` is NULL.
Functions
---------
`unsigned int strhash(const char *buf)`::
`unsigned int strihash(const char *buf)`::
`unsigned int memhash(const void *buf, size_t len)`::
`unsigned int memihash(const void *buf, size_t len)`::
Ready-to-use hash functions for strings, using the FNV-1 algorithm (see
http://www.isthe.com/chongo/tech/comp/fnv).
+
`strhash` and `strihash` take 0-terminated strings, while `memhash` and
`memihash` operate on arbitrary-length memory.
+
`strihash` and `memihash` are case insensitive versions.
`void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function, size_t initial_size)`::
Initializes a hashmap structure.
+
`map` is the hashmap to initialize.
+
The `equals_function` can be specified to compare two entries for equality.
If NULL, entries are considered equal if their hash codes are equal.
+
If the total number of entries is known in advance, the `initial_size`
parameter may be used to preallocate a sufficiently large table and thus
prevent expensive resizing. If 0, the table is dynamically resized.
`void hashmap_free(struct hashmap *map, int free_entries)`::
Frees a hashmap structure and allocated memory.
+
`map` is the hashmap to free.
+
If `free_entries` is true, each hashmap_entry in the map is freed as well
(using stdlib's free()).
`void hashmap_entry_init(void *entry, int hash)`::
Initializes a hashmap_entry structure.
+
`entry` points to the entry to initialize.
+
`hash` is the hash code of the entry.
`void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata)`::
Returns the hashmap entry for the specified key, or NULL if not found.
+
`map` is the hashmap structure.
+
`key` is a hashmap_entry structure (or user data structure that starts with
hashmap_entry) that has at least been initialized with the proper hash code
(via `hashmap_entry_init`).
+
If an entry with matching hash code is found, `key` and `keydata` are passed
to `hashmap_cmp_fn` to decide whether the entry matches the key.
`void *hashmap_get_next(const struct hashmap *map, const void *entry)`::
Returns the next equal hashmap entry, or NULL if not found. This can be
used to iterate over duplicate entries (see `hashmap_add`).
+
`map` is the hashmap structure.
+
`entry` is the hashmap_entry to start the search from, obtained via a previous
call to `hashmap_get` or `hashmap_get_next`.
`void hashmap_add(struct hashmap *map, void *entry)`::
Adds a hashmap entry. This allows to add duplicate entries (i.e.
separate values with the same key according to hashmap_cmp_fn).
+
`map` is the hashmap structure.
+
`entry` is the entry to add.
`void *hashmap_put(struct hashmap *map, void *entry)`::
Adds or replaces a hashmap entry. If the hashmap contains duplicate
entries equal to the specified entry, only one of them will be replaced.
+
`map` is the hashmap structure.
+
`entry` is the entry to add or replace.
+
Returns the replaced entry, or NULL if not found (i.e. the entry was added).
`void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata)`::
Removes a hashmap entry matching the specified key. If the hashmap
contains duplicate entries equal to the specified key, only one of
them will be removed.
+
`map` is the hashmap structure.
+
`key` is a hashmap_entry structure (or user data structure that starts with
hashmap_entry) that has at least been initialized with the proper hash code
(via `hashmap_entry_init`).
+
If an entry with matching hash code is found, `key` and `keydata` are
passed to `hashmap_cmp_fn` to decide whether the entry matches the key.
+
Returns the removed entry, or NULL if not found.
`void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter)`::
`void *hashmap_iter_next(struct hashmap_iter *iter)`::
`void *hashmap_iter_first(struct hashmap *map, struct hashmap_iter *iter)`::
Used to iterate over all entries of a hashmap.
+
`hashmap_iter_init` initializes a `hashmap_iter` structure.
+
`hashmap_iter_next` returns the next hashmap_entry, or NULL if there are no
more entries.
+
`hashmap_iter_first` is a combination of both (i.e. initializes the iterator
and returns the first entry, if any).
Usage example
-------------
Here's a simple usage example that maps long keys to double values.
[source,c]
------------
struct hashmap map;
struct long2double {
struct hashmap_entry ent; /* must be the first member! */
long key;
double value;
};
static int long2double_cmp(const struct long2double *e1, const struct long2double *e2, const void *unused)
{
return !(e1->key == e2->key);
}
void long2double_init(void)
{
hashmap_init(&map, (hashmap_cmp_fn) long2double_cmp, 0);
}
void long2double_free(void)
{
hashmap_free(&map, 1);
}
static struct long2double *find_entry(long key)
{
struct long2double k;
hashmap_entry_init(&k, memhash(&key, sizeof(long)));
k.key = key;
return hashmap_get(&map, &k, NULL);
}
double get_value(long key)
{
struct long2double *e = find_entry(key);
return e ? e->value : 0;
}
void set_value(long key, double value)
{
struct long2double *e = find_entry(key);
if (!e) {
e = malloc(sizeof(struct long2double));
hashmap_entry_init(e, memhash(&key, sizeof(long)));
e->key = key;
hashmap_add(&map, e);
}
e->value = value;
}
------------
Using variable-sized keys
-------------------------
The `hashmap_entry_get` and `hashmap_entry_remove` functions expect an ordinary
`hashmap_entry` structure as key to find the correct entry. If the key data is
variable-sized (e.g. a FLEX_ARRAY string) or quite large, it is undesirable
to create a full-fledged entry structure on the heap and copy all the key data
into the structure.
In this case, the `keydata` parameter can be used to pass
variable-sized key data directly to the comparison function, and the `key`
parameter can be a stripped-down, fixed size entry structure allocated on the
stack.
See test-hashmap.c for an example using arbitrary-length strings as keys.

View File

@@ -555,6 +555,7 @@ TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-hashmap
TEST_PROGRAMS_NEED_X += test-index-version
TEST_PROGRAMS_NEED_X += test-line-buffer
TEST_PROGRAMS_NEED_X += test-match-trees
@@ -672,6 +673,7 @@ LIB_H += gpg-interface.h
LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
LIB_H += hashmap.h
LIB_H += help.h
LIB_H += http.h
LIB_H += kwset.h
@@ -803,6 +805,7 @@ LIB_OBJS += gpg-interface.o
LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hash.o
LIB_OBJS += hashmap.o
LIB_OBJS += help.o
LIB_OBJS += hex.o
LIB_OBJS += ident.o

View File

@@ -1289,6 +1289,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
PATHSPEC_PREFER_FULL,
prefix, argv);
enable_fscache(1);
read_cache_preload(&s.pathspec);
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);

View File

@@ -599,6 +599,8 @@ enum hide_dotfiles_type {
};
extern enum hide_dotfiles_type hide_dotfiles;
extern int core_fscache;
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,

View File

@@ -474,22 +474,6 @@ int mingw_chmod(const char *filename, int mode)
return _wchmod(wfilename, mode);
}
/*
* The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
* Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
*/
static inline long long filetime_to_hnsec(const FILETIME *ft)
{
long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
/* Windows to Unix Epoch conversion */
return winTime - 116444736000000000LL;
}
static inline time_t filetime_to_time_t(const FILETIME *ft)
{
return (time_t)(filetime_to_hnsec(ft) / 10000000);
}
/* We keep the do_lstat code in a separate function to avoid recursion.
* When a path ends with a slash, the stat will fail with ENOENT. In
* this case, we strip the trailing slashes and stat again.
@@ -590,6 +574,8 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
return do_lstat(follow, alt_name, buf);
}
int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
int mingw_lstat(const char *file_name, struct stat *buf)
{
return do_stat_internal(0, file_name, buf);

View File

@@ -274,6 +274,22 @@ static inline int getrlimit(int resource, struct rlimit *rlp)
return 0;
}
/*
* The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
* Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
*/
static inline long long filetime_to_hnsec(const FILETIME *ft)
{
long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
/* Windows to Unix Epoch conversion */
return winTime - 116444736000000000LL;
}
static inline time_t filetime_to_time_t(const FILETIME *ft)
{
return (time_t)(filetime_to_hnsec(ft) / 10000000);
}
/*
* Use mingw specific stat()/lstat()/fstat() implementations on Windows.
*/
@@ -295,7 +311,7 @@ int mingw_fstat(int fd, struct stat *buf);
#ifdef lstat
#undef lstat
#endif
#define lstat mingw_lstat
extern int (*lstat)(const char *file_name, struct stat *buf);
#ifndef _stati64
# define _stati64(x,y) mingw_stat(x,y)

View File

@@ -1,15 +1,19 @@
#include "../../git-compat-util.h"
struct DIR {
typedef struct dirent_DIR {
struct DIR base_dir; /* extend base struct DIR */
struct dirent dd_dir; /* includes d_type */
HANDLE dd_handle; /* FindFirstFile handle */
int dd_stat; /* 0-based index */
};
char dd_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
} dirent_DIR;
DIR *(*opendir)(const char *dirname) = dirent_opendir;
static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
{
/* convert UTF-16 name to UTF-8 */
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
/* convert UTF-16 name to UTF-8 (d_name points to dirent_DIR.dd_name) */
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
/* Set file type, based on WIN32_FIND_DATA */
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
@@ -18,41 +22,7 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
ent->d_type = DT_REG;
}
DIR *opendir(const char *name)
{
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
WIN32_FIND_DATAW fdata;
HANDLE h;
int len;
DIR *dir;
/* convert name to UTF-16 and check length < MAX_PATH */
if ((len = xutftowcs_path(pattern, name)) < 0)
return NULL;
/* append optional '/' and wildcard '*' */
if (len && !is_dir_sep(pattern[len - 1]))
pattern[len++] = '/';
pattern[len++] = '*';
pattern[len] = 0;
/* open find handle */
h = FindFirstFileW(pattern, &fdata);
if (h == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
return NULL;
}
/* initialize DIR structure and copy first dir entry */
dir = xmalloc(sizeof(DIR));
dir->dd_handle = h;
dir->dd_stat = 0;
finddata2dirent(&dir->dd_dir, &fdata);
return dir;
}
struct dirent *readdir(DIR *dir)
static struct dirent *dirent_readdir(dirent_DIR *dir)
{
if (!dir) {
errno = EBADF; /* No set_errno for mingw */
@@ -79,7 +49,7 @@ struct dirent *readdir(DIR *dir)
return &dir->dd_dir;
}
int closedir(DIR *dir)
static int dirent_closedir(dirent_DIR *dir)
{
if (!dir) {
errno = EBADF;
@@ -90,3 +60,40 @@ int closedir(DIR *dir)
free(dir);
return 0;
}
DIR *dirent_opendir(const char *name)
{
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
WIN32_FIND_DATAW fdata;
HANDLE h;
int len;
dirent_DIR *dir;
/* convert name to UTF-16 and check length < MAX_PATH */
if ((len = xutftowcs_path(pattern, name)) < 0)
return NULL;
/* append optional '/' and wildcard '*' */
if (len && !is_dir_sep(pattern[len - 1]))
pattern[len++] = '/';
pattern[len++] = '*';
pattern[len] = 0;
/* open find handle */
h = FindFirstFileW(pattern, &fdata);
if (h == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
return NULL;
}
/* initialize DIR structure and copy first dir entry */
dir = xmalloc(sizeof(dirent_DIR));
dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir;
dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir;
dir->dd_dir.d_name = dir->dd_name;
dir->dd_handle = h;
dir->dd_stat = 0;
finddata2dirent(&dir->dd_dir, &fdata);
return (DIR*) dir;
}

View File

@@ -1,20 +1,32 @@
#ifndef DIRENT_H
#define DIRENT_H
typedef struct DIR DIR;
#define DT_UNKNOWN 0
#define DT_DIR 1
#define DT_REG 2
#define DT_LNK 3
struct dirent {
unsigned char d_type; /* file type to prevent lstat after readdir */
char d_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
unsigned char d_type; /* file type to prevent lstat after readdir */
char *d_name; /* file name */
};
DIR *opendir(const char *dirname);
struct dirent *readdir(DIR *dir);
int closedir(DIR *dir);
/*
* Base DIR structure, contains pointers to readdir/closedir implementations so
* that opendir may choose a concrete implementation on a call-by-call basis.
*/
typedef struct DIR {
struct dirent *(*preaddir)(struct DIR *dir);
int (*pclosedir)(struct DIR *dir);
} DIR;
/* default dirent implementation */
extern DIR *dirent_opendir(const char *dirname);
/* current dirent implementation */
extern DIR *(*opendir)(const char *dirname);
#define readdir(dir) (dir->preaddir(dir))
#define closedir(dir) (dir->pclosedir(dir))
#endif /* DIRENT_H */

458
compat/win32/fscache.c Normal file
View File

@@ -0,0 +1,458 @@
#include "../../cache.h"
#include "../../hashmap.h"
#include "../win32.h"
#include "fscache.h"
static int initialized;
static volatile long enabled;
static struct hashmap map;
static CRITICAL_SECTION mutex;
/*
* An entry in the file system cache. Used for both entire directory listings
* and file entries.
*/
struct fsentry {
struct hashmap_entry ent;
mode_t st_mode;
/* Length of name. */
unsigned short len;
/*
* Name of the entry. For directory listings: relative path of the
* directory, without trailing '/' (empty for cwd()). For file entries:
* name of the file. Typically points to the end of the structure if
* the fsentry is allocated on the heap (see fsentry_alloc), or to a
* local variable if on the stack (see fsentry_init).
*/
const char *name;
/* Pointer to the directory listing, or NULL for the listing itself. */
struct fsentry *list;
/* Pointer to the next file entry of the list. */
struct fsentry *next;
union {
/* Reference count of the directory listing. */
volatile long refcnt;
struct {
/* More stat members (only used for file entries). */
off64_t st_size;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
};
};
};
/*
* Compares the paths of two fsentry structures for equality.
*/
static int fsentry_cmp(const struct fsentry *fse1, const struct fsentry *fse2)
{
int res;
if (fse1 == fse2)
return 0;
/* compare the list parts first */
if (fse1->list != fse2->list && (res = fsentry_cmp(
fse1->list ? fse1->list : fse1,
fse2->list ? fse2->list : fse2)))
return res;
/* if list parts are equal, compare len and name */
if (fse1->len != fse2->len)
return fse1->len - fse2->len;
return strnicmp(fse1->name, fse2->name, fse1->len);
}
/*
* Calculates the hash code of an fsentry structure's path.
*/
static unsigned int fsentry_hash(const struct fsentry *fse)
{
unsigned int hash = fse->list ? fse->list->ent.hash : 0;
return hash ^ memihash(fse->name, fse->len);
}
/*
* Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp.
*/
static void fsentry_init(struct fsentry *fse, struct fsentry *list,
const char *name, size_t len)
{
fse->list = list;
fse->name = name;
fse->len = len;
hashmap_entry_init(fse, fsentry_hash(fse));
}
/*
* Allocate an fsentry structure on the heap.
*/
static struct fsentry *fsentry_alloc(struct fsentry *list, const char *name,
size_t len)
{
/* overallocate fsentry and copy the name to the end */
struct fsentry *fse = xmalloc(sizeof(struct fsentry) + len + 1);
char *nm = ((char*) fse) + sizeof(struct fsentry);
memcpy(nm, name, len);
nm[len] = 0;
/* init the rest of the structure */
fsentry_init(fse, list, nm, len);
fse->next = NULL;
fse->refcnt = 1;
return fse;
}
/*
* Add a reference to an fsentry.
*/
inline static void fsentry_addref(struct fsentry *fse)
{
if (fse->list)
fse = fse->list;
InterlockedIncrement(&(fse->refcnt));
}
/*
* Release the reference to an fsentry, frees the memory if its the last ref.
*/
static void fsentry_release(struct fsentry *fse)
{
if (fse->list)
fse = fse->list;
if (InterlockedDecrement(&(fse->refcnt)))
return;
while (fse) {
struct fsentry *next = fse->next;
free(fse);
fse = next;
}
}
/*
* Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
*/
static struct fsentry *fseentry_create_entry(struct fsentry *list,
const WIN32_FIND_DATAW *fdata)
{
char buf[MAX_PATH * 3];
int len;
struct fsentry *fse;
len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf));
fse = fsentry_alloc(list, buf, len);
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
fse->st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
| fdata->nFileSizeLow;
fse->st_atime = filetime_to_time_t(&(fdata->ftLastAccessTime));
fse->st_mtime = filetime_to_time_t(&(fdata->ftLastWriteTime));
fse->st_ctime = filetime_to_time_t(&(fdata->ftCreationTime));
return fse;
}
/*
* Create an fsentry-based directory listing (similar to opendir / readdir).
* Dir should not contain trailing '/'. Use an empty string for the current
* directory (not "."!).
*/
static struct fsentry *fsentry_create_list(const struct fsentry *dir)
{
wchar_t pattern[MAX_PATH + 2]; /* + 2 for '/' '*' */
WIN32_FIND_DATAW fdata;
HANDLE h;
int wlen;
struct fsentry *list, **phead;
DWORD err;
/* convert name to UTF-16 and check length < MAX_PATH */
if ((wlen = xutftowcsn(pattern, dir->name, MAX_PATH, dir->len)) < 0) {
if (errno == ERANGE)
errno = ENAMETOOLONG;
return NULL;
}
/* append optional '/' and wildcard '*' */
if (wlen)
pattern[wlen++] = '/';
pattern[wlen++] = '*';
pattern[wlen] = 0;
/* open find handle */
h = FindFirstFileW(pattern, &fdata);
if (h == INVALID_HANDLE_VALUE) {
err = GetLastError();
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
return NULL;
}
/* allocate object to hold directory listing */
list = fsentry_alloc(NULL, dir->name, dir->len);
/* walk directory and build linked list of fsentry structures */
phead = &list->next;
do {
*phead = fseentry_create_entry(list, &fdata);
phead = &(*phead)->next;
} while (FindNextFileW(h, &fdata));
/* remember result of last FindNextFile, then close find handle */
err = GetLastError();
FindClose(h);
/* return the list if we've got all the files */
if (err == ERROR_NO_MORE_FILES)
return list;
/* otherwise free the list and return error */
fsentry_release(list);
errno = err_win_to_posix(err);
return NULL;
}
/*
* Adds a directory listing to the cache.
*/
static void fscache_add(struct fsentry *fse)
{
if (fse->list)
fse = fse->list;
for (; fse; fse = fse->next)
hashmap_add(&map, fse);
}
/*
* Removes a directory listing from the cache.
*/
static void fscache_remove(struct fsentry *fse)
{
if (fse->list)
fse = fse->list;
for (; fse; fse = fse->next)
hashmap_remove(&map, fse, NULL);
}
/*
* Clears the cache.
*/
static void fscache_clear()
{
struct hashmap_iter iter;
struct fsentry *fse;
while ((fse = hashmap_iter_first(&map, &iter))) {
fscache_remove(fse);
fsentry_release(fse);
}
}
/*
* Checks if the cache is enabled for the given path.
*/
static inline int fscache_enabled(const char *path)
{
return enabled > 0 && !is_absolute_path(path);
}
/*
* Looks up or creates a cache entry for the specified key.
*/
static struct fsentry *fscache_get(struct fsentry *key)
{
struct fsentry *fse;
EnterCriticalSection(&mutex);
/* check if entry is in cache */
fse = hashmap_get(&map, key, NULL);
if (fse) {
fsentry_addref(fse);
LeaveCriticalSection(&mutex);
return fse;
}
/* if looking for a file, check if directory listing is in cache */
if (!fse && key->list) {
fse = hashmap_get(&map, key->list, NULL);
if (fse) {
LeaveCriticalSection(&mutex);
/* dir entry without file entry -> file doesn't exist */
errno = ENOENT;
return NULL;
}
}
/* create the directory listing (outside mutex!) */
LeaveCriticalSection(&mutex);
fse = fsentry_create_list(key->list ? key->list : key);
if (!fse)
return NULL;
EnterCriticalSection(&mutex);
/* add directory listing if it hasn't been added by some other thread */
if (!hashmap_get(&map, key, NULL))
fscache_add(fse);
/* lookup file entry if requested (fse already points to directory) */
if (key->list)
fse = hashmap_get(&map, key, NULL);
/* return entry or ENOENT */
if (fse)
fsentry_addref(fse);
else
errno = ENOENT;
LeaveCriticalSection(&mutex);
return fse;
}
/*
* Enables or disables the cache. Note that the cache is read-only, changes to
* the working directory are NOT reflected in the cache while enabled.
*/
int fscache_enable(int enable)
{
int result;
if (!initialized) {
/* allow the cache to be disabled entirely */
if (!core_fscache)
return 0;
InitializeCriticalSection(&mutex);
hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, 0);
initialized = 1;
}
result = enable ? InterlockedIncrement(&enabled)
: InterlockedDecrement(&enabled);
if (enable && result == 1) {
/* redirect opendir and lstat to the fscache implementations */
opendir = fscache_opendir;
lstat = fscache_lstat;
} else if (!enable && !result) {
/* reset opendir and lstat to the original implementations */
opendir = dirent_opendir;
lstat = mingw_lstat;
EnterCriticalSection(&mutex);
fscache_clear();
LeaveCriticalSection(&mutex);
}
return result;
}
/*
* Lstat replacement, uses the cache if enabled, otherwise redirects to
* mingw_lstat.
*/
int fscache_lstat(const char *filename, struct stat *st)
{
int dirlen, base, len;
struct fsentry key[2], *fse;
if (!fscache_enabled(filename))
return mingw_lstat(filename, st);
/* split filename into path + name */
len = strlen(filename);
if (len && is_dir_sep(filename[len - 1]))
len--;
base = len;
while (base && !is_dir_sep(filename[base - 1]))
base--;
dirlen = base ? base - 1 : 0;
/* lookup entry for path + name in cache */
fsentry_init(key, NULL, filename, dirlen);
fsentry_init(key + 1, key, filename + base, len - base);
fse = fscache_get(key + 1);
if (!fse)
return -1;
/* copy stat data */
st->st_ino = 0;
st->st_gid = 0;
st->st_uid = 0;
st->st_dev = 0;
st->st_rdev = 0;
st->st_nlink = 1;
st->st_mode = fse->st_mode;
st->st_size = fse->st_size;
st->st_atime = fse->st_atime;
st->st_mtime = fse->st_mtime;
st->st_ctime = fse->st_ctime;
/* don't forget to release fsentry */
fsentry_release(fse);
return 0;
}
typedef struct fscache_DIR {
struct DIR base_dir; /* extend base struct DIR */
struct fsentry *pfsentry;
struct dirent dirent;
} fscache_DIR;
/*
* Readdir replacement.
*/
static struct dirent *fscache_readdir(DIR *base_dir)
{
fscache_DIR *dir = (fscache_DIR*) base_dir;
struct fsentry *next = dir->pfsentry->next;
if (!next)
return NULL;
dir->pfsentry = next;
dir->dirent.d_type = S_ISDIR(next->st_mode) ? DT_DIR : DT_REG;
dir->dirent.d_name = (char*) next->name;
return &(dir->dirent);
}
/*
* Closedir replacement.
*/
static int fscache_closedir(DIR *base_dir)
{
fscache_DIR *dir = (fscache_DIR*) base_dir;
fsentry_release(dir->pfsentry);
free(dir);
return 0;
}
/*
* Opendir replacement, uses a directory listing from the cache if enabled,
* otherwise calls original dirent implementation.
*/
DIR *fscache_opendir(const char *dirname)
{
struct fsentry key, *list;
fscache_DIR *dir;
int len;
if (!fscache_enabled(dirname))
return dirent_opendir(dirname);
/* prepare name (strip trailing '/', replace '.') */
len = strlen(dirname);
if ((len == 1 && dirname[0] == '.') ||
(len && is_dir_sep(dirname[len - 1])))
len--;
/* get directory listing from cache */
fsentry_init(&key, NULL, dirname, len);
list = fscache_get(&key);
if (!list)
return NULL;
/* alloc and return DIR structure */
dir = (fscache_DIR*) xmalloc(sizeof(fscache_DIR));
dir->base_dir.preaddir = fscache_readdir;
dir->base_dir.pclosedir = fscache_closedir;
dir->pfsentry = list;
return (DIR*) dir;
}

10
compat/win32/fscache.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef FSCACHE_H
#define FSCACHE_H
int fscache_enable(int enable);
#define enable_fscache(x) fscache_enable(x)
DIR *fscache_opendir(const char *dir);
int fscache_lstat(const char *file_name, struct stat *buf);
#endif

View File

@@ -890,6 +890,11 @@ static int git_default_core_config(const char *var, const char *value)
return 0;
}
if (!strcmp(var, "core.fscache")) {
core_fscache = git_config_bool(var, value);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}

View File

@@ -362,7 +362,7 @@ ifeq ($(uname_S),Windows)
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
COMPAT_OBJS = compat/msvc.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
compat/win32/dirent.o compat/win32/fscache.o
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@ -511,7 +511,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
compat/win32/dirent.o compat/win32/fscache.o
BASIC_LDFLAGS += -Wl,--large-address-aware
EXTLIBS += -lws2_32
GITLIBS += git.res

View File

@@ -64,6 +64,7 @@ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
struct startup_info *startup_info;
unsigned long pack_size_limit_cfg;
enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
int core_fscache;
/*
* The character that begins a commented line in user-editable file

View File

@@ -132,8 +132,10 @@
#if defined(__MINGW32__)
/* pull in Windows compatibility stuff */
#include "compat/mingw.h"
#include "compat/win32/fscache.h"
#elif defined(_MSC_VER)
#include "compat/msvc.h"
#include "compat/win32/fscache.h"
#else
#include <sys/wait.h>
#include <sys/resource.h>
@@ -733,4 +735,19 @@ struct passwd *xgetpwuid_self(void);
#define get_home_directory() getenv("HOME")
#endif
/*
* Enable/disable a read-only cache for file system data on platforms that
* support it.
*
* Implementing a live-cache is complicated and requires special platform
* support (inotify, ReadDirectoryChangesW...). enable_fscache shall be used
* to mark sections of git code that extensively read from the file system
* without modifying anything. Implementations can use this to cache e.g. stat
* data or even file content without the need to synchronize with the file
* system.
*/
#ifndef enable_fscache
#define enable_fscache(x) /* noop */
#endif
#endif

228
hashmap.c Normal file
View File

@@ -0,0 +1,228 @@
/*
* Generic implementation of hash-based key value mappings.
*/
#include "cache.h"
#include "hashmap.h"
#define FNV32_BASE ((unsigned int) 0x811c9dc5)
#define FNV32_PRIME ((unsigned int) 0x01000193)
unsigned int strhash(const char *str)
{
unsigned int c, hash = FNV32_BASE;
while ((c = (unsigned char) *str++))
hash = (hash * FNV32_PRIME) ^ c;
return hash;
}
unsigned int strihash(const char *str)
{
unsigned int c, hash = FNV32_BASE;
while ((c = (unsigned char) *str++)) {
if (c >= 'a' && c <= 'z')
c -= 'a' - 'A';
hash = (hash * FNV32_PRIME) ^ c;
}
return hash;
}
unsigned int memhash(const void *buf, size_t len)
{
unsigned int hash = FNV32_BASE;
unsigned char *ucbuf = (unsigned char *) buf;
while (len--) {
unsigned int c = *ucbuf++;
hash = (hash * FNV32_PRIME) ^ c;
}
return hash;
}
unsigned int memihash(const void *buf, size_t len)
{
unsigned int hash = FNV32_BASE;
unsigned char *ucbuf = (unsigned char *) buf;
while (len--) {
unsigned int c = *ucbuf++;
if (c >= 'a' && c <= 'z')
c -= 'a' - 'A';
hash = (hash * FNV32_PRIME) ^ c;
}
return hash;
}
#define HASHMAP_INITIAL_SIZE 64
/* grow / shrink by 2^2 */
#define HASHMAP_RESIZE_BITS 2
/* load factor in percent */
#define HASHMAP_LOAD_FACTOR 80
static void alloc_table(struct hashmap *map, unsigned int size)
{
map->tablesize = size;
map->table = xcalloc(size, sizeof(struct hashmap_entry *));
/* calculate resize thresholds for new size */
map->grow_at = (unsigned int) ((uint64_t) size * HASHMAP_LOAD_FACTOR / 100);
if (size <= HASHMAP_INITIAL_SIZE)
map->shrink_at = 0;
else
/*
* The shrink-threshold must be slightly smaller than
* (grow-threshold / resize-factor) to prevent erratic resizing,
* thus we divide by (resize-factor + 1).
*/
map->shrink_at = map->grow_at / ((1 << HASHMAP_RESIZE_BITS) + 1);
}
static inline int entry_equals(const struct hashmap *map,
const struct hashmap_entry *e1, const struct hashmap_entry *e2,
const void *keydata)
{
return (e1 == e2) || (e1->hash == e2->hash && !map->cmpfn(e1, e2, keydata));
}
static inline unsigned int bucket(const struct hashmap *map,
const struct hashmap_entry *key)
{
return key->hash & (map->tablesize - 1);
}
static void rehash(struct hashmap *map, unsigned int newsize)
{
unsigned int i, oldsize = map->tablesize;
struct hashmap_entry **oldtable = map->table;
alloc_table(map, newsize);
for (i = 0; i < oldsize; i++) {
struct hashmap_entry *e = oldtable[i];
while (e) {
struct hashmap_entry *next = e->next;
unsigned int b = bucket(map, e);
e->next = map->table[b];
map->table[b] = e;
e = next;
}
}
free(oldtable);
}
static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,
const struct hashmap_entry *key, const void *keydata)
{
struct hashmap_entry **e = &map->table[bucket(map, key)];
while (*e && !entry_equals(map, *e, key, keydata))
e = &(*e)->next;
return e;
}
static int always_equal(const void *unused1, const void *unused2, const void *unused3)
{
return 0;
}
void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,
size_t initial_size)
{
unsigned int size = HASHMAP_INITIAL_SIZE;
map->size = 0;
map->cmpfn = equals_function ? equals_function : always_equal;
/* calculate initial table size and allocate the table */
initial_size = (unsigned int) ((uint64_t) initial_size * 100
/ HASHMAP_LOAD_FACTOR);
while (initial_size > size)
size <<= HASHMAP_RESIZE_BITS;
alloc_table(map, size);
}
void hashmap_free(struct hashmap *map, int free_entries)
{
if (!map || !map->table)
return;
if (free_entries) {
struct hashmap_iter iter;
struct hashmap_entry *e;
hashmap_iter_init(map, &iter);
while ((e = hashmap_iter_next(&iter)))
free(e);
}
free(map->table);
memset(map, 0, sizeof(*map));
}
void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata)
{
return *find_entry_ptr(map, key, keydata);
}
void *hashmap_get_next(const struct hashmap *map, const void *entry)
{
struct hashmap_entry *e = ((struct hashmap_entry *) entry)->next;
for (; e; e = e->next)
if (entry_equals(map, entry, e, NULL))
return e;
return NULL;
}
void hashmap_add(struct hashmap *map, void *entry)
{
unsigned int b = bucket(map, entry);
/* add entry */
((struct hashmap_entry *) entry)->next = map->table[b];
map->table[b] = entry;
/* fix size and rehash if appropriate */
map->size++;
if (map->size > map->grow_at)
rehash(map, map->tablesize << HASHMAP_RESIZE_BITS);
}
void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata)
{
struct hashmap_entry *old;
struct hashmap_entry **e = find_entry_ptr(map, key, keydata);
if (!*e)
return NULL;
/* remove existing entry */
old = *e;
*e = old->next;
old->next = NULL;
/* fix size and rehash if appropriate */
map->size--;
if (map->size < map->shrink_at)
rehash(map, map->tablesize >> HASHMAP_RESIZE_BITS);
return old;
}
void *hashmap_put(struct hashmap *map, void *entry)
{
struct hashmap_entry *old = hashmap_remove(map, entry, NULL);
hashmap_add(map, entry);
return old;
}
void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter)
{
iter->map = map;
iter->tablepos = 0;
iter->next = NULL;
}
void *hashmap_iter_next(struct hashmap_iter *iter)
{
struct hashmap_entry *current = iter->next;
for (;;) {
if (current) {
iter->next = current->next;
return current;
}
if (iter->tablepos >= iter->map->tablesize)
return NULL;
current = iter->map->table[iter->tablepos++];
}
}

71
hashmap.h Normal file
View File

@@ -0,0 +1,71 @@
#ifndef HASHMAP_H
#define HASHMAP_H
/*
* Generic implementation of hash-based key-value mappings.
* See Documentation/technical/api-hashmap.txt.
*/
/* FNV-1 functions */
extern unsigned int strhash(const char *buf);
extern unsigned int strihash(const char *buf);
extern unsigned int memhash(const void *buf, size_t len);
extern unsigned int memihash(const void *buf, size_t len);
/* data structures */
struct hashmap_entry {
struct hashmap_entry *next;
unsigned int hash;
};
typedef int (*hashmap_cmp_fn)(const void *entry, const void *entry_or_key,
const void *keydata);
struct hashmap {
struct hashmap_entry **table;
hashmap_cmp_fn cmpfn;
unsigned int size, tablesize, grow_at, shrink_at;
};
struct hashmap_iter {
struct hashmap *map;
struct hashmap_entry *next;
unsigned int tablepos;
};
/* hashmap functions */
extern void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,
size_t initial_size);
extern void hashmap_free(struct hashmap *map, int free_entries);
/* hashmap_entry functions */
static inline void hashmap_entry_init(void *entry, int hash)
{
struct hashmap_entry *e = entry;
e->hash = hash;
e->next = NULL;
}
extern void *hashmap_get(const struct hashmap *map, const void *key,
const void *keydata);
extern void *hashmap_get_next(const struct hashmap *map, const void *entry);
extern void hashmap_add(struct hashmap *map, void *entry);
extern void *hashmap_put(struct hashmap *map, void *entry);
extern void *hashmap_remove(struct hashmap *map, const void *key,
const void *keydata);
/* hashmap_iter functions */
extern void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter);
extern void *hashmap_iter_next(struct hashmap_iter *iter);
static inline void *hashmap_iter_first(struct hashmap *map,
struct hashmap_iter *iter)
{
hashmap_iter_init(map, iter);
return hashmap_iter_next(iter);
}
#endif

View File

@@ -83,6 +83,7 @@ static void preload_index(struct index_state *index,
offset = 0;
work = DIV_ROUND_UP(index->cache_nr, threads);
memset(&data, 0, sizeof(data));
enable_fscache(1);
for (i = 0; i < threads; i++) {
struct thread_data *p = data+i;
p->index = index;
@@ -99,6 +100,7 @@ static void preload_index(struct index_state *index,
if (pthread_join(p->pthread, NULL))
die("unable to join threaded lstat");
}
enable_fscache(0);
}
#endif

View File

@@ -116,30 +116,7 @@ int remove_path_from_gitmodules(const char *path)
void stage_updated_gitmodules(void)
{
struct strbuf buf = STRBUF_INIT;
struct stat st;
int pos;
struct cache_entry *ce;
int namelen = strlen(".gitmodules");
pos = cache_name_pos(".gitmodules", namelen);
if (pos < 0) {
warning(_("could not find .gitmodules in index"));
return;
}
ce = active_cache[pos];
ce->ce_flags = namelen;
if (strbuf_read_file(&buf, ".gitmodules", 0) < 0)
die(_("reading updated .gitmodules failed"));
if (lstat(".gitmodules", &st) < 0)
die_errno(_("unable to stat updated .gitmodules"));
fill_stat_cache_info(ce, &st);
ce->ce_mode = ce_mode_from_stat(ce, st.st_mode);
if (remove_cache_entry_at(pos) < 0)
die(_("unable to remove .gitmodules from index"));
if (write_sha1_file(buf.buf, buf.len, blob_type, ce->sha1))
die(_("adding updated .gitmodules failed"));
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
if (add_file_to_cache(".gitmodules", 0))
die(_("staging updated .gitmodules failed"));
}

240
t/t0011-hashmap.sh Executable file
View File

@@ -0,0 +1,240 @@
#!/bin/sh
test_description='test hashmap and string hash functions'
. ./test-lib.sh
test_hashmap() {
echo "$1" | test-hashmap $3 > actual &&
echo "$2" > expect &&
test_cmp expect actual
}
test_expect_success 'hash functions' '
test_hashmap "hash key1" "2215982743 2215982743 116372151 116372151" &&
test_hashmap "hash key2" "2215982740 2215982740 116372148 116372148" &&
test_hashmap "hash fooBarFrotz" "1383912807 1383912807 3189766727 3189766727" &&
test_hashmap "hash foobarfrotz" "2862305959 2862305959 3189766727 3189766727"
'
test_expect_success 'put' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
put foobarfrotz value4
size" "NULL
NULL
NULL
NULL
64 4"
'
test_expect_success 'put (case insensitive)' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
size" "NULL
NULL
NULL
64 3" ignorecase
'
test_expect_success 'replace' '
test_hashmap "put key1 value1
put key1 value2
put fooBarFrotz value3
put fooBarFrotz value4
size" "NULL
value1
NULL
value3
64 2"
'
test_expect_success 'replace (case insensitive)' '
test_hashmap "put key1 value1
put Key1 value2
put fooBarFrotz value3
put foobarfrotz value4
size" "NULL
value1
NULL
value3
64 2" ignorecase
'
test_expect_success 'get' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
put foobarfrotz value4
get key1
get key2
get fooBarFrotz
get notInMap" "NULL
NULL
NULL
NULL
value1
value2
value3
NULL"
'
test_expect_success 'get (case insensitive)' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
get Key1
get keY2
get foobarfrotz
get notInMap" "NULL
NULL
NULL
value1
value2
value3
NULL" ignorecase
'
test_expect_success 'add' '
test_hashmap "add key1 value1
add key1 value2
add fooBarFrotz value3
add fooBarFrotz value4
get key1
get fooBarFrotz
get notInMap" "value2
value1
value4
value3
NULL"
'
test_expect_success 'add (case insensitive)' '
test_hashmap "add key1 value1
add Key1 value2
add fooBarFrotz value3
add foobarfrotz value4
get key1
get Foobarfrotz
get notInMap" "value2
value1
value4
value3
NULL" ignorecase
'
test_expect_success 'remove' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
remove key1
remove key2
remove notInMap
size" "NULL
NULL
NULL
value1
value2
NULL
64 1"
'
test_expect_success 'remove (case insensitive)' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
remove Key1
remove keY2
remove notInMap
size" "NULL
NULL
NULL
value1
value2
NULL
64 1" ignorecase
'
test_expect_success 'iterate' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
iterate" "NULL
NULL
NULL
key2 value2
key1 value1
fooBarFrotz value3"
'
test_expect_success 'iterate (case insensitive)' '
test_hashmap "put key1 value1
put key2 value2
put fooBarFrotz value3
iterate" "NULL
NULL
NULL
fooBarFrotz value3
key2 value2
key1 value1" ignorecase
'
test_expect_success 'grow / shrink' '
rm -f in &&
rm -f expect &&
for n in $(test_seq 51)
do
echo put key$n value$n >> in &&
echo NULL >> expect
done &&
echo size >> in &&
echo 64 51 >> expect &&
echo put key52 value52 >> in &&
echo NULL >> expect
echo size >> in &&
echo 256 52 >> expect &&
for n in $(test_seq 12)
do
echo remove key$n >> in &&
echo value$n >> expect
done &&
echo size >> in &&
echo 256 40 >> expect &&
echo remove key40 >> in &&
echo value40 >> expect &&
echo size >> in &&
echo 64 39 >> expect &&
cat in | test-hashmap > out &&
test_cmp expect out
'
test_done

340
test-hashmap.c Normal file
View File

@@ -0,0 +1,340 @@
#include "cache.h"
#include "hashmap.h"
#include <stdio.h>
struct test_entry
{
struct hashmap_entry ent;
/* key and value as two \0-terminated strings */
char key[FLEX_ARRAY];
};
static const char *get_value(const struct test_entry *e)
{
return e->key + strlen(e->key) + 1;
}
static int test_entry_cmp(const struct test_entry *e1,
const struct test_entry *e2, const char* key)
{
return strcmp(e1->key, key ? key : e2->key);
}
static int test_entry_cmp_icase(const struct test_entry *e1,
const struct test_entry *e2, const char* key)
{
return strcasecmp(e1->key, key ? key : e2->key);
}
static struct test_entry *alloc_test_entry(int hash, char *key, int klen,
char *value, int vlen)
{
struct test_entry *entry = malloc(sizeof(struct test_entry) + klen
+ vlen + 2);
hashmap_entry_init(entry, hash);
memcpy(entry->key, key, klen + 1);
memcpy(entry->key + klen + 1, value, vlen + 1);
return entry;
}
#define HASH_METHOD_FNV 0
#define HASH_METHOD_I 1
#define HASH_METHOD_IDIV10 2
#define HASH_METHOD_0 3
#define HASH_METHOD_X2 4
#define TEST_SPARSE 8
#define TEST_ADD 16
#define TEST_SIZE 100000
static unsigned int hash(unsigned int method, unsigned int i, const char *key)
{
unsigned int hash;
switch (method & 3)
{
case HASH_METHOD_FNV:
hash = strhash(key);
break;
case HASH_METHOD_I:
hash = i;
break;
case HASH_METHOD_IDIV10:
hash = i / 10;
break;
case HASH_METHOD_0:
hash = 0;
break;
}
if (method & HASH_METHOD_X2)
hash = 2 * hash;
return hash;
}
/*
* Test performance of hashmap.[ch]
* Usage: time echo "perfhashmap method rounds" | test-hashmap
*/
static void perf_hashmap(unsigned int method, unsigned int rounds)
{
struct hashmap map;
char buf[16];
struct test_entry **entries;
unsigned int *hashes;
unsigned int i, j;
entries = malloc(TEST_SIZE * sizeof(struct test_entry *));
hashes = malloc(TEST_SIZE * sizeof(int));
for (i = 0; i < TEST_SIZE; i++) {
snprintf(buf, sizeof(buf), "%i", i);
entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0);
hashes[i] = hash(method, i, entries[i]->key);
}
if (method & TEST_ADD) {
/* test adding to the map */
for (j = 0; j < rounds; j++) {
hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
/* add entries */
for (i = 0; i < TEST_SIZE; i++) {
hashmap_entry_init(entries[i], hashes[i]);
hashmap_add(&map, entries[i]);
}
hashmap_free(&map, 0);
}
} else {
/* test map lookups */
hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0);
/* fill the map (sparsely if specified) */
j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE;
for (i = 0; i < j; i++) {
hashmap_entry_init(entries[i], hashes[i]);
hashmap_add(&map, entries[i]);
}
for (j = 0; j < rounds; j++) {
for (i = 0; i < TEST_SIZE; i++) {
struct hashmap_entry key;
hashmap_entry_init(&key, hashes[i]);
hashmap_get(&map, &key, entries[i]->key);
}
}
hashmap_free(&map, 0);
}
}
struct hash_entry
{
struct hash_entry *next;
char key[FLEX_ARRAY];
};
/*
* Test performance of hash.[ch]
* Usage: time echo "perfhash method rounds" | test-hashmap
*/
static void perf_hash(unsigned int method, unsigned int rounds)
{
struct hash_table map;
char buf[16];
struct hash_entry **entries, **res, *entry;
unsigned int *hashes;
unsigned int i, j;
entries = malloc(TEST_SIZE * sizeof(struct hash_entry *));
hashes = malloc(TEST_SIZE * sizeof(int));
for (i = 0; i < TEST_SIZE; i++) {
snprintf(buf, sizeof(buf), "%i", i);
entries[i] = malloc(sizeof(struct hash_entry) + strlen(buf) + 1);
strcpy(entries[i]->key, buf);
hashes[i] = hash(method, i, entries[i]->key);
}
if (method & TEST_ADD) {
/* test adding to the map */
for (j = 0; j < rounds; j++) {
init_hash(&map);
/* add entries */
for (i = 0; i < TEST_SIZE; i++) {
res = (struct hash_entry **) insert_hash(
hashes[i], entries[i], &map);
if (res) {
entries[i]->next = *res;
*res = entries[i];
} else {
entries[i]->next = NULL;
}
}
free_hash(&map);
}
} else {
/* test map lookups */
init_hash(&map);
/* fill the map (sparsely if specified) */
j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE;
for (i = 0; i < j; i++) {
res = (struct hash_entry **) insert_hash(hashes[i],
entries[i], &map);
if (res) {
entries[i]->next = *res;
*res = entries[i];
} else {
entries[i]->next = NULL;
}
}
for (j = 0; j < rounds; j++) {
for (i = 0; i < TEST_SIZE; i++) {
entry = lookup_hash(hashes[i], &map);
while (entry) {
if (!strcmp(entries[i]->key, entry->key))
break;
entry = entry->next;
}
}
}
free_hash(&map);
}
}
#define DELIM " \t\r\n"
/*
* Read stdin line by line and print result of commands to stdout:
*
* hash key -> strhash(key) memhash(key) strihash(key) memihash(key)
* put key value -> NULL / old value
* get key -> NULL / value
* remove key -> NULL / old value
* iterate -> key1 value1\nkey2 value2\n...
* size -> tablesize numentries
*
* perfhashmap method rounds -> test hashmap.[ch] performance
* perfhash method rounds -> test hash.[ch] performance
*/
int main(int argc, char *argv[])
{
char line[1024];
struct hashmap map;
int icase;
/* init hash map */
icase = argc > 1 && !strcmp("ignorecase", argv[1]);
hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase
: test_entry_cmp), 0);
/* process commands from stdin */
while (fgets(line, sizeof(line), stdin)) {
char *cmd, *p1 = NULL, *p2 = NULL;
int l1 = 0, l2 = 0, hash = 0;
struct test_entry *entry;
/* break line into command and up to two parameters */
cmd = strtok(line, DELIM);
/* ignore empty lines */
if (!cmd || *cmd == '#')
continue;
p1 = strtok(NULL, DELIM);
if (p1) {
l1 = strlen(p1);
hash = icase ? strihash(p1) : strhash(p1);
p2 = strtok(NULL, DELIM);
if (p2)
l2 = strlen(p2);
}
if (!strcmp("hash", cmd) && l1) {
/* print results of different hash functions */
printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1),
strihash(p1), memihash(p1, l1));
} else if (!strcmp("add", cmd) && l1 && l2) {
/* create entry with key = p1, value = p2 */
entry = alloc_test_entry(hash, p1, l1, p2, l2);
/* add to hashmap */
hashmap_add(&map, entry);
} else if (!strcmp("put", cmd) && l1 && l2) {
/* create entry with key = p1, value = p2 */
entry = alloc_test_entry(hash, p1, l1, p2, l2);
/* add / replace entry */
entry = hashmap_put(&map, entry);
/* print and free replaced entry, if any */
puts(entry ? get_value(entry) : "NULL");
free(entry);
} else if (!strcmp("get", cmd) && l1) {
/* setup static key */
struct hashmap_entry key;
hashmap_entry_init(&key, hash);
/* lookup entry in hashmap */
entry = hashmap_get(&map, &key, p1);
/* print result */
if (!entry)
puts("NULL");
while (entry) {
puts(get_value(entry));
entry = hashmap_get_next(&map, entry);
}
} else if (!strcmp("remove", cmd) && l1) {
/* setup static key */
struct hashmap_entry key;
hashmap_entry_init(&key, hash);
/* remove entry from hashmap */
entry = hashmap_remove(&map, &key, p1);
/* print result and free entry*/
puts(entry ? get_value(entry) : "NULL");
free(entry);
} else if (!strcmp("iterate", cmd)) {
struct hashmap_iter iter;
hashmap_iter_init(&map, &iter);
while ((entry = hashmap_iter_next(&iter)))
printf("%s %s\n", entry->key, get_value(entry));
} else if (!strcmp("size", cmd)) {
/* print table sizes */
printf("%u %u\n", map.tablesize, map.size);
} else if (!strcmp("perfhashmap", cmd) && l1 && l2) {
perf_hashmap(atoi(p1), atoi(p2));
} else if (!strcmp("perfhash", cmd) && l1 && l2) {
perf_hash(atoi(p1), atoi(p2));
} else {
printf("Unknown command %s\n", cmd);
}
}
hashmap_free(&map, 1);
return 0;
}