From 1efe42ea586bb14806a3b8293a6ec45f0d03a494 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 26 Jul 2014 07:37:14 +0000 Subject: [PATCH] use khash for fstring and id_str tables frozen_strings and global_symbols.id_str hashes are two of the bigger hashes in Ruby. They are needlessly ordered and incurs malloc overhead in every st_table_entry. Use an unordered open-addressing table which incurs no additional malloc overhead besides the (larger) table itself. Reduces "ruby -e exit" (w/RubyGems) by ~200K on eglibc malloc on amd64 Debian stable due to having fewer allocations global_symbols.str_id is left unchanged (for now) because it is used for Symbol.all_symbols where ordering is expected This introduces no user-visible changes or incompatibility (unless I added a bug :x). I chose khash because it is flexible and has (IMHO) a good API. The API should also be familiar to mruby hackers, as mruby uses a version of khash. Future changes: * covert smaller internal hashes where ordering is not exposed to Ruby users * (hopefully) other hashes (methods/constants/ivars) hashes [Feature #9614] --- common.mk | 4 +- klib/khash.h | 617 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ruby_khash.h | 50 +++++ string.c | 109 +++++------ symbol.c | 97 +++++++--- 5 files changed, 792 insertions(+), 85 deletions(-) create mode 100644 klib/khash.h create mode 100644 ruby_khash.h diff --git a/common.mk b/common.mk index 160df01..34f8771 100644 --- a/common.mk +++ b/common.mk @@ -638,6 +638,7 @@ VM_CORE_H_INCLUDES = {$(VPATH)}vm_core.h {$(VPATH)}thread_$(THREAD_MODEL).h \ {$(VPATH)}node.h {$(VPATH)}method.h {$(VPATH)}ruby_atomic.h \ {$(VPATH)}vm_debug.h {$(VPATH)}id.h {$(VPATH)}thread_native.h \ $(CCAN_LIST_INCLUDES) +RUBY_KHASH_INCLUDES = {$(VPATH)}ruby_khash.h {$(VPATH)}klib/khash.h ### @@ -777,7 +778,8 @@ st.$(OBJEXT): {$(VPATH)}st.c $(RUBY_H_INCLUDES) strftime.$(OBJEXT): {$(VPATH)}strftime.c $(RUBY_H_INCLUDES) \ {$(VPATH)}timev.h $(ENCODING_H_INCLUDES) string.$(OBJEXT): {$(VPATH)}string.c $(RUBY_H_INCLUDES) {$(VPATH)}re.h {$(VPATH)}gc.h \ - {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) + {$(VPATH)}regex.h $(ENCODING_H_INCLUDES) {$(VPATH)}internal.h $(PROBES_H_INCLUDES) \ + $(RUBY_KHASH_INCLUDES) struct.$(OBJEXT): {$(VPATH)}struct.c $(RUBY_H_INCLUDES) {$(VPATH)}internal.h symbol.$(OBJEXT): {$(VPATH)}symbol.c $(RUBY_H_INCLUDES) $(ENCODING_H_INCLUDES) \ {$(VPATH)}internal.h {$(VPATH)}node.h {$(VPATH)}id.h {$(VPATH)}symbol.h \ diff --git a/klib/khash.h b/klib/khash.h new file mode 100644 index 0000000..5772e82 --- /dev/null +++ b/klib/khash.h @@ -0,0 +1,617 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t k, i, last, mask, step = 0; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + (++step)) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) return -1; \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) return -1; \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t k, i, step = 0; \ + k = __hash_func(key); \ + i = k & new_mask; \ + while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + (++step)) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: -1 if the operation failed; + 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ diff --git a/ruby_khash.h b/ruby_khash.h new file mode 100644 index 0000000..1fdc942 --- /dev/null +++ b/ruby_khash.h @@ -0,0 +1,50 @@ +#ifndef RUBY_KHASH_H +#define RUBY_KHASH_H +#include "ruby/ruby.h" +#include "id.h" + +/* + * Avoid modifying klib/khash directly since that is imported from + * https://github.com/attractivechaos/klib.git + */ + +/* override klib defaults to do GC accounting */ +#define kcalloc(N,Z) ruby_xcalloc((N),(Z)) +#define kmalloc(N) ruby_xmalloc((N)) +#define krealloc(P,Z) ruby_xrealloc((P),(Z)) +#define kfree(P) ruby_xfree((P)) + +#include "klib/khash.h" + +static inline khint_t +rb_khash_str(VALUE str) +{ + st_index_t h = rb_str_hash(str); + + if (sizeof(st_index_t) == (sizeof(khint_t) * 2)) { + union { st_index_t in; khint_t out[2]; } tmp; + + tmp.in = h; + return tmp.out[0] ^ tmp.out[1]; + } + return (khint_t)h; +} + +static inline khint_t +rb_khash_id(ID id) +{ + return (khint_t)(id >> RUBY_ID_SCOPE_SHIFT); +} + +/* + * TODO: send this to khash upstream if it works well + * This is based on kh_foreach_value, but for keys. + */ +#define rb_kh_foreach_key(h, kvar, code) do { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + code; \ + } } while (0) + +#endif /* RUBY_KHASH_H */ diff --git a/string.c b/string.c index 9f06837..4f6b577 100644 --- a/string.c +++ b/string.c @@ -18,6 +18,7 @@ #include "probes.h" #include "gc.h" #include +#include "ruby_khash.h" #define BEG(no) (regs->beg[(no)]) #define END(no) (regs->end[(no)]) @@ -174,62 +175,60 @@ mustnot_broken(VALUE str) } } -static int fstring_cmp(VALUE a, VALUE b); - -static st_table* frozen_strings; - -static const struct st_hash_type fstring_hash_type = { - fstring_cmp, - rb_str_hash, -}; +static int fstring_eq(VALUE a, VALUE b); +KHASH_INIT(fstring, VALUE, char, 0, rb_khash_str, fstring_eq); +static khash_t(fstring) *frozen_strings; /* TODO: make mvm friendly */ static int -fstr_update_callback(st_data_t *key, st_data_t *value, st_data_t arg, int existing) +fstring_eq(VALUE a, VALUE b) +{ + int cmp = rb_str_hash_cmp(a, b); + if (cmp == 0) { + return (ENCODING_GET(b) - ENCODING_GET(a)) == 0; + } + return 0; +} + +VALUE +rb_fstring(VALUE str) { - VALUE *fstr = (VALUE *)arg; - VALUE str = (VALUE)*key; + VALUE fstr; + khint_t k; + int ret; + + Check_Type(str, T_STRING); + + if (!frozen_strings) + frozen_strings = kh_init(fstring); + + if (FL_TEST(str, RSTRING_FSTR)) + return str; + + k = kh_get(fstring, frozen_strings, str); + if (k != kh_end(frozen_strings)) { /* existing */ + fstr = kh_key(frozen_strings, k); - if (existing) { /* because of lazy sweep, str may be unmarked already and swept * at next time */ - if (rb_objspace_garbage_object_p(str)) { - str = *fstr; + if (rb_objspace_garbage_object_p(fstr)) { goto create_new_fstr; } - - *fstr = str; - return ST_STOP; } else { if (STR_SHARED_P(str)) { /* str should not be shared */ create_new_fstr: - str = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), STR_ENC_GET(str)); - OBJ_FREEZE(str); + fstr = rb_enc_str_new(RSTRING_PTR(str), RSTRING_LEN(str), STR_ENC_GET(str)); + OBJ_FREEZE(fstr); } else { - str = rb_str_new_frozen(str); + fstr = rb_str_new_frozen(str); } - RBASIC(str)->flags |= RSTRING_FSTR; - - *key = *value = *fstr = str; - return ST_CONTINUE; + RBASIC(fstr)->flags |= RSTRING_FSTR; + kh_put(fstring, frozen_strings, fstr, &ret); + if (ret < 0) rb_memerror(); } -} - -VALUE -rb_fstring(VALUE str) -{ - Check_Type(str, T_STRING); - - if (!frozen_strings) - frozen_strings = st_init_table(&fstring_hash_type); - - if (FL_TEST(str, RSTRING_FSTR)) - return str; - - st_update(frozen_strings, (st_data_t)str, fstr_update_callback, (st_data_t)&str); - return str; + return fstr; } static VALUE @@ -254,23 +253,6 @@ rb_fstring_new(const char *ptr, long len) return rb_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII)); } -static int -fstring_set_class_i(st_data_t key, st_data_t val, st_data_t arg) -{ - RBASIC_SET_CLASS((VALUE)key, (VALUE)arg); - return ST_CONTINUE; -} - -static int -fstring_cmp(VALUE a, VALUE b) -{ - int cmp = rb_str_hash_cmp(a, b); - if (cmp != 0) { - return cmp; - } - return ENCODING_GET(b) - ENCODING_GET(a); -} - static inline int single_byte_optimizable(VALUE str) { @@ -940,8 +922,10 @@ void rb_str_free(VALUE str) { if (FL_TEST(str, RSTRING_FSTR)) { - st_data_t fstr = (st_data_t)str; - st_delete(frozen_strings, &fstr, NULL); + khint_t k = kh_get(fstring, frozen_strings, str); + if (k != kh_end(frozen_strings)) { + kh_del(fstring, frozen_strings, k); + } } if (!STR_EMBED_P(str) && !FL_TEST(str, STR_SHARED)) { @@ -8924,6 +8908,11 @@ Init_String(void) rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); - if (frozen_strings) - st_foreach(frozen_strings, fstring_set_class_i, rb_cString); + if (frozen_strings) { + VALUE str; + + rb_kh_foreach_key(frozen_strings, str, do { + RBASIC_SET_CLASS(str, rb_cString); + } while (0)); + } } diff --git a/symbol.c b/symbol.c index 5089d8f..1960bd4 100644 --- a/symbol.c +++ b/symbol.c @@ -17,6 +17,7 @@ #include "symbol.h" #include "gc.h" #include "probes.h" +#include "ruby_khash.h" static ID register_static_symid(ID, const char *, long, rb_encoding *); static ID register_static_symid_str(ID, VALUE); @@ -70,10 +71,18 @@ static const struct { STATIC_ASSERT(op_tbl_name_size, sizeof(op_tbl[0].name) == 3); #define op_tbl_len(i) (!op_tbl[i].name[1] ? 1 : !op_tbl[i].name[2] ? 2 : 3) +static int +id_str_eq(ID a, ID b) +{ + return a == b; +} + +KHASH_INIT(id_str, ID, VALUE, 1, rb_khash_id, id_str_eq); + static struct symbols { ID last_id; - st_table *str_id; - st_table *id_str; + st_table *str_id; /* ordered for now in case of compatibility */ + khash_t(id_str) *id_str; VALUE dsymbol_fstr_hash; } global_symbols = {tNEXT_ID-1}; @@ -82,6 +91,18 @@ static const struct st_hash_type symhash = { rb_str_hash, }; +static int +lookup_static_id_str(ID id, VALUE *str) +{ + khint_t k = kh_get(id_str, global_symbols.id_str, id); + + if (k != kh_end(global_symbols.id_str)) { + *str = kh_val(global_symbols.id_str, k); + return TRUE; + } + return FALSE; +} + void Init_sym(void) { @@ -90,14 +111,18 @@ Init_sym(void) rb_gc_register_mark_object(dsym_fstrs); rb_obj_hide(dsym_fstrs); + /* load factor in a chained hash table is often > 1 */ global_symbols.str_id = st_init_table_with_size(&symhash, 1000); - global_symbols.id_str = st_init_numtable_with_size(1000); + + /* load factor in an open-addressed hash table is always < 1 */ + global_symbols.id_str = kh_init(id_str); + kh_resize(id_str, global_symbols.id_str, 4096); Init_id(); } static ID attrsetname_to_attr(VALUE name); -static int lookup_id_str(ID id, st_data_t *data); +static int lookup_id_str(ID id, VALUE *str); ID rb_id_attrset(ID id) @@ -120,10 +145,10 @@ rb_id_attrset(ID id) return id; default: { - st_data_t data; - if (lookup_id_str(id, &data)) { + VALUE str; + if (lookup_id_str(id, &str)) { rb_name_error(id, "cannot make unknown type ID %d:%"PRIsVALUE" attrset", - scope, (VALUE)data); + scope, str); } else { rb_name_error_str(Qnil, "cannot make unknown type anonymous ID %d:%"PRIxVALUE" attrset", @@ -315,6 +340,24 @@ register_static_symid(ID id, const char *name, long len, rb_encoding *enc) return register_static_symid_str(id, str); } +static void +register_direct(ID id, VALUE str) +{ + int ret; + khint_t k; + + st_add_direct(global_symbols.str_id, (st_data_t)str, id); + + k = kh_put(id_str, global_symbols.id_str, id, &ret); + if (ret == 0) { + rb_bug("duplicate element (id_str): %s", RSTRING_PTR(str)); + } else if (ret < 0) { + rb_memerror(); + } else { + kh_val(global_symbols.id_str, k) = str; + } +} + static ID register_static_symid_str(ID id, VALUE str) { @@ -325,8 +368,7 @@ register_static_symid_str(ID id, VALUE str) RUBY_DTRACE_SYMBOL_CREATE(RSTRING_PTR(str), rb_sourcefile(), rb_sourceline()); } - st_add_direct(global_symbols.str_id, (st_data_t)str, id); - st_add_direct(global_symbols.id_str, id, (st_data_t)str); + register_direct(id, str); rb_gc_register_mark_object(str); return id; @@ -357,10 +399,10 @@ must_be_dynamic_symbol(VALUE x) { if (UNLIKELY(!DYNAMIC_SYM_P(x))) { if (STATIC_SYM_P(x)) { - st_data_t str; + VALUE str; if (lookup_id_str(RSHIFT((unsigned long)(x),RUBY_SPECIAL_SHIFT), &str)) { - rb_bug("wrong argument: %s (inappropriate Symbol)", RSTRING_PTR((VALUE)str)); + rb_bug("wrong argument: %s (inappropriate Symbol)", RSTRING_PTR(str)); } else { rb_bug("wrong argument: inappropriate Symbol (%p)", (void *)x); @@ -394,8 +436,7 @@ dsymbol_alloc(const VALUE klass, const VALUE str, rb_encoding * const enc) RB_OBJ_WRITE(dsym, &RSYMBOL(dsym)->fstr, str); RSYMBOL(dsym)->type = type; - st_add_direct(global_symbols.str_id, (st_data_t)str, (st_data_t)dsym); - st_add_direct(global_symbols.id_str, (ID)dsym, (st_data_t)str); + register_direct((ID)dsym, str); rb_hash_aset(global_symbols.dsymbol_fstr_hash, str, Qtrue); if (RUBY_DTRACE_SYMBOL_CREATE_ENABLED()) { @@ -405,6 +446,18 @@ dsymbol_alloc(const VALUE klass, const VALUE str, rb_encoding * const enc) return dsym; } +static inline int +id_str_delete(ID id) +{ + khint_t k = kh_get(id_str, global_symbols.id_str, id); + + if (k != kh_end(global_symbols.id_str)) { + kh_del(id_str, global_symbols.id_str, k); + return TRUE; + } + return FALSE; +} + static inline VALUE dsymbol_check(const VALUE sym) { @@ -415,7 +468,7 @@ dsymbol_check(const VALUE sym) if (st_delete(global_symbols.str_id, (st_data_t *)&fstr, NULL) == 0) { rb_bug("can't remove fstr from str_id (%s)", RSTRING_PTR(fstr)); } - if (st_delete(global_symbols.id_str, (st_data_t *)&sym, NULL) == 0) { + if (!id_str_delete(sym)) { rb_bug("can't remove sym from id_sym (%s)", RSTRING_PTR(fstr)); } return dsymbol_alloc(rb_cSymbol, fstr, rb_enc_get(fstr)); @@ -483,16 +536,13 @@ lookup_str_sym(const st_data_t str, st_data_t *sym_datap) } static int -lookup_id_str(ID id, st_data_t *data) +lookup_id_str(ID id, VALUE *str) { if (ID_DYNAMIC_SYM_P(id)) { - *data = RSYMBOL(id)->fstr; + *str = RSYMBOL(id)->fstr; return TRUE; } - if (st_lookup(global_symbols.id_str, id, data)) { - return TRUE; - } - return FALSE; + return lookup_static_id_str(id, str); } ID @@ -681,13 +731,13 @@ void rb_gc_free_dsymbol(VALUE sym) { st_data_t str_data = (st_data_t)RSYMBOL(sym)->fstr; - st_data_t sym_data = (st_data_t)sym; if (str_data) { if (st_delete(global_symbols.str_id, &str_data, 0) == 0) { rb_bug("rb_gc_free_dsymbol: %p can't remove str from str_id (%s)", (void *)sym, RSTRING_PTR(RSYMBOL(sym)->fstr)); } - if (st_delete(global_symbols.id_str, &sym_data, 0) == 0) { + + if (!id_str_delete((ID)sym)) { rb_bug("rb_gc_free_dsymbol: %p can't remove sym from id_str (%s)", (void *)sym, RSTRING_PTR(RSYMBOL(sym)->fstr)); } RSYMBOL(sym)->fstr = (VALUE)NULL; @@ -855,8 +905,7 @@ symbols_i(st_data_t key, st_data_t value, st_data_t arg) VALUE sym = ID2SYM((ID)value); if (DYNAMIC_SYM_P(sym) && !SYMBOL_PINNED_P(sym) && rb_objspace_garbage_object_p(sym)) { - st_data_t sym_data = (st_data_t)sym; - st_delete(global_symbols.id_str, &sym_data, NULL); + id_str_delete((ID)sym); RSYMBOL(sym)->fstr = 0; return ST_DELETE; } -- 2.0.2.732.g0c88dd3.dirty