1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Use 1 byte hint for ar_table [Feature #15602]

On ar_table, Do not keep a full-length hash value (FLHV, 8 bytes)
but keep a 1 byte hint from a FLHV (lowest byte of FLHV).
An ar_table only contains at least 8 entries, so hints consumes
8 bytes at most. We can store hints in RHash::ar_hint.

On 32bit CPU, we use 4 entries ar_table.

The advantages:
* We don't need to keep FLHV so ar_table only consumes
  16 bytes (VALUEs of key and value) * 8 entries = 128 bytes.
* We don't need to scan ar_table, but only need to check hints
  in many cases. Especially we don't need to access ar_table
  if there is no match entries (in many cases).
  It will increase memory cache locality.

The disadvantages:
* This technique can increase `#eql?` time because hints can
  conflicts (in theory, it conflicts once in 256 times).
  It can introduce incompatibility if there is a object x where
  x.eql? returns true even if hash values are different.
  I believe we don't need to care such irregular case.
* We need to re-calculate FLHV if we need to switch from ar_table
  to st_table (e.g. exceeds 8 entries).
  It also can introduce incompatibility, on mutating key objects.
  I believe we don't need to care such irregular case too.

Add new debug counters to measure the performance:
* artable_hint_hit - hint is matched and eql?#=>true
* artable_hint_miss - hint is not matched but eql?#=>false
* artable_hint_notfound - lookup counts
This commit is contained in:
Koichi Sasada 2019-01-17 16:53:10 +00:00 committed by Koichi Sasada
parent ebd398ac5a
commit 72825c35b0
5 changed files with 207 additions and 156 deletions

View file

@ -223,7 +223,6 @@ RB_DEBUG_COUNTER(obj_hash_g8)
RB_DEBUG_COUNTER(obj_hash_ar)
RB_DEBUG_COUNTER(obj_hash_st)
RB_DEBUG_COUNTER(obj_hash_transient)
RB_DEBUG_COUNTER(obj_hash_force_convert)
RB_DEBUG_COUNTER(obj_struct_embed)
@ -262,6 +261,11 @@ RB_DEBUG_COUNTER(obj_iclass_ptr)
RB_DEBUG_COUNTER(obj_class_ptr)
RB_DEBUG_COUNTER(obj_module_ptr)
/* ar_table */
RB_DEBUG_COUNTER(artable_hint_hit)
RB_DEBUG_COUNTER(artable_hint_miss)
RB_DEBUG_COUNTER(artable_hint_notfound)
/* heap function counts
*
* * heap_xmalloc/realloc/xfree: call counts

View file

@ -273,7 +273,7 @@ dump_object(VALUE obj, struct dump_config *dc)
case T_HASH:
dump_append(dc, ", \"size\":%"PRIuSIZE, (size_t)RHASH_SIZE(obj));
if (FL_TEST(obj, HASH_PROC_DEFAULT))
if (FL_TEST(obj, RHASH_PROC_DEFAULT))
dump_append(dc, ", \"default\":\"%#"PRIxVALUE"\"", RHASH_IFNONE(obj));
break;

343
hash.c
View file

@ -37,11 +37,11 @@
#define HAS_EXTRA_STATES(hash, klass) ( \
((klass = has_extra_methods(rb_obj_class(hash))) != 0) || \
FL_TEST((hash), FL_EXIVAR|FL_TAINT|HASH_PROC_DEFAULT) || \
FL_TEST((hash), FL_EXIVAR|FL_TAINT|RHASH_PROC_DEFAULT) || \
!NIL_P(RHASH_IFNONE(hash)))
#define SET_DEFAULT(hash, ifnone) ( \
FL_UNSET_RAW(hash, HASH_PROC_DEFAULT), \
FL_UNSET_RAW(hash, RHASH_PROC_DEFAULT), \
RHASH_SET_IFNONE(hash, ifnone))
#define SET_PROC_DEFAULT(hash, proc) set_proc_default(hash, proc)
@ -51,8 +51,8 @@
static inline void
copy_default(struct RHash *hash, const struct RHash *hash2)
{
hash->basic.flags &= ~HASH_PROC_DEFAULT;
hash->basic.flags |= hash2->basic.flags & HASH_PROC_DEFAULT;
hash->basic.flags &= ~RHASH_PROC_DEFAULT;
hash->basic.flags |= hash2->basic.flags & RHASH_PROC_DEFAULT;
RHASH_SET_IFNONE(hash, RHASH_IFNONE(hash2));
}
@ -102,9 +102,6 @@ static int
rb_any_cmp(VALUE a, VALUE b)
{
if (a == b) return 0;
if (FIXNUM_P(a) && FIXNUM_P(b)) {
return a != b;
}
if (RB_TYPE_P(a, T_STRING) && RBASIC(a)->klass == rb_cString &&
RB_TYPE_P(b, T_STRING) && RBASIC(b)->klass == rb_cString) {
return rb_str_hash_cmp(a, b);
@ -175,10 +172,10 @@ any_hash(VALUE a, st_index_t (*other_func)(VALUE))
if (SPECIAL_CONST_P(a)) {
if (STATIC_SYM_P(a)) {
hnum = a >> (RUBY_SPECIAL_SHIFT + ID_SCOPE_SHIFT);
hnum = rb_hash_start(hnum);
goto out;
}
hnum = a >> (RUBY_SPECIAL_SHIFT + ID_SCOPE_SHIFT);
hnum = rb_hash_start(hnum);
goto out;
}
else if (FLONUM_P(a)) {
/* prevent pathological behavior: [Bug #10761] */
goto flt;
@ -315,12 +312,7 @@ static const struct st_hash_type identhash = {
rb_ident_hash,
};
#define RESERVED_HASH_VAL (~(st_hash_t) 0)
#define RESERVED_HASH_SUBSTITUTION_VAL ((st_hash_t) 0)
typedef st_index_t st_hash_t;
extern const st_hash_t st_reserved_hash_val;
extern const st_hash_t st_reserved_hash_substitution_val;
/*
* RHASH_AR_TABLE_P(h):
@ -332,17 +324,20 @@ extern const st_hash_t st_reserved_hash_substitution_val;
* * as.st points st_table.
*/
#define RHASH_AR_TABLE_MAX_SIZE 8
#define RHASH_AR_TABLE_MAX_SIZE sizeof(VALUE)
#define RHASH_AR_TABLE_MAX_BOUND RHASH_AR_TABLE_MAX_SIZE
typedef struct ar_table_entry {
st_hash_t hash;
#define RHASH_AR_TABLE_REF(hash, n) (&RHASH_AR_TABLE(hash)->pairs[n])
#define RHASH_AR_CLEARED_HINT 0xff
typedef struct ar_table_pair_struct {
VALUE key;
VALUE record;
} ar_table_entry;
VALUE val;
} ar_table_pair;
typedef struct ar_table_struct {
ar_table_entry entries[RHASH_AR_TABLE_MAX_SIZE];
/* 64bit CPU: 8B * 2 * 8 = 128B */
ar_table_pair pairs[RHASH_AR_TABLE_MAX_SIZE];
} ar_table;
size_t
@ -354,30 +349,63 @@ rb_hash_ar_table_size(void)
static inline st_hash_t
ar_do_hash(st_data_t key)
{
st_hash_t hash = (st_hash_t)rb_any_hash(key);
return (RESERVED_HASH_VAL == hash) ? RESERVED_HASH_SUBSTITUTION_VAL : hash;
return (st_hash_t)rb_any_hash(key);
}
static inline unsigned char
ar_do_hash_hint(st_hash_t hash_value)
{
return (unsigned char)hash_value;
}
static inline unsigned char
ar_hint(VALUE hash, unsigned int index)
{
return RHASH(hash)->ar_hint.ary[index];
}
static inline void
ar_set_entry(ar_table_entry *entry, st_data_t key, st_data_t val, st_hash_t hash)
ar_hint_set_hint(VALUE hash, unsigned int index, unsigned char hint)
{
entry->hash = hash;
entry->key = key;
entry->record = val;
RHASH(hash)->ar_hint.ary[index] = hint;
}
static inline void
ar_clear_entry(ar_table_entry* entry)
ar_hint_set(VALUE hash, unsigned int index, st_hash_t hash_value)
{
entry->key = Qundef;
entry->record = Qundef;
entry->hash = RESERVED_HASH_VAL;
ar_hint_set_hint(hash, index, ar_do_hash_hint(hash_value));
}
static inline void
ar_clear_entry(VALUE hash, unsigned int index)
{
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, index);
pair->key = Qundef;
ar_hint_set_hint(hash, index, RHASH_AR_CLEARED_HINT);
}
static inline int
ar_empty_entry(ar_table_entry *entry)
ar_cleared_entry(VALUE hash, unsigned int index)
{
return entry->hash == RESERVED_HASH_VAL;
if (ar_hint(hash, index) == RHASH_AR_CLEARED_HINT) {
/* RHASH_AR_CLEARED_HINT is only a hint, not mean cleared entry,
* so you need to check key == Qundef
*/
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, index);
return pair->key == Qundef;
}
else {
return FALSE;
}
}
static inline void
ar_set_entry(VALUE hash, unsigned int index, st_data_t key, st_data_t val, st_hash_t hash_value)
{
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, index);
pair->key = key;
pair->val = val;
ar_hint_set(hash, index, hash_value);
}
#define RHASH_AR_TABLE_SIZE(h) (HASH_ASSERT(RHASH_AR_TABLE_P(h)), \
@ -392,7 +420,6 @@ ar_empty_entry(ar_table_entry *entry)
#define RHASH_ST_TABLE_SET(h, s) rb_hash_st_table_set(h, s)
#define RHASH_TYPE(hash) (RHASH_AR_TABLE_P(hash) ? &objhash : RHASH_ST_TABLE(hash)->type)
#define RHASH_AR_TABLE_REF(hash, n) (&RHASH_AR_TABLE(hash)->entries[n])
#define HASH_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(1, expr, #expr)
@ -411,17 +438,17 @@ rb_hash_dump(VALUE hash)
RHASH_AR_TABLE_SIZE(hash), RHASH_AR_TABLE_BOUND(hash));
for (i=0; i<bound; i++) {
ar_table_entry *cur_entry = RHASH_AR_TABLE_REF(hash, i);
st_data_t k, v;
if (!ar_empty_entry(cur_entry)) {
if (!ar_cleared_entry(hash, i)) {
char b1[0x100], b2[0x100];
/* h = cur_entry->hash; */
k = cur_entry->key;
v = cur_entry->record;
fprintf(stderr, " %d key:%s val:%s\n", i,
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
k = pair->key;
v = pair->val;
fprintf(stderr, " %d key:%s val:%s hint:%02x\n", i,
rb_raw_obj_info(b1, 0x100, k),
rb_raw_obj_info(b2, 0x100, v));
rb_raw_obj_info(b2, 0x100, v),
ar_hint(hash, i));
n++;
}
else {
@ -440,13 +467,11 @@ hash_verify_(VALUE hash, const char *file, int line)
unsigned i, n = 0, bound = RHASH_AR_TABLE_BOUND(hash);
for (i=0; i<bound; i++) {
ar_table_entry *cur_entry = RHASH_AR_TABLE_REF(hash, i);
st_data_t h, k, v;
if (!ar_empty_entry(cur_entry)) {
h = cur_entry->hash;
k = cur_entry->key;
v = cur_entry->record;
HASH_ASSERT(h != RESERVED_HASH_VAL);
st_data_t k, v;
if (!ar_cleared_entry(hash, i)) {
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
k = pair->key;
v = pair->val;
HASH_ASSERT(k != Qundef);
HASH_ASSERT(v != Qundef);
n++;
@ -606,31 +631,62 @@ ar_alloc_table(VALUE hash)
return tab;
}
static inline int
NOINLINE(static int ar_equal(VALUE x, VALUE y));
static int
ar_equal(VALUE x, VALUE y)
{
return x == y || rb_any_cmp(x, y) == 0;
return rb_any_cmp(x, y) == 0;
}
static inline int
ar_ptr_equal(ar_table_entry *entry, st_hash_t hash_val, VALUE key)
static unsigned
ar_find_entry_hint(VALUE hash, unsigned char hint, st_data_t key)
{
return entry->hash == hash_val && ar_equal(key, entry->key);
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
const unsigned char *hints = RHASH(hash)->ar_hint.ary;
/* if table is NULL, then bound also should be 0 */
for (i = 0; i < bound; i++) {
if (hints[i] == hint) {
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
if (ar_equal(key, pair->key)) {
RB_DEBUG_COUNTER_INC(artable_hint_hit);
return i;
}
else {
#if 0
static int pid;
static char fname[256];
static FILE *fp;
if (pid != getpid()) {
snprintf(fname, sizeof(fname), "/tmp/ruby-armiss.%d", pid = getpid());
if ((fp = fopen(fname, "w")) == NULL) rb_bug("fopen");
}
st_hash_t h1 = ar_do_hash(key);
st_hash_t h2 = ar_do_hash(pair->key);
fprintf(fp, "miss: hash_eq:%d hints[%d]:%02x hint:%02x\n"
" key :%016lx %s\n"
" pair->key:%016lx %s\n",
h1 == h2, i, hints[i], hint,
h1, rb_obj_info(key), h2, rb_obj_info(pair->key));
#endif
RB_DEBUG_COUNTER_INC(artable_hint_miss);
}
}
}
RB_DEBUG_COUNTER_INC(artable_hint_notfound);
return RHASH_AR_TABLE_MAX_BOUND;
}
static unsigned
ar_find_entry(VALUE hash, st_hash_t hash_value, st_data_t key)
{
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
/* if table is NULL, then bound also should be 0 */
for (i = 0; i < bound; i++) {
if (ar_ptr_equal(RHASH_AR_TABLE_REF(hash, i), hash_value, key)) {
return i;
}
}
return RHASH_AR_TABLE_MAX_BOUND;
unsigned char hint = ar_do_hash_hint(hash_value);
return ar_find_entry_hint(hash, hint, key);
}
static inline void
@ -652,13 +708,10 @@ ar_free_and_clear_table(VALUE hash)
HASH_ASSERT(RHASH_TRANSIENT_P(hash) == 0);
}
void st_add_direct_with_hash(st_table *tab, st_data_t key, st_data_t value, st_hash_t hash); /* st.c */
static void
ar_try_convert_table(VALUE hash)
{
st_table *new_tab;
ar_table_entry *entry;
const unsigned size = RHASH_AR_TABLE_SIZE(hash);
st_index_t i;
@ -669,10 +722,8 @@ ar_try_convert_table(VALUE hash)
new_tab = st_init_table_with_size(&objhash, size * 2);
for (i = 0; i < RHASH_AR_TABLE_MAX_BOUND; i++) {
entry = RHASH_AR_TABLE_REF(hash, i);
HASH_ASSERT(entry->hash != RESERVED_HASH_VAL);
st_add_direct_with_hash(new_tab, entry->key, entry->record, entry->hash);
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
st_add_direct(new_tab, pair->key, pair->val);
}
ar_free_and_clear_table(hash);
RHASH_ST_TABLE_SET(hash, new_tab);
@ -689,7 +740,6 @@ ar_force_convert_table(VALUE hash, const char *file, int line)
}
if (RHASH_AR_TABLE(hash)) {
ar_table_entry *entry;
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
#if RHASH_CONVERT_TABLE_DEBUG
@ -701,10 +751,10 @@ ar_force_convert_table(VALUE hash, const char *file, int line)
new_tab = st_init_table_with_size(&objhash, RHASH_AR_TABLE_SIZE(hash));
for (i = 0; i < bound; i++) {
entry = RHASH_AR_TABLE_REF(hash, i);
if (ar_empty_entry(entry)) continue;
if (ar_cleared_entry(hash, i)) continue;
st_add_direct_with_hash(new_tab, entry->key, entry->record, entry->hash);
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
st_add_direct(new_tab, pair->key, pair->val);
}
ar_free_and_clear_table(hash);
}
@ -736,15 +786,16 @@ ar_compact_table(VALUE hash)
}
else {
unsigned i, j=0;
ar_table_entry *entries = RHASH_AR_TABLE_REF(hash, 0);
ar_table_pair *pairs = RHASH_AR_TABLE(hash)->pairs;
for (i=0; i<bound; i++) {
if (ar_empty_entry(&entries[i])) {
if (ar_cleared_entry(hash, i)) {
if (j <= i) j = i+1;
for (; j<bound; j++) {
if (!ar_empty_entry(&entries[j])) {
entries[i] = entries[j];
ar_clear_entry(&entries[j]);
if (!ar_cleared_entry(hash, j)) {
pairs[i] = pairs[j];
ar_hint_set_hint(hash, i, (st_hash_t)ar_hint(hash, j));
ar_clear_entry(hash, j);
j++;
goto found;
}
@ -778,7 +829,7 @@ ar_add_direct_with_hash(VALUE hash, st_data_t key, st_data_t val, st_hash_t hash
}
HASH_ASSERT(bin < RHASH_AR_TABLE_MAX_BOUND);
ar_set_entry(RHASH_AR_TABLE_REF(hash, bin), key, val, hash_value);
ar_set_entry(hash, bin, key, val, hash_value);
RHASH_AR_TABLE_BOUND_SET(hash, bin+1);
RHASH_AR_TABLE_SIZE_INC(hash);
return 0;
@ -792,11 +843,11 @@ ar_general_foreach(VALUE hash, int (*func)(ANYARGS), st_update_callback_func *re
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
for (i = 0; i < bound; i++) {
enum st_retval retval;
ar_table_entry *cur_entry = RHASH_AR_TABLE_REF(hash, i);
if (ar_empty_entry(cur_entry)) continue;
retval = (*func)(cur_entry->key, cur_entry->record, arg, 0);
/* cur_entry is not valid after that */
if (ar_cleared_entry(hash, i)) continue;
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
enum st_retval retval = (*func)(pair->key, pair->val, arg, 0);
/* pair may be not valid here because of theap */
switch (retval) {
case ST_CONTINUE:
@ -806,20 +857,18 @@ ar_general_foreach(VALUE hash, int (*func)(ANYARGS), st_update_callback_func *re
return 0;
case ST_REPLACE:
if (replace) {
VALUE key;
VALUE value;
VALUE key = pair->key;
VALUE val = pair->val;
retval = (*replace)(&key, &val, arg, TRUE);
key = cur_entry->key;
value = cur_entry->record;
retval = (*replace)(&key, &value, arg, TRUE);
ar_table_entry *entry = RHASH_AR_TABLE_REF(hash, i);
entry->key = key;
entry->record = value;
// TODO: pair should be same as pair before.
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
pair->key = key;
pair->val = val;
}
break;
case ST_DELETE:
ar_clear_entry(RHASH_AR_TABLE_REF(hash, i));
ar_clear_entry(hash, i);
RHASH_AR_TABLE_SIZE_DEC(hash);
break;
}
@ -847,27 +896,24 @@ ar_foreach_check(VALUE hash, int (*func)(ANYARGS), st_data_t arg,
if (RHASH_AR_TABLE_SIZE(hash) > 0) {
unsigned i, ret = 0, bound = RHASH_AR_TABLE_BOUND(hash);
enum st_retval retval;
ar_table_entry *cur_entry;
st_data_t key;
st_hash_t hash_value;
ar_table_pair *pair;
unsigned char hint;
for (i = 0; i < bound; i++) {
cur_entry = RHASH_AR_TABLE_REF(hash, i);
if (ar_empty_entry(cur_entry))
continue;
key = cur_entry->key;
hash_value = cur_entry->hash;
if (ar_cleared_entry(hash, i)) continue;
retval = (*func)(key, cur_entry->record, arg, 0);
pair = RHASH_AR_TABLE_REF(hash, i);
key = pair->key;
hint = ar_hint(hash, i);
retval = (*func)(key, pair->val, arg, 0);
hash_verify(hash);
cur_entry = RHASH_AR_TABLE_REF(hash, i);
switch (retval) {
case ST_CHECK: {
if (cur_entry->key == never && cur_entry->hash == RESERVED_HASH_VAL)
break;
ret = ar_find_entry(hash, hash_value, key);
if (pair->key == never) break;
ret = ar_find_entry_hint(hash, hint, key);
if (ret == RHASH_AR_TABLE_MAX_BOUND) {
retval = (*func)(0, 0, arg, 1);
return 2;
@ -879,8 +925,8 @@ ar_foreach_check(VALUE hash, int (*func)(ANYARGS), st_data_t arg,
case ST_REPLACE:
return 0;
case ST_DELETE: {
if (!ar_empty_entry(cur_entry)) {
ar_clear_entry(cur_entry);
if (!ar_cleared_entry(hash, i)) {
ar_clear_entry(hash, i);
RHASH_AR_TABLE_SIZE_DEC(hash);
}
break;
@ -910,12 +956,13 @@ ar_update(VALUE hash, st_data_t key,
}
if (existing) {
ar_table_entry *entry = RHASH_AR_TABLE_REF(hash, bin);
key = entry->key;
value = entry->record;
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, bin);
key = pair->key;
value = pair->val;
}
old_key = key;
retval = (*func)(&key, &value, arg, existing);
/* pair can be invalid here because of theap */
switch (retval) {
case ST_CONTINUE:
@ -925,16 +972,16 @@ ar_update(VALUE hash, st_data_t key,
}
}
else {
ar_table_entry *entry = RHASH_AR_TABLE_REF(hash, bin);
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, bin);
if (old_key != key) {
entry->key = key;
pair->key = key;
}
entry->record = value;
pair->val = value;
}
break;
case ST_DELETE:
if (existing) {
ar_clear_entry(RHASH_AR_TABLE_REF(hash, bin));
ar_clear_entry(hash, bin);
RHASH_AR_TABLE_SIZE_DEC(hash);
}
break;
@ -961,13 +1008,13 @@ ar_insert(VALUE hash, st_data_t key, st_data_t value)
}
HASH_ASSERT(bin < RHASH_AR_TABLE_MAX_BOUND);
ar_set_entry(RHASH_AR_TABLE_REF(hash, bin), key, value, hash_value);
ar_set_entry(hash, bin, key, value, hash_value);
RHASH_AR_TABLE_BOUND_SET(hash, bin+1);
RHASH_AR_TABLE_SIZE_INC(hash);
return 0;
}
else {
RHASH_AR_TABLE_REF(hash, bin)->record = value;
RHASH_AR_TABLE_REF(hash, bin)->val = value;
return 1;
}
}
@ -988,7 +1035,7 @@ ar_lookup(VALUE hash, st_data_t key, st_data_t *value)
else {
HASH_ASSERT(bin < RHASH_AR_TABLE_MAX_BOUND);
if (value != NULL) {
*value = RHASH_AR_TABLE_REF(hash, bin)->record;
*value = RHASH_AR_TABLE_REF(hash, bin)->val;
}
return 1;
}
@ -1009,9 +1056,11 @@ ar_delete(VALUE hash, st_data_t *key, st_data_t *value)
return 0;
}
else {
ar_table_entry *entry = RHASH_AR_TABLE_REF(hash, bin);
if (value != 0) *value = entry->record;
ar_clear_entry(entry);
if (value != 0) {
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, bin);
*value = pair->val;
}
ar_clear_entry(hash, bin);
RHASH_AR_TABLE_SIZE_DEC(hash);
return 1;
}
@ -1022,20 +1071,19 @@ ar_shift(VALUE hash, st_data_t *key, st_data_t *value)
{
if (RHASH_AR_TABLE_SIZE(hash) > 0) {
unsigned i, bound = RHASH_AR_TABLE_BOUND(hash);
ar_table_entry *entry, *entries = RHASH_AR_TABLE(hash)->entries;
for (i = 0; i < bound; i++) {
entry = &entries[i];
if (!ar_empty_entry(entry)) {
if (value != 0) *value = entry->record;
*key = entry->key;
ar_clear_entry(entry);
if (!ar_cleared_entry(hash, i)) {
ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, i);
if (value != 0) *value = pair->val;
*key = pair->key;
ar_clear_entry(hash, i);
RHASH_AR_TABLE_SIZE_DEC(hash);
return 1;
}
}
}
if (value != 0) *value = 0;
if (value != NULL) *value = 0;
return 0;
}
@ -1050,9 +1098,9 @@ ar_keys(VALUE hash, st_data_t *keys, st_index_t size)
break;
}
else {
ar_table_entry *cur_entry = RHASH_AR_TABLE_REF(hash, i);
if (!ar_empty_entry(cur_entry))
*keys++ = cur_entry->key;
if (!ar_cleared_entry(hash, i)) {
*keys++ = RHASH_AR_TABLE_REF(hash, i)->key;
}
}
}
@ -1070,9 +1118,9 @@ ar_values(VALUE hash, st_data_t *values, st_index_t size)
break;
}
else {
ar_table_entry *cur_entry = RHASH_AR_TABLE_REF(hash, i);
if (!ar_empty_entry(cur_entry))
*values++ = cur_entry->record;
if (!ar_cleared_entry(hash, i)) {
*values++ = RHASH_AR_TABLE_REF(hash, i)->val;
}
}
}
@ -1097,6 +1145,7 @@ ar_copy(VALUE hash1, VALUE hash2)
}
}
*new_tab = *old_tab;
RHASH(hash1)->ar_hint.word = RHASH(hash2)->ar_hint.word;
RHASH_AR_TABLE_BOUND_SET(hash1, RHASH_AR_TABLE_BOUND(hash2));
RHASH_AR_TABLE_SIZE_SET(hash1, RHASH_AR_TABLE_SIZE(hash2));
RHASH_AR_TABLE_SET(hash1, new_tab);
@ -1456,7 +1505,7 @@ rb_hash_dup(VALUE hash)
{
const VALUE flags = RBASIC(hash)->flags;
VALUE ret = hash_dup(hash, rb_obj_class(hash),
flags & (FL_EXIVAR|FL_TAINT|HASH_PROC_DEFAULT));
flags & (FL_EXIVAR|FL_TAINT|RHASH_PROC_DEFAULT));
if (flags & FL_EXIVAR)
rb_copy_generic_ivar(ret, hash);
return ret;
@ -1597,7 +1646,7 @@ set_proc_default(VALUE hash, VALUE proc)
}
}
FL_SET_RAW(hash, HASH_PROC_DEFAULT);
FL_SET_RAW(hash, RHASH_PROC_DEFAULT);
RHASH_SET_IFNONE(hash, proc);
}
@ -1836,7 +1885,7 @@ rb_hash_default_value(VALUE hash, VALUE key)
{
if (rb_method_basic_definition_p(CLASS_OF(hash), id_default)) {
VALUE ifnone = RHASH_IFNONE(hash);
if (!FL_TEST(hash, HASH_PROC_DEFAULT)) return ifnone;
if (!FL_TEST(hash, RHASH_PROC_DEFAULT)) return ifnone;
if (key == Qundef) return Qnil;
return rb_funcall(ifnone, id_yield, 2, hash, key);
}
@ -1896,7 +1945,7 @@ rb_hash_lookup2(VALUE hash, VALUE key, VALUE def)
{
st_data_t val;
if (rb_hash_stlike_lookup(hash, key, &val)) {
if (hash_stlike_lookup(hash, key, &val)) {
return (VALUE)val;
}
else {
@ -2009,7 +2058,7 @@ rb_hash_default(int argc, VALUE *argv, VALUE hash)
rb_check_arity(argc, 0, 1);
ifnone = RHASH_IFNONE(hash);
if (FL_TEST(hash, HASH_PROC_DEFAULT)) {
if (FL_TEST(hash, RHASH_PROC_DEFAULT)) {
if (argc == 0) return Qnil;
args[0] = hash;
args[1] = argv[0];
@ -2064,7 +2113,7 @@ rb_hash_set_default(VALUE hash, VALUE ifnone)
static VALUE
rb_hash_default_proc(VALUE hash)
{
if (FL_TEST(hash, HASH_PROC_DEFAULT)) {
if (FL_TEST(hash, RHASH_PROC_DEFAULT)) {
return RHASH_IFNONE(hash);
}
return Qnil;
@ -3253,7 +3302,7 @@ rb_hash_to_h(VALUE hash)
}
if (rb_obj_class(hash) != rb_cHash) {
const VALUE flags = RBASIC(hash)->flags;
hash = hash_dup(hash, rb_cHash, flags & HASH_PROC_DEFAULT);
hash = hash_dup(hash, rb_cHash, flags & RHASH_PROC_DEFAULT);
}
return hash;
}
@ -3497,7 +3546,7 @@ hash_equal(VALUE hash1, VALUE hash2, int eql)
#if 0
if (!(rb_equal(RHASH_IFNONE(hash1), RHASH_IFNONE(hash2)) &&
FL_TEST(hash1, HASH_PROC_DEFAULT) == FL_TEST(hash2, HASH_PROC_DEFAULT)))
FL_TEST(hash1, RHASH_PROC_DEFAULT) == FL_TEST(hash2, RHASH_PROC_DEFAULT)))
return Qfalse;
#endif
return Qtrue;
@ -5986,10 +6035,6 @@ Init_Hash(void)
{
#undef rb_intern
#define rb_intern(str) rb_intern_const(str)
RUBY_ASSERT(RESERVED_HASH_VAL == st_reserved_hash_val);
RUBY_ASSERT(RESERVED_HASH_SUBSTITUTION_VAL == st_reserved_hash_substitution_val);
id_hash = rb_intern("hash");
id_yield = rb_intern("yield");
id_default = rb_intern("default");

View file

@ -815,6 +815,7 @@ struct RComplex {
#define RCOMPLEX_SET_IMAG(cmp, i) RB_OBJ_WRITE((cmp), &((struct RComplex *)(cmp))->imag,(i))
enum ruby_rhash_flags {
RHASH_PROC_DEFAULT = FL_USER2, /* FL 2 */
RHASH_ST_TABLE_FLAG = FL_USER3, /* FL 3 */
RHASH_AR_TABLE_MAX_SIZE = 8,
RHASH_AR_TABLE_SIZE_MASK = (FL_USER4|FL_USER5|FL_USER6|FL_USER7), /* FL 4..7 */
@ -834,8 +835,6 @@ enum ruby_rhash_flags {
RHASH_ENUM_END
};
#define HASH_PROC_DEFAULT FL_USER2
#define RHASH_AR_TABLE_SIZE_RAW(h) \
((unsigned int)((RBASIC(h)->flags & RHASH_AR_TABLE_SIZE_MASK) >> RHASH_AR_TABLE_SIZE_SHIFT))
@ -881,7 +880,10 @@ struct RHash {
struct ar_table_struct *ar; /* possibly 0 */
} as;
const VALUE ifnone;
const VALUE reserved;
union {
unsigned char ary[sizeof(VALUE)];
VALUE word;
} ar_hint;
};
#ifdef RHASH_IFNONE
@ -890,7 +892,7 @@ struct RHash {
# define RHASH_IFNONE(h) (RHASH(h)->ifnone)
# define RHASH_SIZE(h) (RHASH_AR_TABLE_P(h) ? RHASH_AR_TABLE_SIZE_RAW(h) : RHASH_ST_SIZE(h))
#endif /* #ifdef RHASH_ITER_LEV */
#endif /* ifdef RHASH_IFNONE */
struct RMoved {
VALUE flags;

View file

@ -917,7 +917,7 @@ w_object(VALUE obj, struct dump_arg *arg, int limit)
if (NIL_P(RHASH_IFNONE(obj))) {
w_byte(TYPE_HASH, arg);
}
else if (FL_TEST(obj, HASH_PROC_DEFAULT)) {
else if (FL_TEST(obj, RHASH_PROC_DEFAULT)) {
rb_raise(rb_eTypeError, "can't dump hash with default proc");
}
else {