Split inttable into a hash part and an array part.

upb_inttable() now supports a "compact" operation that will
decide on an array size and put all entries with small enough
keys into the array part for faster lookup.

Also exposed the upb_itof_ent structure and put a few useful
values there, so they are one fewer pointer chase away.
pull/13171/head
Joshua Haberman 14 years ago
parent f9a6f67e27
commit f1e1cc4695
  1. 6
      src/upb.h
  2. 28
      src/upb_decoder.c
  3. 19
      src/upb_def.c
  4. 30
      src/upb_def.h
  5. 360
      src/upb_table.c
  6. 164
      src/upb_table.h
  7. 29
      tests/test_table.cc

@ -29,6 +29,12 @@ extern "C" {
#define UPB_MIN(x, y) ((x) < (y) ? (x) : (y)) #define UPB_MIN(x, y) ((x) < (y) ? (x) : (y))
#define UPB_INDEX(base, i, m) (void*)((char*)(base) + ((i)*(m))) #define UPB_INDEX(base, i, m) (void*)((char*)(base) + ((i)*(m)))
// Rounds val up to the next multiple of align.
INLINE size_t upb_align_up(size_t val, size_t align) {
return val % align == 0 ? val : val + align - (val % align);
}
// The maximum that any submessages can be nested. Matches proto2's limit. // The maximum that any submessages can be nested. Matches proto2's limit.
#define UPB_MAX_NESTING 64 #define UPB_MAX_NESTING 64

@ -11,6 +11,12 @@
#include <stdlib.h> #include <stdlib.h>
#include "upb_def.h" #include "upb_def.h"
// If the return value is other than UPB_CONTINUE, that is what the last
// callback returned.
extern upb_flow_t upb_fastdecode(const char **p, const char *end,
upb_value_handler_t *value_cb, void *closure,
void *table, int table_size);
/* Pure Decoding **************************************************************/ /* Pure Decoding **************************************************************/
// The key fast-path varint-decoding routine. Here we can assume we have at // The key fast-path varint-decoding routine. Here we can assume we have at
@ -329,6 +335,12 @@ void upb_decoder_run(upb_src *src, upb_status *status) {
CHECK_FLOW(upb_pop(d)); CHECK_FLOW(upb_pop(d));
} }
// Decodes as many fields as possible, updating d->ptr appropriately,
// before falling through to the slow(er) path.
//CHECK_FLOW(upb_fastdecode(&d->ptr, d->end,
// d->dispatcher->top->handlers.set->value,
// d->top->handlers.closure));
// Parse/handle tag. // Parse/handle tag.
upb_tag tag; upb_tag tag;
if (!upb_decode_tag(d, &tag)) { if (!upb_decode_tag(d, &tag)) {
@ -373,15 +385,19 @@ void upb_decoder_run(upb_src *src, upb_status *status) {
} }
// Look up field by tag number. // Look up field by tag number.
upb_fielddef *f = upb_msgdef_itof(d->top->msgdef, tag.field_number); upb_itof_ent *e = upb_msgdef_itofent(d->top->msgdef, tag.field_number);
if (!f) { if (!e) {
if (tag.wire_type == UPB_WIRE_TYPE_DELIMITED) if (tag.wire_type == UPB_WIRE_TYPE_DELIMITED)
CHECK(upb_decode_string(d, &val, &d->tmp)); CHECK(upb_decode_string(d, &val, &d->tmp));
CHECK_FLOW(upb_dispatch_unknownval(&d->dispatcher, tag.field_number, val)); CHECK_FLOW(upb_dispatch_unknownval(&d->dispatcher, tag.field_number, val));
continue; continue;
} else if (!upb_check_type(tag.wire_type, f->type)) { }
// TODO: put more details in this error msg.
upb_fielddef *f = e->f;
if (tag.wire_type != e->native_wire_type) {
// TODO: Support packed fields.
upb_seterr(status, UPB_ERROR, "Field had incorrect type, name: " UPB_STRFMT upb_seterr(status, UPB_ERROR, "Field had incorrect type, name: " UPB_STRFMT
", field type: %d, expected wire type %d, actual wire type: %d", ", field type: %d, expected wire type %d, actual wire type: %d",
UPB_STRARG(f->name), f->type, upb_types[f->type].native_wire_type, UPB_STRARG(f->name), f->type, upb_types[f->type].native_wire_type,
@ -398,10 +414,10 @@ void upb_decoder_run(upb_src *src, upb_status *status) {
// that the top 32 bits all match the highest bit of the low 32 bits. // that the top 32 bits all match the highest bit of the low 32 bits.
// If this is not true we are losing data. But the main protobuf library // If this is not true we are losing data. But the main protobuf library
// doesn't check this, and it would slow us down, so pass for now. // doesn't check this, and it would slow us down, so pass for now.
switch (f->type) { switch (e->field_type) {
case UPB_TYPE(MESSAGE): case UPB_TYPE(MESSAGE):
case UPB_TYPE(GROUP): case UPB_TYPE(GROUP):
CHECK_FLOW(upb_push(d, f, val, f->type)); CHECK_FLOW(upb_push(d, f, val, e->field_type));
continue; // We have no value to dispatch. continue; // We have no value to dispatch.
case UPB_TYPE(STRING): case UPB_TYPE(STRING):
case UPB_TYPE(BYTES): case UPB_TYPE(BYTES):

@ -12,11 +12,6 @@
#define alignof(t) offsetof(struct { char c; t x; }, x) #define alignof(t) offsetof(struct { char c; t x; }, x)
/* Rounds p up to the next multiple of t. */
static size_t upb_align_up(size_t val, size_t align) {
return val % align == 0 ? val : val + align - (val % align);
}
static int upb_div_round_up(int numerator, int denominator) { static int upb_div_round_up(int numerator, int denominator) {
/* cf. http://stackoverflow.com/questions/17944/how-to-round-up-the-result-of-integer-division */ /* cf. http://stackoverflow.com/questions/17944/how-to-round-up-the-result-of-integer-division */
return numerator > 0 ? (numerator - 1) / denominator + 1 : 0; return numerator > 0 ? (numerator - 1) / denominator + 1 : 0;
@ -491,10 +486,10 @@ static upb_flow_t upb_enumdef_EnumValueDescriptorProto_endmsg(void *_b) {
return UPB_BREAK; return UPB_BREAK;
} }
upb_ntoi_ent ntoi_ent = {{b->name, 0}, b->number}; upb_ntoi_ent ntoi_ent = {{b->name, 0}, b->number};
upb_iton_ent iton_ent = {{b->number, 0}, b->name}; upb_iton_ent iton_ent = {0, b->name};
upb_enumdef *e = upb_downcast_enumdef(upb_defbuilder_last(b)); upb_enumdef *e = upb_downcast_enumdef(upb_defbuilder_last(b));
upb_strtable_insert(&e->ntoi, &ntoi_ent.e); upb_strtable_insert(&e->ntoi, &ntoi_ent.e);
upb_inttable_insert(&e->iton, &iton_ent.e); upb_inttable_insert(&e->iton, b->number, &iton_ent);
// We don't unref "name" because we pass our ref to the iton entry of the // We don't unref "name" because we pass our ref to the iton entry of the
// table. strtables can ref their keys, but the inttable doesn't know that // table. strtables can ref their keys, but the inttable doesn't know that
// the value is a string. // the value is a string.
@ -579,8 +574,7 @@ upb_enum_iter upb_enum_begin(upb_enumdef *e) {
} }
upb_enum_iter upb_enum_next(upb_enumdef *e, upb_enum_iter iter) { upb_enum_iter upb_enum_next(upb_enumdef *e, upb_enum_iter iter) {
assert(iter); return upb_inttable_next(&e->iton, iter);
return upb_inttable_next(&e->iton, &iter->e);
} }
upb_string *upb_enumdef_iton(upb_enumdef *def, upb_enumval_t num) { upb_string *upb_enumdef_iton(upb_enumdef *def, upb_enumval_t num) {
@ -621,9 +615,9 @@ static upb_flow_t upb_fielddef_endmsg(void *_b) {
// Field was successfully read, add it as a field of the msgdef. // Field was successfully read, add it as a field of the msgdef.
upb_msgdef *m = upb_defbuilder_top(b); upb_msgdef *m = upb_defbuilder_top(b);
upb_itof_ent itof_ent = {{f->number, 0}, f}; upb_itof_ent itof_ent = {0, upb_types[f->type].native_wire_type, f->type, f};
upb_ntof_ent ntof_ent = {{f->name, 0}, f}; upb_ntof_ent ntof_ent = {{f->name, 0}, f};
upb_inttable_insert(&m->itof, &itof_ent.e); upb_inttable_insert(&m->itof, f->number, &itof_ent);
upb_strtable_insert(&m->ntof, &ntof_ent.e); upb_strtable_insert(&m->ntof, &ntof_ent.e);
return UPB_CONTINUE; return UPB_CONTINUE;
} }
@ -702,6 +696,7 @@ static upb_flow_t upb_msgdef_endmsg(void *_b) {
return UPB_BREAK; return UPB_BREAK;
} }
upb_inttable_compact(&m->itof);
// Create an ordering over the fields. // Create an ordering over the fields.
upb_field_count_t n = upb_msgdef_numfields(m); upb_field_count_t n = upb_msgdef_numfields(m);
upb_fielddef **sorted_fields = malloc(sizeof(upb_fielddef*) * n); upb_fielddef **sorted_fields = malloc(sizeof(upb_fielddef*) * n);
@ -830,7 +825,7 @@ upb_msg_iter upb_msg_begin(upb_msgdef *m) {
} }
upb_msg_iter upb_msg_next(upb_msgdef *m, upb_msg_iter iter) { upb_msg_iter upb_msg_next(upb_msgdef *m, upb_msg_iter iter) {
return upb_inttable_next(&m->itof, &iter->e); return upb_inttable_next(&m->itof, iter);
} }
/* upb_symtab adding defs *****************************************************/ /* upb_symtab adding defs *****************************************************/

@ -162,7 +162,9 @@ typedef struct _upb_msgdef {
// Hash table entries for looking up fields by name or number. // Hash table entries for looking up fields by name or number.
typedef struct { typedef struct {
upb_inttable_entry e; bool junk;
upb_wire_type_t native_wire_type;
upb_fieldtype_t field_type;
upb_fielddef *f; upb_fielddef *f;
} upb_itof_ent; } upb_itof_ent;
typedef struct { typedef struct {
@ -173,9 +175,13 @@ typedef struct {
// Looks up a field by name or number. While these are written to be as fast // Looks up a field by name or number. While these are written to be as fast
// as possible, it will still be faster to cache the results of this lookup if // as possible, it will still be faster to cache the results of this lookup if
// possible. These return NULL if no such field is found. // possible. These return NULL if no such field is found.
INLINE upb_itof_ent *upb_msgdef_itofent(upb_msgdef *m, uint32_t num) {
return (upb_itof_ent*)upb_inttable_fastlookup(
&m->itof, num, sizeof(upb_itof_ent));
}
INLINE upb_fielddef *upb_msgdef_itof(upb_msgdef *m, uint32_t num) { INLINE upb_fielddef *upb_msgdef_itof(upb_msgdef *m, uint32_t num) {
upb_itof_ent *e = upb_itof_ent *e = upb_msgdef_itofent(m, num);
(upb_itof_ent*)upb_inttable_fastlookup(&m->itof, num, sizeof(*e));
return e ? e->f : NULL; return e ? e->f : NULL;
} }
@ -194,14 +200,15 @@ INLINE upb_field_count_t upb_msgdef_numfields(upb_msgdef *m) {
// upb_fielddef *f = upb_msg_iter_field(i); // upb_fielddef *f = upb_msg_iter_field(i);
// // ... // // ...
// } // }
typedef upb_itof_ent *upb_msg_iter; typedef upb_inttable_iter upb_msg_iter;
upb_msg_iter upb_msg_begin(upb_msgdef *m); upb_msg_iter upb_msg_begin(upb_msgdef *m);
upb_msg_iter upb_msg_next(upb_msgdef *m, upb_msg_iter iter); upb_msg_iter upb_msg_next(upb_msgdef *m, upb_msg_iter iter);
INLINE bool upb_msg_done(upb_msg_iter iter) { return iter == NULL; } INLINE bool upb_msg_done(upb_msg_iter iter) { return upb_inttable_done(iter); }
INLINE upb_fielddef *upb_msg_iter_field(upb_msg_iter iter) { INLINE upb_fielddef *upb_msg_iter_field(upb_msg_iter iter) {
return iter->f; upb_itof_ent *ent = (upb_itof_ent*)upb_inttable_iter_value(iter);
return ent->f;
} }
/* upb_enumdef ****************************************************************/ /* upb_enumdef ****************************************************************/
@ -218,7 +225,7 @@ typedef struct {
} upb_ntoi_ent; } upb_ntoi_ent;
typedef struct { typedef struct {
upb_inttable_entry e; bool junk;
upb_string *string; upb_string *string;
} upb_iton_ent; } upb_iton_ent;
@ -234,17 +241,18 @@ upb_string *upb_enumdef_iton(upb_enumdef *e, upb_enumval_t num);
// for(i = upb_enum_begin(e); !upb_enum_done(i); i = upb_enum_next(e, i)) { // for(i = upb_enum_begin(e); !upb_enum_done(i); i = upb_enum_next(e, i)) {
// // ... // // ...
// } // }
typedef upb_iton_ent *upb_enum_iter; typedef upb_inttable_iter upb_enum_iter;
upb_enum_iter upb_enum_begin(upb_enumdef *e); upb_enum_iter upb_enum_begin(upb_enumdef *e);
upb_enum_iter upb_enum_next(upb_enumdef *e, upb_enum_iter iter); upb_enum_iter upb_enum_next(upb_enumdef *e, upb_enum_iter iter);
INLINE bool upb_enum_done(upb_enum_iter iter) { return iter == NULL; } INLINE bool upb_enum_done(upb_enum_iter iter) { return upb_inttable_done(iter); }
INLINE upb_string *upb_enum_iter_name(upb_enum_iter iter) { INLINE upb_string *upb_enum_iter_name(upb_enum_iter iter) {
return iter->string; upb_iton_ent *e = (upb_iton_ent*)upb_inttable_iter_value(iter);
return e->string;
} }
INLINE int32_t upb_enum_iter_number(upb_enum_iter iter) { INLINE int32_t upb_enum_iter_number(upb_enum_iter iter) {
return iter->e.key; return upb_inttable_iter_key(iter);
} }

@ -1,6 +1,8 @@
/* /*
* upb - a minimalist implementation of protocol buffers. * upb - a minimalist implementation of protocol buffers.
* *
* There are a few printf's strewn throughout this file, uncommenting them
* can be useful for debugging.
* Copyright (c) 2009 Joshua Haberman. See LICENSE for details. * Copyright (c) 2009 Joshua Haberman. See LICENSE for details.
*/ */
@ -13,140 +15,294 @@
static const double MAX_LOAD = 0.85; static const double MAX_LOAD = 0.85;
// The minimum percentage of an array part that we will allow. This is a
// speed/memory-usage tradeoff (though it's not straightforward because of
// cache effects). The lower this is, the more memory we'll use.
static const double MIN_DENSITY = 0.1;
static uint32_t MurmurHash2(const void *key, size_t len, uint32_t seed); static uint32_t MurmurHash2(const void *key, size_t len, uint32_t seed);
/* We use 1-based indexes into the table so that 0 can be "NULL". */ /* Base table (shared code) ***************************************************/
static upb_inttable_entry *intent(upb_inttable *t, int32_t i) {
return UPB_INDEX(t->t.entries, i-1, t->t.entry_size);
}
static upb_strtable_entry *strent(upb_strtable *t, int32_t i) {
return UPB_INDEX(t->t.entries, i-1, t->t.entry_size);
}
void upb_table_init(upb_table *t, uint32_t size, uint16_t entry_size) static uint32_t upb_table_size(upb_table *t) { return 1 << t->size_lg2; }
{ static size_t upb_table_entrysize(upb_table *t) { return t->entry_size; }
static size_t upb_table_valuesize(upb_table *t) { return t->value_size; }
void upb_table_init(upb_table *t, uint32_t size, uint16_t entry_size) {
t->count = 0; t->count = 0;
t->entry_size = entry_size; t->entry_size = entry_size;
t->size_lg2 = 0; t->size_lg2 = 1;
while(size >>= 1) t->size_lg2++; while(upb_table_size(t) < size) t->size_lg2++;
size_t bytes = upb_table_size(t) * t->entry_size; size_t bytes = upb_table_size(t) * t->entry_size;
t->mask = upb_table_size(t) - 1; t->mask = upb_table_size(t) - 1;
t->entries = malloc(bytes); t->entries = malloc(bytes);
memset(t->entries, 0, bytes); /* Both tables consider 0's an empty entry. */
} }
void upb_inttable_init(upb_inttable *t, uint32_t size, uint16_t entsize) void upb_table_free(upb_table *t) { free(t->entries); }
{
upb_table_init(&t->t, size, entsize); /* upb_inttable ***************************************************************/
static upb_inttable_entry *intent(upb_inttable *t, int32_t i) {
//printf("looking up int entry %d, size of entry: %d\n", i, t->t.entry_size);
return UPB_INDEX(t->t.entries, i, t->t.entry_size);
} }
void upb_strtable_init(upb_strtable *t, uint32_t size, uint16_t entsize) static uint32_t upb_inttable_hashtablesize(upb_inttable *t) {
{ return upb_table_size(&t->t);
upb_table_init(&t->t, size, entsize);
} }
void upb_table_free(upb_table *t) { free(t->entries); } void upb_inttable_sizedinit(upb_inttable *t, uint32_t arrsize, uint32_t hashsize,
void upb_inttable_free(upb_inttable *t) { upb_table_free(&t->t); } uint16_t value_size) {
void upb_strtable_free(upb_strtable *t) { size_t entsize = _upb_inttable_entrysize(value_size);
// Free refs from the strtable. upb_table_init(&t->t, hashsize, entsize);
upb_strtable_entry *e = upb_strtable_begin(t); for (uint32_t i = 0; i < upb_table_size(&t->t); i++) {
for(; e; e = upb_strtable_next(t, e)) { upb_inttable_entry *e = intent(t, i);
upb_string_unref(e->key); e->hdr.key = 0;
e->hdr.next = UPB_END_OF_CHAIN;
e->val.has_entry = 0;
}
t->t.value_size = value_size;
// Always make the array part at least 1 long, so that we know key 0
// won't be in the hash part (which lets us speed up that code path).
t->array_size = UPB_MAX(1, arrsize);
t->array = malloc(upb_table_valuesize(&t->t) * t->array_size);
t->array_count = 0;
for (uint32_t i = 0; i < t->array_size; i++) {
upb_inttable_value *val = UPB_INDEX(t->array, i, upb_table_valuesize(&t->t));
val->has_entry = false;
} }
upb_table_free(&t->t);
} }
static uint32_t strtable_bucket(upb_strtable *t, upb_string *key) void upb_inttable_init(upb_inttable *t, uint32_t hashsize, uint16_t value_size) {
{ upb_inttable_sizedinit(t, 0, hashsize, value_size);
uint32_t hash = MurmurHash2(upb_string_getrobuf(key), upb_string_len(key), 0);
return (hash & (upb_strtable_size(t)-1)) + 1;
} }
void *upb_strtable_lookup(upb_strtable *t, upb_string *key) void upb_inttable_free(upb_inttable *t) {
{ upb_table_free(&t->t);
uint32_t bucket = strtable_bucket(t, key); free(t->array);
upb_strtable_entry *e;
do {
e = strent(t, bucket);
if(e->key && upb_streql(e->key, key)) return e;
} while((bucket = e->next) != UPB_END_OF_CHAIN);
return NULL;
} }
static uint32_t empty_intbucket(upb_inttable *table) static uint32_t empty_intbucket(upb_inttable *table)
{ {
/* TODO: does it matter that this is biased towards the front of the table? */ // TODO: does it matter that this is biased towards the front of the table?
for(uint32_t i = 1; i <= upb_inttable_size(table); i++) { for(uint32_t i = 0; i < upb_inttable_hashtablesize(table); i++) {
upb_inttable_entry *e = intent(table, i); upb_inttable_entry *e = intent(table, i);
if(!e->has_entry) return i; if(!e->val.has_entry) return i;
} }
assert(false); assert(false);
return 0; return 0;
} }
/* The insert routines have a lot more code duplication between int/string // The insert routines have a lot more code duplication between int/string
* variants than I would like, but there's just a bit too much that varies to // variants than I would like, but there's just a bit too much that varies to
* parameterize them. */ // parameterize them.
static void intinsert(upb_inttable *t, upb_inttable_entry *e) static void intinsert(upb_inttable *t, upb_inttable_key_t key, void *val) {
{ assert(upb_inttable_lookup(t, key) == NULL);
assert(upb_inttable_lookup(t, e->key) == NULL); upb_inttable_value *table_val;
t->t.count++; if (_upb_inttable_isarrkey(t, key)) {
uint32_t bucket = upb_inttable_bucket(t, e->key); table_val = UPB_INDEX(t->array, key, upb_table_valuesize(&t->t));
upb_inttable_entry *table_e = intent(t, bucket); //printf("Inserting key %d to Array part! %p\n", key, table_val);
if(table_e->has_entry) { /* Collision. */ } else {
if(bucket == upb_inttable_bucket(t, table_e->key)) { t->t.count++;
/* Existing element is in its main posisiton. Find an empty slot to uint32_t bucket = _upb_inttable_bucket(t, key);
* place our new element and append it to this key's chain. */ upb_inttable_entry *table_e = intent(t, bucket);
uint32_t empty_bucket = empty_intbucket(t); //printf("Hash part! Inserting into bucket %d?\n", bucket);
while (table_e->next != UPB_END_OF_CHAIN) if(table_e->val.has_entry) { /* Collision. */
table_e = intent(t, table_e->next); //printf("Collision!\n");
table_e->next = empty_bucket; if(bucket == _upb_inttable_bucket(t, table_e->hdr.key)) {
table_e = intent(t, empty_bucket); /* Existing element is in its main posisiton. Find an empty slot to
} else { * place our new element and append it to this key's chain. */
/* Existing element is not in its main position. Move it to an empty uint32_t empty_bucket = empty_intbucket(t);
* slot and put our element in its main position. */ while (table_e->hdr.next != UPB_END_OF_CHAIN)
uint32_t empty_bucket = empty_intbucket(t); table_e = intent(t, table_e->hdr.next);
uint32_t evictee_bucket = upb_inttable_bucket(t, table_e->key); table_e->hdr.next = empty_bucket;
memcpy(intent(t, empty_bucket), table_e, t->t.entry_size); /* copies next */ table_e = intent(t, empty_bucket);
upb_inttable_entry *evictee_e = intent(t, evictee_bucket); } else {
while(1) { /* Existing element is not in its main position. Move it to an empty
assert(evictee_e->has_entry); * slot and put our element in its main position. */
assert(evictee_e->next != UPB_END_OF_CHAIN); uint32_t empty_bucket = empty_intbucket(t);
if(evictee_e->next == bucket) { uint32_t evictee_bucket = _upb_inttable_bucket(t, table_e->hdr.key);
evictee_e->next = empty_bucket; memcpy(intent(t, empty_bucket), table_e, t->t.entry_size); /* copies next */
break; upb_inttable_entry *evictee_e = intent(t, evictee_bucket);
while(1) {
assert(evictee_e->val.has_entry);
assert(evictee_e->hdr.next != UPB_END_OF_CHAIN);
if(evictee_e->hdr.next == bucket) {
evictee_e->hdr.next = empty_bucket;
break;
}
evictee_e = intent(t, evictee_e->hdr.next);
} }
evictee_e = intent(t, evictee_e->next); /* table_e remains set to our mainpos. */
} }
/* table_e remains set to our mainpos. */
} }
//printf("Inserting! to:%p, copying to: %p\n", table_e, &table_e->val);
table_val = &table_e->val;
table_e->hdr.key = key;
table_e->hdr.next = UPB_END_OF_CHAIN;
} }
memcpy(table_e, e, t->t.entry_size); memcpy(table_val, val, upb_table_valuesize(&t->t));
table_e->next = UPB_END_OF_CHAIN; table_val->has_entry = true;
table_e->has_entry = true; assert(upb_inttable_lookup(t, key) == table_val);
assert(upb_inttable_lookup(t, e->key) == table_e);
} }
void upb_inttable_insert(upb_inttable *t, upb_inttable_entry *e) // Insert all elements from src into dest. Caller ensures that a resize will
{ // not be necessary.
if((double)(t->t.count + 1) / upb_inttable_size(t) > MAX_LOAD) { static void upb_inttable_insertall(upb_inttable *dst, upb_inttable *src) {
/* Need to resize. New table of double the size, add old elements to it. */ for(upb_inttable_iter i = upb_inttable_begin(src); !upb_inttable_done(i);
i = upb_inttable_next(src, i)) {
//printf("load check: %d %d\n", upb_inttable_count(dst), upb_inttable_hashtablesize(dst));
assert((double)(upb_inttable_count(dst)) /
upb_inttable_hashtablesize(dst) <= MAX_LOAD);
intinsert(dst, upb_inttable_iter_key(i), upb_inttable_iter_value(i));
}
}
void upb_inttable_insert(upb_inttable *t, upb_inttable_key_t key, void *val) {
if((double)(t->t.count + 1) / upb_inttable_hashtablesize(t) > MAX_LOAD) {
//printf("RESIZE!\n");
// Need to resize. Allocate new table with double the size of however many
// elements we have now, add old elements to it. We create the new hash
// table without an array part, even if the old table had an array part.
// If/when the user calls upb_inttable_compact() again, we'll create an
// array part then.
upb_inttable new_table; upb_inttable new_table;
upb_inttable_init(&new_table, upb_inttable_size(t)*2, t->t.entry_size); //printf("Old table count=%d, size=%d\n", upb_inttable_count(t), upb_inttable_hashtablesize(t));
new_table.t.count = t->t.count; upb_inttable_init(&new_table, upb_inttable_count(t)*2, upb_table_valuesize(&t->t));
upb_inttable_entry *old_e; upb_inttable_insertall(&new_table, t);
for(old_e = upb_inttable_begin(t); old_e; old_e = upb_inttable_next(t, old_e))
intinsert(&new_table, old_e);
upb_inttable_free(t); upb_inttable_free(t);
*t = new_table; *t = new_table;
} }
intinsert(t, e); intinsert(t, key, val);
}
void upb_inttable_compact(upb_inttable *t) {
// Find the largest array part we can that satisfies the MIN_DENSITY
// definition. For now we just count down powers of two.
upb_inttable_key_t largest_key = 0;
for(upb_inttable_iter i = upb_inttable_begin(t); !upb_inttable_done(i);
i = upb_inttable_next(t, i)) {
largest_key = UPB_MAX(largest_key, upb_inttable_iter_key(i));
}
int lg2_array = 0;
while ((1UL << lg2_array) < largest_key) ++lg2_array;
++lg2_array; // Undo the first iteration.
size_t array_size;
int array_count;
while (lg2_array > 0) {
array_size = (1 << --lg2_array);
//printf("Considering size %d (btw, our table has %d things total)\n", array_size, upb_inttable_count(t));
if ((double)upb_inttable_count(t) / array_size < MIN_DENSITY) {
// Even if 100% of the keys were in the array pary, an array of this
// size would not be dense enough.
continue;
}
array_count = 0;
for(upb_inttable_iter i = upb_inttable_begin(t); !upb_inttable_done(i);
i = upb_inttable_next(t, i)) {
if (upb_inttable_iter_key(i) < array_size)
array_count++;
}
//printf("There would be %d things in that array\n", array_count);
if ((double)array_count / array_size >= MIN_DENSITY) break;
}
upb_inttable new_table;
int hash_size = (upb_inttable_count(t) - array_count + 1) / MAX_LOAD;
upb_inttable_sizedinit(&new_table, array_size, hash_size,
upb_table_valuesize(&t->t));
//printf("For %d things, using array size=%d, hash_size = %d\n", upb_inttable_count(t), array_size, hash_size);
upb_inttable_insertall(&new_table, t);
upb_inttable_free(t);
*t = new_table;
}
upb_inttable_iter upb_inttable_begin(upb_inttable *t) {
upb_inttable_iter iter = {-1, NULL, true}; // -1 will overflow to 0 on the first iteration.
return upb_inttable_next(t, iter);
}
upb_inttable_iter upb_inttable_next(upb_inttable *t, upb_inttable_iter iter) {
const size_t hdrsize = sizeof(upb_inttable_header);
const size_t entsize = upb_table_entrysize(&t->t);
if (iter.array_part) {
while (++iter.key < t->array_size) {
//printf("considering value %d\n", iter.key);
iter.value = UPB_INDEX(t->array, iter.key, t->t.value_size);
if (iter.value->has_entry) return iter;
}
//printf("Done with array part!\n");
iter.array_part = false;
// Point to the value of the table[-1] entry.
iter.value = UPB_INDEX(intent(t, -1), 1, hdrsize);
}
void *end = intent(t, upb_inttable_hashtablesize(t));
// Point to the entry for the value that was previously in iter.
upb_inttable_entry *e = UPB_INDEX(iter.value, -1, hdrsize);
do {
e = UPB_INDEX(e, 1, entsize);
//printf("considering value %p (val: %p)\n", e, &e->val);
if(e == end) {
//printf("No values.\n");
iter.value = NULL;
return iter;
}
} while(!e->val.has_entry);
//printf("USING VALUE! %p\n", e);
iter.key = e->hdr.key;
iter.value = &e->val;
return iter;
}
/* upb_strtable ***************************************************************/
static upb_strtable_entry *strent(upb_strtable *t, int32_t i) {
return UPB_INDEX(t->t.entries, i, t->t.entry_size);
}
static uint32_t upb_strtable_size(upb_strtable *t) {
return upb_table_size(&t->t);
}
void upb_strtable_init(upb_strtable *t, uint32_t size, uint16_t entsize) {
upb_table_init(&t->t, size, entsize);
for (uint32_t i = 0; i < upb_table_size(&t->t); i++) {
upb_strtable_entry *e = strent(t, i);
e->key = NULL;
e->next = UPB_END_OF_CHAIN;
}
}
void upb_strtable_free(upb_strtable *t) {
// Free refs from the strtable.
upb_strtable_entry *e = upb_strtable_begin(t);
for(; e; e = upb_strtable_next(t, e)) {
upb_string_unref(e->key);
}
upb_table_free(&t->t);
}
static uint32_t strtable_bucket(upb_strtable *t, upb_string *key)
{
uint32_t hash = MurmurHash2(upb_string_getrobuf(key), upb_string_len(key), 0);
return (hash & t->t.mask);
}
void *upb_strtable_lookup(upb_strtable *t, upb_string *key)
{
uint32_t bucket = strtable_bucket(t, key);
upb_strtable_entry *e;
do {
e = strent(t, bucket);
if(e->key && upb_streql(e->key, key)) return e;
} while((bucket = e->next) != UPB_END_OF_CHAIN);
return NULL;
} }
static uint32_t empty_strbucket(upb_strtable *table) static uint32_t empty_strbucket(upb_strtable *table)
{ {
/* TODO: does it matter that this is biased towards the front of the table? */ // TODO: does it matter that this is biased towards the front of the table?
for(uint32_t i = 1; i <= upb_strtable_size(table); i++) { for(uint32_t i = 0; i < upb_strtable_size(table); i++) {
upb_strtable_entry *e = strent(table, i); upb_strtable_entry *e = strent(table, i);
if(!e->key) return i; if(!e->key) return i;
} }
@ -191,13 +347,16 @@ static void strinsert(upb_strtable *t, upb_strtable_entry *e)
} }
memcpy(table_e, e, t->t.entry_size); memcpy(table_e, e, t->t.entry_size);
table_e->next = UPB_END_OF_CHAIN; table_e->next = UPB_END_OF_CHAIN;
//printf("Looking up, string=" UPB_STRFMT "...\n", UPB_STRARG(e->key));
assert(upb_strtable_lookup(t, e->key) == table_e); assert(upb_strtable_lookup(t, e->key) == table_e);
//printf("Yay!\n");
} }
void upb_strtable_insert(upb_strtable *t, upb_strtable_entry *e) void upb_strtable_insert(upb_strtable *t, upb_strtable_entry *e)
{ {
if((double)(t->t.count + 1) / upb_strtable_size(t) > MAX_LOAD) { if((double)(t->t.count + 1) / upb_strtable_size(t) > MAX_LOAD) {
/* Need to resize. New table of double the size, add old elements to it. */ // Need to resize. New table of double the size, add old elements to it.
//printf("RESIZE!!\n");
upb_strtable new_table; upb_strtable new_table;
upb_strtable_init(&new_table, upb_strtable_size(t)*2, t->t.entry_size); upb_strtable_init(&new_table, upb_strtable_size(t)*2, t->t.entry_size);
upb_strtable_entry *old_e; upb_strtable_entry *old_e;
@ -209,25 +368,12 @@ void upb_strtable_insert(upb_strtable *t, upb_strtable_entry *e)
strinsert(t, e); strinsert(t, e);
} }
void *upb_inttable_begin(upb_inttable *t) {
return upb_inttable_next(t, intent(t, 0));
}
void *upb_inttable_next(upb_inttable *t, upb_inttable_entry *cur) {
upb_inttable_entry *end = intent(t, upb_inttable_size(t)+1);
do {
cur = (void*)((char*)cur + t->t.entry_size);
if(cur == end) return NULL;
} while(!cur->has_entry);
return cur;
}
void *upb_strtable_begin(upb_strtable *t) { void *upb_strtable_begin(upb_strtable *t) {
return upb_strtable_next(t, strent(t, 0)); return upb_strtable_next(t, strent(t, -1));
} }
void *upb_strtable_next(upb_strtable *t, upb_strtable_entry *cur) { void *upb_strtable_next(upb_strtable *t, upb_strtable_entry *cur) {
upb_strtable_entry *end = strent(t, upb_strtable_size(t)+1); upb_strtable_entry *end = strent(t, upb_strtable_size(t));
do { do {
cur = (void*)((char*)cur + t->t.entry_size); cur = (void*)((char*)cur + t->t.entry_size);
if(cur == end) return NULL; if(cur == end) return NULL;

@ -25,12 +25,21 @@ extern "C" {
typedef uint32_t upb_inttable_key_t; typedef uint32_t upb_inttable_key_t;
#define UPB_END_OF_CHAIN (uint32_t)0 #define UPB_END_OF_CHAIN (uint32_t)-1
typedef struct { typedef struct {
upb_inttable_key_t key;
bool has_entry:1; bool has_entry:1;
uint32_t next:31; // Internal chaining. // The rest of the bits are the user's.
} upb_inttable_value;
typedef struct {
upb_inttable_key_t key;
uint32_t next; // Internal chaining.
} upb_inttable_header;
typedef struct {
upb_inttable_header hdr;
upb_inttable_value val;
} upb_inttable_entry; } upb_inttable_entry;
// TODO: consider storing the hash in the entry. This would avoid the need to // TODO: consider storing the hash in the entry. This would avoid the need to
@ -43,11 +52,12 @@ typedef struct {
} upb_strtable_entry; } upb_strtable_entry;
typedef struct { typedef struct {
void *entries; void *entries; // Hash table.
uint32_t count; /* How many elements are currently in the table? */ uint32_t count; // Number of entries in the hash part.
uint16_t entry_size; /* How big is each entry? */ uint32_t mask; // Mask to turn hash value -> bucket.
uint8_t size_lg2; /* The table is 2^size_lg2 in size. */ uint16_t entry_size; // Size of each entry.
uint32_t mask; uint16_t value_size; // Size of each value.
uint8_t size_lg2; // Size of the hash table part is 2^size_lg2 entries.
} upb_table; } upb_table;
typedef struct { typedef struct {
@ -56,83 +66,121 @@ typedef struct {
typedef struct { typedef struct {
upb_table t; upb_table t;
void *array; // Array part of the table.
uint32_t array_size; // Array part size.
uint32_t array_count; // Array part number of elements.
} upb_inttable; } upb_inttable;
/* Initialize and free a table, respectively. Specify the initial size // Initialize and free a table, respectively. Specify the initial size
* with 'size' (the size will be increased as necessary). Entry size // with 'size' (the size will be increased as necessary). Value size
* specifies how many bytes each entry in the table is. */ // specifies how many bytes each value in the table is.
void upb_inttable_init(upb_inttable *table, uint32_t size, uint16_t entry_size); //
// WARNING! The lowest bit of every entry is reserved by the hash table.
// It will always be overwritten when you insert, and must not be modified
// when looked up!
void upb_inttable_init(upb_inttable *table, uint32_t size, uint16_t value_size);
void upb_inttable_free(upb_inttable *table); void upb_inttable_free(upb_inttable *table);
void upb_strtable_init(upb_strtable *table, uint32_t size, uint16_t entry_size); void upb_strtable_init(upb_strtable *table, uint32_t size, uint16_t entry_size); // TODO: update
void upb_strtable_free(upb_strtable *table); void upb_strtable_free(upb_strtable *table);
INLINE uint32_t upb_table_size(upb_table *t) { return 1 << t->size_lg2; } // Number of values in the hash table.
INLINE uint32_t upb_inttable_size(upb_inttable *t) {
return upb_table_size(&t->t);
}
INLINE uint32_t upb_strtable_size(upb_strtable *t) {
return upb_table_size(&t->t);
}
INLINE uint32_t upb_table_count(upb_table *t) { return t->count; } INLINE uint32_t upb_table_count(upb_table *t) { return t->count; }
INLINE uint32_t upb_inttable_count(upb_inttable *t) { INLINE uint32_t upb_inttable_count(upb_inttable *t) {
return upb_table_count(&t->t); return t->array_count + upb_table_count(&t->t);
} }
INLINE uint32_t upb_strtable_count(upb_strtable *t) { INLINE uint32_t upb_strtable_count(upb_strtable *t) {
return upb_table_count(&t->t); return upb_table_count(&t->t);
} }
/* Inserts the given key into the hashtable with the given value. The key must // Inserts the given key into the hashtable with the given value. The key must
* not already exist in the hash table. The data will be copied from e into // not already exist in the hash table. The data will be copied from val into
* the hashtable (the amount of data copied comes from entry_size when the // the hashtable (the amount of data copied comes from value_size when the
* table was constructed). Therefore the data at val may be freed once the // table was constructed). Therefore the data at val may be freed once the
* call returns. */ // call returns. For string tables, the table takes a ref on str.
void upb_inttable_insert(upb_inttable *t, upb_inttable_entry *e); //
void upb_strtable_insert(upb_strtable *t, upb_strtable_entry *e); // WARNING: the lowest bit of val is reserved and will be overwritten!
void upb_inttable_insert(upb_inttable *t, upb_inttable_key_t key, void *val);
void upb_strtable_insert(upb_strtable *t, upb_strtable_entry *ent); // TODO: update
void upb_inttable_compact(upb_inttable *t);
INLINE uint32_t _upb_inttable_bucket(upb_inttable *t, upb_inttable_key_t k) {
uint32_t bucket = k & t->t.mask; // Identity hash for ints.
assert(bucket != UPB_END_OF_CHAIN);
return bucket;
}
INLINE uint32_t upb_inttable_bucket(upb_inttable *t, upb_inttable_key_t k) { // Returns true if this key belongs in the array part of the table.
return (k & t->t.mask) + 1; /* Identity hash for ints. */ INLINE bool _upb_inttable_isarrkey(upb_inttable *t, upb_inttable_key_t k) {
return (k < t->array_size);
} }
/* Looks up key in this table. Inlined because this is in the critical path of // Looks up key in this table, returning a pointer to the user's inserted data.
* decoding. We have the caller specify the entry_size because fixing this as // We have the caller specify the entry_size because fixing this as a literal
* a literal (instead of reading table->entry_size) gives the compiler more // (instead of reading table->entry_size) gives the compiler more ability to
* ability to optimize. */ // optimize.
INLINE void *upb_inttable_fastlookup(upb_inttable *t, uint32_t key, INLINE void *_upb_inttable_fastlookup(upb_inttable *t, uint32_t key,
uint32_t entry_size) { size_t entry_size, size_t value_size) {
uint32_t bucket = upb_inttable_bucket(t, key); upb_inttable_value *arrval =
(upb_inttable_value*)UPB_INDEX(t->array, key, value_size);
if (_upb_inttable_isarrkey(t, key)) {
//printf("array lookup for key %d, &val=%p, has_entry=%d\n", key, val, val->has_entry);
return (arrval->has_entry) ? arrval : NULL;
}
uint32_t bucket = _upb_inttable_bucket(t, key);
upb_inttable_entry *e = upb_inttable_entry *e =
(upb_inttable_entry*)UPB_INDEX(t->t.entries, bucket-1, entry_size); (upb_inttable_entry*)UPB_INDEX(t->t.entries, bucket, entry_size);
if (key == 0) { //printf("looking in first bucket %d, entry size=%zd, addr=%p\n", bucket, entry_size, e);
while (1) { while (1) {
if (e->key == 0 && e->has_entry) return e; //printf("%d, %d, %d\n", e->val.has_entry, e->hdr.key, key);
if ((bucket = e->next) == UPB_END_OF_CHAIN) return NULL; if (e->hdr.key == key) return &e->val;
e = (upb_inttable_entry*)UPB_INDEX(t->t.entries, bucket-1, entry_size); if ((bucket = e->hdr.next) == UPB_END_OF_CHAIN) return NULL;
} //printf("looking in bucket %d\n", bucket);
} else { e = (upb_inttable_entry*)UPB_INDEX(t->t.entries, bucket, entry_size);
while (1) {
if (e->key == key) return e;
if ((bucket = e->next) == UPB_END_OF_CHAIN) return NULL;
e = (upb_inttable_entry*)UPB_INDEX(t->t.entries, bucket-1, entry_size);
}
} }
} }
INLINE size_t _upb_inttable_entrysize(size_t value_size) {
return upb_align_up(sizeof(upb_inttable_header) + value_size, 8);
}
INLINE void *upb_inttable_fastlookup(upb_inttable *t, uint32_t key,
uint32_t value_size) {
return _upb_inttable_fastlookup(t, key, _upb_inttable_entrysize(value_size), value_size);
}
INLINE void *upb_inttable_lookup(upb_inttable *t, uint32_t key) { INLINE void *upb_inttable_lookup(upb_inttable *t, uint32_t key) {
return upb_inttable_fastlookup(t, key, t->t.entry_size); return _upb_inttable_fastlookup(t, key, t->t.entry_size, t->t.value_size);
} }
void *upb_strtable_lookup(upb_strtable *t, upb_string *key); void *upb_strtable_lookup(upb_strtable *t, upb_string *key);
/* Provides iteration over the table. The order in which the entries are // Provides iteration over the table. The order in which the entries are
* returned is undefined. Insertions invalidate iterators. The _next // returned is undefined. Insertions invalidate iterators.
* functions return NULL when the end has been reached. */
void *upb_inttable_begin(upb_inttable *t);
void *upb_inttable_next(upb_inttable *t, upb_inttable_entry *cur);
void *upb_strtable_begin(upb_strtable *t); void *upb_strtable_begin(upb_strtable *t);
void *upb_strtable_next(upb_strtable *t, upb_strtable_entry *cur); void *upb_strtable_next(upb_strtable *t, upb_strtable_entry *cur);
// Inttable iteration (should update strtable iteration to use this scheme too).
// The order is undefined.
// for(upb_inttable_iter i = upb_inttable_begin(t); !upb_inttable_done(i);
// i = upb_inttable_next(t, i)) {
// // ...
// }
typedef struct {
upb_inttable_key_t key;
upb_inttable_value *value;
bool array_part;
} upb_inttable_iter;
upb_inttable_iter upb_inttable_begin(upb_inttable *t);
upb_inttable_iter upb_inttable_next(upb_inttable *t, upb_inttable_iter iter);
INLINE bool upb_inttable_done(upb_inttable_iter iter) { return iter.value == NULL; }
INLINE upb_inttable_key_t upb_inttable_iter_key(upb_inttable_iter iter) {
return iter.key;
}
INLINE void *upb_inttable_iter_value(upb_inttable_iter iter) {
return iter.value;
}
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif

@ -19,7 +19,6 @@ using std::string;
using std::vector; using std::vector;
typedef struct { typedef struct {
upb_inttable_entry e;
uint32_t value; /* key*2 */ uint32_t value; /* key*2 */
} inttable_entry; } inttable_entry;
@ -60,6 +59,7 @@ void test_strtable(const vector<string>& keys, uint32_t num_to_insert)
const string& key = keys[i]; const string& key = keys[i];
upb_string *str = upb_strduplen(key.c_str(), key.size()); upb_string *str = upb_strduplen(key.c_str(), key.size());
strtable_entry *e = (strtable_entry*)upb_strtable_lookup(&table, str); strtable_entry *e = (strtable_entry*)upb_strtable_lookup(&table, str);
printf("Looking up " UPB_STRFMT "...\n", UPB_STRARG(str));
if(m.find(key) != m.end()) { /* Assume map implementation is correct. */ if(m.find(key) != m.end()) { /* Assume map implementation is correct. */
assert(e); assert(e);
assert(upb_streql(e->e.key, str)); assert(upb_streql(e->e.key, str));
@ -97,21 +97,36 @@ void test_inttable(int32_t *keys, uint16_t num_entries)
int32_t key = keys[i]; int32_t key = keys[i];
largest_key = UPB_MAX((int32_t)largest_key, key); largest_key = UPB_MAX((int32_t)largest_key, key);
inttable_entry e; inttable_entry e;
e.e.key = key; e.value = (key*2) << 1;
e.value = key*2; upb_inttable_insert(&table, key, &e);
upb_inttable_insert(&table, &e.e);
m[key] = key*2; m[key] = key*2;
hm[key] = key*2; hm[key] = key*2;
} }
/* Test correctness. */ /* Test correctness. */
for(uint32_t i = 1; i <= largest_key; i++) { for(uint32_t i = 0; i <= largest_key; i++) {
inttable_entry *e = (inttable_entry*)upb_inttable_lookup( inttable_entry *e = (inttable_entry*)upb_inttable_lookup(
&table, i); &table, i);
if(m.find(i) != m.end()) { /* Assume map implementation is correct. */ if(m.find(i) != m.end()) { /* Assume map implementation is correct. */
assert(e); assert(e);
assert(e->e.key == i); //printf("addr: %p, expected: %d, actual: %d\n", e, i*2, e->value);
assert(e->value == i*2); assert(((e->value) >> 1) == i*2);
assert(m[i] == i*2);
assert(hm[i] == i*2);
} else {
assert(e == NULL);
}
}
// Compact and test correctness again.
upb_inttable_compact(&table);
for(uint32_t i = 0; i <= largest_key; i++) {
inttable_entry *e = (inttable_entry*)upb_inttable_lookup(
&table, i);
if(m.find(i) != m.end()) { /* Assume map implementation is correct. */
assert(e);
//printf("addr: %p, expected: %d, actual: %d\n", e, i*2, e->value);
assert(((e->value) >> 1) == i*2);
assert(m[i] == i*2); assert(m[i] == i*2);
assert(hm[i] == i*2); assert(hm[i] == i*2);
} else { } else {

Loading…
Cancel
Save