From 3fdb2555cc1af53388f9e4499c8fce62aa4c3f5d Mon Sep 17 00:00:00 2001
From: Sokolov Yura aka funny_falcon <funny.falcon@gmail.com>
Date: Tue, 31 Jan 2017 16:31:26 +0300
Subject: [PATCH 1/2] id_table.c, symbol.c: reduce id_table memory consumption
 on 64bit platform

- embed collision bit into key
- use array of keys + array of values instead of array of struct item_t.
Both arrays allocated at once, and pointer to values is calculated.

Additional changes:
- stricter check in next_id_base.
  One bit is needed for collision check on 64bit platform.
  Range is more limited on 32bit platform with ID_SCOPE_SHIFT and RUBY_SPECIAL_SHIFT.
- stricter check for result of next_id_base.
  Rais exception in rb_sym2id, assert in rb_make_internal_id.
- allow to shrink id_table.
  Shrink was disabled for mix_table cause it leads to error in id_table type
  detection. Since mix_table were removed, it is safe to shrink it again.
---
 id_table.c | 83 +++++++++++++++++++++++++-------------------------------------
 symbol.c   | 22 +++++++++++++++--
 2 files changed, 53 insertions(+), 52 deletions(-)

diff --git a/id_table.c b/id_table.c
index 6fe14d7..d5bd53c 100644
--- a/id_table.c
+++ b/id_table.c
@@ -6,11 +6,6 @@
 #define ID_TABLE_DEBUG 0
 #endif
 
-#if ID_TABLE_DEBUG == 0
-#define NDEBUG
-#endif
-#include "ruby_assert.h"
-
 typedef rb_id_serial_t id_key_t;
 
 static inline ID
@@ -29,42 +24,30 @@ id2key(ID id)
    uses mark-bit on collisions - need extra 1 bit,
    ID is strictly 3 bits larger than rb_id_serial_t */
 
-typedef struct rb_id_item {
-    id_key_t key;
-#if SIZEOF_VALUE == 8
-    int      collision;
-#endif
-    VALUE    val;
-} item_t;
-
 struct rb_id_table {
     int capa;
     int num;
     int used;
-    item_t *items;
+    id_key_t *keys;
 };
 
-#if SIZEOF_VALUE == 8
-#define ITEM_GET_KEY(tbl, i) ((tbl)->items[i].key)
-#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items[i].key)
-#define ITEM_COLLIDED(tbl, i) ((tbl)->items[i].collision)
-#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->items[i].collision = 1)
+#define ITEM_GET_KEY(tbl, i) ((tbl)->keys[i] >> 1)
+#define ITEM_KEY_ISSET(tbl, i) ((tbl)->keys[i] > 1)
+#define ITEM_COLLIDED(tbl, i) ((tbl)->keys[i] & 1)
+#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->keys[i] |= 1)
 static inline void
 ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key)
 {
-    tbl->items[i].key = key;
+    tbl->keys[i] = (key << 1) | ITEM_COLLIDED(tbl, i);
 }
-#else
-#define ITEM_GET_KEY(tbl, i) ((tbl)->items[i].key >> 1)
-#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items[i].key > 1)
-#define ITEM_COLLIDED(tbl, i) ((tbl)->items[i].key & 1)
-#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->items[i].key |= 1)
+#define ID_TABLE_VALUES(tbl) ((VALUE*)((tbl)->keys+(tbl)->capa))
+#define ITEM_GET_VALUE(tbl, i) (ID_TABLE_VALUES(tbl)[i])
 static inline void
-ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key)
+ITEM_SET_VALUE(struct rb_id_table *tbl, int i, VALUE val)
 {
-    tbl->items[i].key = (key << 1) | ITEM_COLLIDED(tbl, i);
+    ID_TABLE_VALUES(tbl)[i] = val;
 }
-#endif
+#define ITEM_SIZE (sizeof(id_key_t) + sizeof(VALUE))
 
 static inline int
 round_capa(int capa)
@@ -86,7 +69,7 @@ rb_id_table_init(struct rb_id_table *tbl, int capa)
     if (capa > 0) {
 	capa = round_capa(capa);
 	tbl->capa = (int)capa;
-	tbl->items = ZALLOC_N(item_t, capa);
+	tbl->keys = (id_key_t*)ruby_xcalloc(capa, ITEM_SIZE);
     }
     return tbl;
 }
@@ -101,7 +84,7 @@ rb_id_table_create(size_t capa)
 void
 rb_id_table_free(struct rb_id_table *tbl)
 {
-    xfree(tbl->items);
+    xfree(tbl->keys);
     xfree(tbl);
 }
 
@@ -110,7 +93,7 @@ rb_id_table_clear(struct rb_id_table *tbl)
 {
     tbl->num = 0;
     tbl->used = 0;
-    MEMZERO(tbl->items, item_t, tbl->capa);
+    memset(tbl->keys, 0, tbl->capa * ITEM_SIZE);
 }
 
 size_t
@@ -122,7 +105,7 @@ rb_id_table_size(const struct rb_id_table *tbl)
 size_t
 rb_id_table_memsize(const struct rb_id_table *tbl)
 {
-    return sizeof(item_t) * tbl->capa + sizeof(struct rb_id_table);
+    return ITEM_SIZE * (size_t)tbl->capa + sizeof(struct rb_id_table);
 }
 
 static int
@@ -149,7 +132,9 @@ hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val)
     int mask = tbl->capa - 1;
     int ix = key & mask;
     int d = 1;
-    assert(key != 0);
+#if ID_TABLE_DEBUG
+    assert(key > 0);
+#endif
     while (ITEM_KEY_ISSET(tbl, ix)) {
 	ITEM_SET_COLLIDED(tbl, ix);
 	ix = (ix + d) & mask;
@@ -160,7 +145,7 @@ hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val)
 	tbl->used++;
     }
     ITEM_SET_KEY(tbl, ix, key);
-    tbl->items[ix].val = val;
+    ITEM_SET_VALUE(tbl, ix, val);
 }
 
 static int
@@ -172,7 +157,7 @@ hash_delete_index(struct rb_id_table *tbl, int ix)
 	}
 	tbl->num--;
 	ITEM_SET_KEY(tbl, ix, 0);
-	tbl->items[ix].val = 0;
+	ITEM_SET_VALUE(tbl, ix, 0);
 	return TRUE;
     } else {
 	return FALSE;
@@ -182,23 +167,20 @@ hash_delete_index(struct rb_id_table *tbl, int ix)
 static void
 hash_table_extend(struct rb_id_table* tbl)
 {
+    /* fill rate 66% */
     if (tbl->used + (tbl->used >> 1) >= tbl->capa) {
-	int new_cap = round_capa(tbl->num + (tbl->num >> 1));
+	struct rb_id_table tmp_tbl;
 	int i;
-	item_t* old;
-	struct rb_id_table tmp_tbl = {0, 0, 0};
-	if (new_cap < tbl->capa) {
-	    new_cap = round_capa(tbl->used + (tbl->used >> 1));
-	}
-	tmp_tbl.capa = new_cap;
-	tmp_tbl.items = ZALLOC_N(item_t, new_cap);
+	id_key_t* old;
+	VALUE* values = ID_TABLE_VALUES(tbl);
+	rb_id_table_init(&tmp_tbl, tbl->num + (tbl->num >> 1) + 1);
 	for (i = 0; i < tbl->capa; i++) {
 	    id_key_t key = ITEM_GET_KEY(tbl, i);
 	    if (key != 0) {
-		hash_table_raw_insert(&tmp_tbl, key, tbl->items[i].val);
+		hash_table_raw_insert(&tmp_tbl, key, values[i]);
 	    }
 	}
-	old = tbl->items;
+	old = tbl->keys;
 	*tbl = tmp_tbl;
 	xfree(old);
     }
@@ -228,7 +210,7 @@ rb_id_table_lookup(struct rb_id_table *tbl, ID id, VALUE *valp)
     int index = hash_table_index(tbl, key);
 
     if (index >= 0) {
-	*valp = tbl->items[index].val;
+	*valp = ITEM_GET_VALUE(tbl, index);
 	return TRUE;
     }
     else {
@@ -242,7 +224,7 @@ rb_id_table_insert_key(struct rb_id_table *tbl, const id_key_t key, const VALUE
     const int index = hash_table_index(tbl, key);
 
     if (index >= 0) {
-	tbl->items[index].val = val;
+	ITEM_SET_VALUE(tbl, index, val);
     }
     else {
 	hash_table_extend(tbl);
@@ -273,8 +255,8 @@ rb_id_table_foreach(struct rb_id_table *tbl, rb_id_table_foreach_func_t *func, v
     for (i=0; i<capa; i++) {
 	if (ITEM_KEY_ISSET(tbl, i)) {
 	    const id_key_t key = ITEM_GET_KEY(tbl, i);
-	    enum rb_id_table_iterator_result ret = (*func)(key2id(key), tbl->items[i].val, data);
-	    assert(key != 0);
+	    const VALUE val = ITEM_GET_VALUE(tbl, i);
+	    enum rb_id_table_iterator_result ret = (*func)(key2id(key), val, data);
 
 	    if (ret == ID_TABLE_DELETE)
 		hash_delete_index(tbl, i);
@@ -291,7 +273,8 @@ rb_id_table_foreach_values(struct rb_id_table *tbl, rb_id_table_foreach_values_f
 
     for (i=0; i<capa; i++) {
 	if (ITEM_KEY_ISSET(tbl, i)) {
-	    enum rb_id_table_iterator_result ret = (*func)(tbl->items[i].val, data);
+	    const VALUE val = ITEM_GET_VALUE(tbl, i);
+	    enum rb_id_table_iterator_result ret = (*func)(val, data);
 
 	    if (ret == ID_TABLE_DELETE)
 		hash_delete_index(tbl, i);
diff --git a/symbol.c b/symbol.c
index facdb3c..623dd7d 100644
--- a/symbol.c
+++ b/symbol.c
@@ -14,6 +14,7 @@
 #include "symbol.h"
 #include "gc.h"
 #include "probes.h"
+#include "ruby_assert.h"
 
 #ifndef SYMBOL_DEBUG
 # define SYMBOL_DEBUG 0
@@ -569,7 +570,13 @@ next_id_base(void)
 {
     rb_id_serial_t next_serial = global_symbols.last_id + 1;
 
-    if (next_serial == 0) {
+    if (sizeof(next_serial) >= sizeof(ID) &&
+	    next_serial >= (rb_id_serial_t)(~(ID)0 >> (ID_SCOPE_SHIFT + RUBY_SPECIAL_SHIFT))) {
+	return (ID)-1;
+    }
+    /* need at lest 1 bit for collision mark in id_table */
+    else if (sizeof(next_serial) < sizeof(ID) &&
+	    next_serial >= ~(rb_id_serial_t)0 >> 1) {
 	return (ID)-1;
     }
     else {
@@ -708,6 +715,13 @@ rb_sym2id(VALUE sym)
 	    VALUE fstr = RSYMBOL(sym)->fstr;
 	    ID num = next_id_base();
 
+	    if (num == (ID)-1) {
+		fstr = rb_str_ellipsize(fstr, 20);
+		rb_raise(rb_eRuntimeError,
+				"symbol table overflow (symbol %"PRIsVALUE")",
+				fstr);
+	    }
+
 	    RSYMBOL(sym)->id = id |= num;
 	    /* make it permanent object */
 	    set_id_entry(rb_id_to_serial(num), fstr, sym);
@@ -767,7 +781,11 @@ rb_id2name(ID id)
 ID
 rb_make_internal_id(void)
 {
-    return next_id_base() | ID_INTERNAL | ID_STATIC_SYM;
+    ID nid = next_id_base();
+    /* make_internal_id called very rarely during initialization,
+     * so don't bother with exception */
+    assert(nid != (ID)-1);
+    return nid | ID_INTERNAL | ID_STATIC_SYM;
 }
 
 static int
-- 
2.9.3


From b66f30e5fce44aeeacbd697f9811ae8cafddefb9 Mon Sep 17 00:00:00 2001
From: Sokolov Yura aka funny_falcon <funny.falcon@gmail.com>
Date: Tue, 31 Jan 2017 16:40:22 +0300
Subject: [PATCH 2/2] id_table.c: mix key bits to step to improve big table
 performace.

quadratic probing may suffer from secondary clustering.
Small tables which fits into several cache lines still performs good,
but larger tables performs worse.
With this change some higher bits of key are mixed into step value.
Note: second value is still looked next to first.
---
 id_table.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/id_table.c b/id_table.c
index d5bd53c..0344032 100644
--- a/id_table.c
+++ b/id_table.c
@@ -114,12 +114,13 @@ hash_table_index(struct rb_id_table* tbl, id_key_t key)
     if (tbl->capa > 0) {
 	int mask = tbl->capa - 1;
 	int ix = key & mask;
+	id_key_t mix = tbl->capa > 64 ? key : 0;
 	int d = 1;
 	while (key != ITEM_GET_KEY(tbl, ix)) {
 	    if (!ITEM_COLLIDED(tbl, ix))
 		return -1;
 	    ix = (ix + d) & mask;
-	    d++;
+	    d += 1 + (mix >>= 7);
 	}
 	return ix;
     }
@@ -131,6 +132,7 @@ hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val)
 {
     int mask = tbl->capa - 1;
     int ix = key & mask;
+    id_key_t mix = tbl->capa > 64 ? key : 0;
     int d = 1;
 #if ID_TABLE_DEBUG
     assert(key > 0);
@@ -138,7 +140,7 @@ hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val)
     while (ITEM_KEY_ISSET(tbl, ix)) {
 	ITEM_SET_COLLIDED(tbl, ix);
 	ix = (ix + d) & mask;
-	d++;
+	d += 1 + (mix >>= 7);
     }
     tbl->num++;
     if (!ITEM_COLLIDED(tbl, ix)) {
-- 
2.9.3

