diff --git a/benchmark/vm_ivar_memoize.yml b/benchmark/vm_ivar_memoize.yml new file mode 100644 index 0000000000..90f6b07f05 --- /dev/null +++ b/benchmark/vm_ivar_memoize.yml @@ -0,0 +1,85 @@ +prelude: | + IVARS = 60 + class Record + def initialize(offset = false) + @offset = 1 if offset + @first = 0 + IVARS.times do |i| + instance_variable_set("@ivar_#{i}", i) + end + end + + def first + @first + end + + def lazy_set + @lazy_set ||= 123 + end + + def undef + @undef + end + end + + Record.new # Need one alloc to right size + + BASE = Record.new + LAZY = Record.new + LAZY.lazy_set + + class Miss < Record + @first = 0 + IVARS.times do |i| + instance_variable_set("@i_#{i}", i) + end + end + + Miss.new # Need one alloc to right size + MISS = Miss.new + + DIVERGENT = Record.new(true) + +benchmark: + vm_ivar_stable_shape: | + BASE.first + BASE.first + BASE.first + BASE.first + BASE.first + BASE.first + vm_ivar_memoize_unstable_shape: | + BASE.first + LAZY.first + BASE.first + LAZY.first + BASE.first + LAZY.first + vm_ivar_memoize_unstable_shape_miss: | + BASE.first + MISS.first + BASE.first + MISS.first + BASE.first + MISS.first + vm_ivar_unstable_undef: | + BASE.undef + LAZY.undef + BASE.undef + LAZY.undef + BASE.undef + LAZY.undef + vm_ivar_divergent_shape: | + BASE.first + DIVERGENT.first + BASE.first + DIVERGENT.first + BASE.first + DIVERGENT.first + vm_ivar_divergent_shape_imbalanced: | + BASE.first + DIVERGENT.first + DIVERGENT.first + DIVERGENT.first + DIVERGENT.first + DIVERGENT.first diff --git a/shape.c b/shape.c index bc0951082f..8fae6fe7ab 100644 --- a/shape.c +++ b/shape.c @@ -38,6 +38,7 @@ #define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)x & SINGLE_CHILD_MASK) #define ANCESTOR_CACHE_THRESHOLD 10 #define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1) +#define ANCESTOR_SEARCH_MAX_DEPTH 2 static ID id_frozen; static ID id_t_object; @@ -723,6 +724,62 @@ rb_shape_transition_shape_capa(rb_shape_t* shape) return rb_shape_transition_shape_capa_create(shape, rb_malloc_grow_capa(shape->capacity, sizeof(VALUE))); } +// Same as rb_shape_get_iv_index, but uses a provided valid shape id and index +// to return a result faster if branches of the shape tree are closely related. +bool +rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint) +{ + attr_index_t index_hint = *value; + rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id); + rb_shape_t *initial_shape = shape; + + if (*shape_id_hint == INVALID_SHAPE_ID) { + *shape_id_hint = shape_id; + return rb_shape_get_iv_index(shape, id, value); + } + + rb_shape_t * shape_hint = rb_shape_get_shape_by_id(*shape_id_hint); + + // We assume it's likely shape_id_hint and shape_id have a close common + // ancestor, so we check up to ANCESTOR_SEARCH_MAX_DEPTH ancestors before + // eventually using the index, as in case of a match it will be faster. + // However if the shape doesn't have an index, we walk the entire tree. + int depth = INT_MAX; + if (shape->ancestor_index && shape->next_iv_index >= ANCESTOR_CACHE_THRESHOLD) { + depth = ANCESTOR_SEARCH_MAX_DEPTH; + } + + while (depth > 0 && shape->next_iv_index > index_hint) { + while (shape_hint->next_iv_index > shape->next_iv_index) { + shape_hint = rb_shape_get_parent(shape_hint); + } + + if (shape_hint == shape) { + // We've found a common ancestor so use the index hint + *value = index_hint; + *shape_id_hint = rb_shape_id(shape); + return true; + } + if (shape->edge_name == id) { + // We found the matching id before a common ancestor + *value = shape->next_iv_index - 1; + *shape_id_hint = rb_shape_id(shape); + return true; + } + + shape = rb_shape_get_parent(shape); + depth--; + } + + // If the original shape had an index but its ancestor doesn't + // we switch back to the original one as it will be faster. + if (!shape->ancestor_index && initial_shape->ancestor_index) { + shape = initial_shape; + } + *shape_id_hint = shape_id; + return rb_shape_get_iv_index(shape, id, value); +} + bool rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value) { diff --git a/shape.h b/shape.h index bcfd94b964..6404465d45 100644 --- a/shape.h +++ b/shape.h @@ -155,6 +155,7 @@ rb_shape_t* rb_shape_get_shape_by_id(shape_id_t shape_id); shape_id_t rb_shape_get_shape_id(VALUE obj); rb_shape_t * rb_shape_get_next_iv_shape(rb_shape_t * shape, ID id); bool rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t * value); +bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t * value, shape_id_t *shape_id_hint); bool rb_shape_obj_too_complex(VALUE obj); void rb_shape_set_shape(VALUE obj, rb_shape_t* shape); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index e28323e588..785dbfedec 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1286,8 +1286,6 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call } #endif - rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id); - if (shape_id == OBJ_TOO_COMPLEX_SHAPE_ID) { st_table *table = NULL; switch (BUILTIN_TYPE(obj)) { @@ -1314,14 +1312,22 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call } } else { - if (rb_shape_get_iv_index(shape, id, &index)) { + shape_id_t previous_cached_id = cached_id; + if (rb_shape_get_iv_index_with_hint(shape_id, id, &index, &cached_id)) { // This fills in the cache with the shared cache object. // "ent" is the shared cache object - fill_ivar_cache(iseq, ic, cc, is_attr, index, shape_id); + if (cached_id != previous_cached_id) { + fill_ivar_cache(iseq, ic, cc, is_attr, index, cached_id); + } - // We fetched the ivar list above - val = ivar_list[index]; - RUBY_ASSERT(!UNDEF_P(val)); + if (index == ATTR_INDEX_NOT_SET) { + val = default_value; + } + else { + // We fetched the ivar list above + val = ivar_list[index]; + RUBY_ASSERT(!UNDEF_P(val)); + } } else { if (is_attr) {