Skip to content

Commit 6133ca3

Browse files
committed
WIP: vm_getivar handle semi-stale
$ make benchmark ITEM=vm_ivar_memoize COMPARE_RUBY=$(which ruby) revision.h updated /usr/bin/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \ --executables="compare-ruby::/opt/rubies/head/bin/ruby -I.ext/common --disable-gem" \ --executables="built-ruby::./miniruby -I./lib -I. -I.ext/common ./tool/runruby.rb --extout=.ext -- --disable-gems --disable-gem" \ --output=markdown --output-compare -v $(find ./benchmark -maxdepth 1 -name 'vm_ivar_memoize' -o -name '*vm_ivar_memoize*.yml' -o -name '*vm_ivar_memoize*.rb' | sort) compare-ruby: ruby 3.3.0dev (2023-10-10T02:05:29Z shape-cache-miss-i.. 5245123) [arm64-darwin22] built-ruby: ruby 3.3.0dev (2023-10-10T09:03:22Z shape-cache-miss-i.. 6c426d75d8) [arm64-darwin22] | |compare-ruby|built-ruby| |:-------------------------------|-----------:|---------:| |vm_ivar_stable_shape | 12.412M| 12.409M| | | 1.00x| -| |vm_ivar_memoize_unstable_shape | 1.236M| 1.772M| | | -| 1.43x|
1 parent 5245123 commit 6133ca3

4 files changed

Lines changed: 141 additions & 20 deletions

File tree

‎benchmark/vm_ivar_memoize.yml‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
prelude: |
2+
class Record
3+
def initialize
4+
@first = 0
5+
68.times do |i|
6+
instance_variable_set("@ivar_#{i}", i)
7+
end
8+
end
9+
10+
def first
11+
@first
12+
end
13+
14+
def lazy_set
15+
@uninitialized ||= 123
16+
end
17+
18+
def lazy_set2
19+
@uninitialized2 ||= 123
20+
end
21+
end
22+
23+
Record.new # Need one alloc to right size
24+
25+
BASE = Record.new
26+
LAZY1 = Record.new
27+
LAZY1.lazy_set
28+
LAZY2 = Record.new
29+
LAZY2.lazy_set2
30+
31+
benchmark:
32+
vm_ivar_stable_shape: |
33+
BASE.first
34+
BASE.first
35+
BASE.first
36+
BASE.first
37+
BASE.first
38+
BASE.first
39+
vm_ivar_memoize_unstable_shape: |
40+
BASE.first
41+
LAZY1.first
42+
LAZY2.first
43+
BASE.first
44+
LAZY1.first
45+
LAZY2.first
46+
loop_count: 5_000_000

‎shape.c‎

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,28 @@ rb_shape_transition_shape_capa(rb_shape_t* shape, uint32_t new_capacity)
427427
return new_shape;
428428
}
429429

430+
static inline bool
431+
shape_get_iv_index(rb_shape_t * shape, attr_index_t *value)
432+
{
433+
enum shape_type shape_type;
434+
shape_type = (enum shape_type)shape->type;
435+
436+
switch (shape_type) {
437+
case SHAPE_IVAR:
438+
RUBY_ASSERT(shape->next_iv_index > 0);
439+
*value = shape->next_iv_index - 1;
440+
return true;
441+
case SHAPE_CAPACITY_CHANGE:
442+
case SHAPE_ROOT:
443+
case SHAPE_INITIAL_CAPACITY:
444+
case SHAPE_T_OBJECT:
445+
return false;
446+
case SHAPE_OBJ_TOO_COMPLEX:
447+
case SHAPE_FROZEN:
448+
rb_bug("Ivar should not exist on transition");
449+
}
450+
}
451+
430452
bool
431453
rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
432454
{
@@ -436,29 +458,43 @@ rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t *value)
436458

437459
while (shape->parent_id != INVALID_SHAPE_ID) {
438460
if (shape->edge_name == id) {
439-
enum shape_type shape_type;
440-
shape_type = (enum shape_type)shape->type;
441-
442-
switch (shape_type) {
443-
case SHAPE_IVAR:
444-
RUBY_ASSERT(shape->next_iv_index > 0);
445-
*value = shape->next_iv_index - 1;
446-
return true;
447-
case SHAPE_CAPACITY_CHANGE:
448-
case SHAPE_ROOT:
449-
case SHAPE_INITIAL_CAPACITY:
450-
case SHAPE_T_OBJECT:
451-
return false;
452-
case SHAPE_OBJ_TOO_COMPLEX:
453-
case SHAPE_FROZEN:
454-
rb_bug("Ivar should not exist on transition");
455-
}
461+
return shape_get_iv_index(shape, value);
456462
}
457463
shape = rb_shape_get_parent(shape);
458464
}
459465
return false;
460466
}
461467

468+
long
469+
rb_shape_get_iv_index_stale(shape_id_t new_shape_id, ID id, attr_index_t *value, shape_id_t old_shape_id)
470+
{
471+
// It doesn't make sense to ask for the index of an IV that's stored
472+
// on an object that is "too complex" as it uses a hash for storing IVs
473+
RUBY_ASSERT(new_shape_id != OBJ_TOO_COMPLEX_SHAPE_ID);
474+
RUBY_ASSERT(old_shape_id != OBJ_TOO_COMPLEX_SHAPE_ID);
475+
476+
rb_shape_t *shape = rb_shape_get_shape_by_id(new_shape_id);
477+
478+
// if (old_shape_id == INVALID_SHAPE_ID) {
479+
// return rb_shape_get_iv_index(shape, id, value) ? -1 : 0;
480+
// }
481+
482+
long depth = 1;
483+
while (shape->parent_id != INVALID_SHAPE_ID) {
484+
if (shape->parent_id == old_shape_id) {
485+
shape_get_iv_index(shape, value);
486+
return depth;
487+
}
488+
else if (shape->edge_name == id) {
489+
return shape_get_iv_index(shape, value) ? -1 : 0;
490+
}
491+
depth++;
492+
shape = rb_shape_get_parent(shape);
493+
}
494+
495+
return 0;
496+
}
497+
462498
void
463499
rb_shape_set_shape(VALUE obj, rb_shape_t* shape)
464500
{

‎shape.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ typedef uint16_t shape_id_t;
4040
# define SPECIAL_CONST_SHAPE_ID (SIZE_POOL_COUNT * 2)
4141
# define OBJ_TOO_COMPLEX_SHAPE_ID (SPECIAL_CONST_SHAPE_ID + 1)
4242

43+
# define SHAPE_CLOSE_DESCENDANT_MAX_DEPTH 4
44+
4345
struct rb_shape {
4446
struct rb_id_table * edges; // id_table from ID (ivar) to next shape
4547
ID edge_name; // ID (ivar) for transition from parent to rb_shape
@@ -145,6 +147,7 @@ rb_shape_t* rb_shape_get_shape_by_id(shape_id_t shape_id);
145147
shape_id_t rb_shape_get_shape_id(VALUE obj);
146148
rb_shape_t * rb_shape_get_next_iv_shape(rb_shape_t * shape, ID id);
147149
bool rb_shape_get_iv_index(rb_shape_t * shape, ID id, attr_index_t * value);
150+
long rb_shape_get_iv_index_stale(shape_id_t new_shape_id, ID id, attr_index_t *value, shape_id_t old_shape_id);
148151
bool rb_shape_obj_too_complex(VALUE obj);
149152

150153
void rb_shape_set_shape(VALUE obj, rb_shape_t* shape);

‎vm_insnhelper.c‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,14 +1286,14 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call
12861286
}
12871287
#endif
12881288

1289-
rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
1290-
12911289
if (shape_id == OBJ_TOO_COMPLEX_SHAPE_ID) {
12921290
if (!st_lookup(ROBJECT_IV_HASH(obj), id, &val)) {
12931291
val = default_value;
12941292
}
12951293
}
1296-
else {
1294+
else if (UNLIKELY(index == ATTR_INDEX_NOT_SET)) {
1295+
rb_shape_t *shape = rb_shape_get_shape_by_id(shape_id);
1296+
12971297
if (rb_shape_get_iv_index(shape, id, &index)) {
12981298
// This fills in the cache with the shared cache object.
12991299
// "ent" is the shared cache object
@@ -1314,6 +1314,42 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call
13141314
val = default_value;
13151315
}
13161316
}
1317+
else {
1318+
long ancestor_depth = rb_shape_get_iv_index_stale(shape_id, id, &index, cached_id);
1319+
switch (ancestor_depth) {
1320+
case 0: // Not found
1321+
if (is_attr) {
1322+
vm_cc_attr_index_initialize(cc, shape_id);
1323+
}
1324+
else {
1325+
vm_ic_attr_index_initialize(ic, shape_id);
1326+
}
1327+
1328+
val = default_value;
1329+
break;
1330+
case -1: // Found but wasn't an ancestor
1331+
// This fills in the cache with the shared cache object.
1332+
// "ent" is the shared cache object
1333+
fill_ivar_cache(iseq, ic, cc, is_attr, index, shape_id);
1334+
1335+
// We fetched the ivar list above
1336+
val = ivar_list[index];
1337+
RUBY_ASSERT(!UNDEF_P(val));
1338+
break;
1339+
default: // cached_id is an ancestor of shape_id so index is unchanged
1340+
if (ancestor_depth < SHAPE_CLOSE_DESCENDANT_MAX_DEPTH) {
1341+
// if shape_id is a close decendent of cached_id, we assume this
1342+
// object might be using the "memoization" pattern, so it will flip-flop
1343+
// a lot and we might as well keep the close ancestor in cache.
1344+
// TODO: is there a better heuristic? e.g. number of edges?
1345+
fill_ivar_cache(iseq, ic, cc, is_attr, index, shape_id);
1346+
}
1347+
1348+
val = ivar_list[index];
1349+
RUBY_ASSERT(!UNDEF_P(val));
1350+
break;
1351+
}
1352+
}
13171353

13181354
}
13191355

0 commit comments

Comments
 (0)