Lazily allocate id tables for children
This patch lazily allocates id tables for shape children. If a shape has only one single child, it tags the child with a bit. When we read children, if the id table has the bit set, we know it's a single child. If we need to add more children, then we create a new table and evacuate the child to the new table. Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com>
This commit is contained in:
parent
0519741702
commit
7c307e0379
Notes:
git
2023-03-22 19:51:11 +00:00
98
shape.c
98
shape.c
@ -14,6 +14,12 @@
|
|||||||
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
#define SHAPE_DEBUG (VM_CHECK_MODE > 0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SINGLE_CHILD_TAG 0x1
|
||||||
|
#define TAG_SINGLE_CHILD(x) (struct rb_id_table *)((uintptr_t)x | SINGLE_CHILD_TAG)
|
||||||
|
#define SINGLE_CHILD_MASK (~((uintptr_t)SINGLE_CHILD_TAG))
|
||||||
|
#define SINGLE_CHILD_P(x) (((uintptr_t)x) & SINGLE_CHILD_TAG)
|
||||||
|
#define SINGLE_CHILD(x) (rb_shape_t *)((uintptr_t)x & SINGLE_CHILD_MASK)
|
||||||
|
|
||||||
static ID id_frozen;
|
static ID id_frozen;
|
||||||
static ID id_t_object;
|
static ID id_t_object;
|
||||||
static ID size_pool_edge_names[SIZE_POOL_COUNT];
|
static ID size_pool_edge_names[SIZE_POOL_COUNT];
|
||||||
@ -148,6 +154,7 @@ rb_shape_alloc(ID edge_name, rb_shape_t * parent, enum shape_type type)
|
|||||||
shape->type = (uint8_t)type;
|
shape->type = (uint8_t)type;
|
||||||
shape->size_pool_index = parent->size_pool_index;
|
shape->size_pool_index = parent->size_pool_index;
|
||||||
shape->capacity = parent->capacity;
|
shape->capacity = parent->capacity;
|
||||||
|
shape->edges = 0;
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,25 +195,48 @@ get_next_shape_internal(rb_shape_t * shape, ID id, enum shape_type shape_type, b
|
|||||||
if (new_shape_necessary || (new_shapes_allowed && (shape->next_iv_index < SHAPE_MAX_NUM_IVS))) {
|
if (new_shape_necessary || (new_shapes_allowed && (shape->next_iv_index < SHAPE_MAX_NUM_IVS))) {
|
||||||
RB_VM_LOCK_ENTER();
|
RB_VM_LOCK_ENTER();
|
||||||
{
|
{
|
||||||
bool had_edges = !!shape->edges;
|
// If the current shape has children
|
||||||
|
if (shape->edges) {
|
||||||
|
// Check if it only has one child
|
||||||
|
if (SINGLE_CHILD_P(shape->edges)) {
|
||||||
|
rb_shape_t * child = SINGLE_CHILD(shape->edges);
|
||||||
|
// If the one child has a matching edge name, then great,
|
||||||
|
// we found what we want.
|
||||||
|
if (child->edge_name == id) {
|
||||||
|
res = child;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise we're going to have to create a new shape
|
||||||
|
// and insert it as a child node, so create an id
|
||||||
|
// table and insert the existing child
|
||||||
|
shape->edges = rb_id_table_create(2);
|
||||||
|
rb_id_table_insert(shape->edges, child->edge_name, (VALUE)child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If it has more than one child, do a hash lookup to find it.
|
||||||
|
VALUE lookup_result;
|
||||||
|
if (rb_id_table_lookup(shape->edges, id, &lookup_result)) {
|
||||||
|
res = (rb_shape_t *)lookup_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!shape->edges) {
|
// If the shape we were looking for above was found,
|
||||||
shape->edges = rb_id_table_create(0);
|
// then `res` will be set to the child. If it isn't set, then
|
||||||
}
|
// we know we need a new child shape, and that we must insert
|
||||||
|
// it in to the table.
|
||||||
// Lookup the shape in edges - if there's already an edge and a corresponding shape for it,
|
if (!res) {
|
||||||
// we can return that. Otherwise, we'll need to get a new shape
|
*variation_created = true;
|
||||||
VALUE lookup_result;
|
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
||||||
if (rb_id_table_lookup(shape->edges, id, &lookup_result)) {
|
rb_id_table_insert(shape->edges, id, (VALUE)new_shape);
|
||||||
res = (rb_shape_t *)lookup_result;
|
res = new_shape;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
*variation_created = had_edges;
|
// If the shape didn't have any outgoing edges, then create
|
||||||
|
// the new outgoing edge and tag the pointer.
|
||||||
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
rb_shape_t * new_shape = rb_shape_alloc_new_child(id, shape, shape_type);
|
||||||
|
shape->edges = TAG_SINGLE_CHILD(new_shape);
|
||||||
rb_id_table_insert(shape->edges, id, (VALUE)new_shape);
|
|
||||||
|
|
||||||
res = new_shape;
|
res = new_shape;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -458,11 +488,19 @@ rb_shape_traverse_from_new_root(rb_shape_t *initial_shape, rb_shape_t *dest_shap
|
|||||||
}
|
}
|
||||||
|
|
||||||
VALUE lookup_result;
|
VALUE lookup_result;
|
||||||
if (rb_id_table_lookup(next_shape->edges, dest_shape->edge_name, &lookup_result)) {
|
if (SINGLE_CHILD_P(next_shape->edges)) {
|
||||||
next_shape = (rb_shape_t *)lookup_result;
|
rb_shape_t * child = SINGLE_CHILD(next_shape->edges);
|
||||||
|
if (child->edge_name == dest_shape->edge_name) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return NULL;
|
if (rb_id_table_lookup(next_shape->edges, dest_shape->edge_name, &lookup_result)) {
|
||||||
|
next_shape = (rb_shape_t *)lookup_result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SHAPE_ROOT:
|
case SHAPE_ROOT:
|
||||||
@ -533,7 +571,12 @@ size_t
|
|||||||
rb_shape_edges_count(rb_shape_t *shape)
|
rb_shape_edges_count(rb_shape_t *shape)
|
||||||
{
|
{
|
||||||
if (shape->edges) {
|
if (shape->edges) {
|
||||||
return rb_id_table_size(shape->edges);
|
if (SINGLE_CHILD_P(shape->edges)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return rb_id_table_size(shape->edges);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -542,7 +585,7 @@ size_t
|
|||||||
rb_shape_memsize(rb_shape_t *shape)
|
rb_shape_memsize(rb_shape_t *shape)
|
||||||
{
|
{
|
||||||
size_t memsize = sizeof(rb_shape_t);
|
size_t memsize = sizeof(rb_shape_t);
|
||||||
if (shape->edges) {
|
if (shape->edges && !SINGLE_CHILD_P(shape->edges)) {
|
||||||
memsize += rb_id_table_memsize(shape->edges);
|
memsize += rb_id_table_memsize(shape->edges);
|
||||||
}
|
}
|
||||||
return memsize;
|
return memsize;
|
||||||
@ -613,7 +656,13 @@ rb_shape_edges(VALUE self)
|
|||||||
VALUE hash = rb_hash_new();
|
VALUE hash = rb_hash_new();
|
||||||
|
|
||||||
if (shape->edges) {
|
if (shape->edges) {
|
||||||
rb_id_table_foreach(shape->edges, rb_edges_to_hash, &hash);
|
if (SINGLE_CHILD_P(shape->edges)) {
|
||||||
|
rb_shape_t * child = SINGLE_CHILD(shape->edges);
|
||||||
|
rb_edges_to_hash(child->edge_name, (VALUE)child, &hash);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rb_id_table_foreach(shape->edges, rb_edges_to_hash, &hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash;
|
return hash;
|
||||||
@ -679,8 +728,13 @@ static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE va
|
|||||||
static VALUE edges(struct rb_id_table* edges)
|
static VALUE edges(struct rb_id_table* edges)
|
||||||
{
|
{
|
||||||
VALUE hash = rb_hash_new();
|
VALUE hash = rb_hash_new();
|
||||||
if (edges)
|
if (SINGLE_CHILD_P(edges)) {
|
||||||
|
rb_shape_t * child = SINGLE_CHILD(edges);
|
||||||
|
collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
|
||||||
|
}
|
||||||
|
else {
|
||||||
rb_id_table_foreach(edges, collect_keys_and_values, &hash);
|
rb_id_table_foreach(edges, collect_keys_and_values, &hash);
|
||||||
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# frozen_string_literal: false
|
# frozen_string_literal: false
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
|
require 'objspace'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
# These test the functionality of object shapes
|
# These test the functionality of object shapes
|
||||||
class TestShapes < Test::Unit::TestCase
|
class TestShapes < Test::Unit::TestCase
|
||||||
@ -28,6 +30,14 @@ class TestShapes < Test::Unit::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class OrderedAlloc
|
||||||
|
def add_ivars
|
||||||
|
10.times do |i|
|
||||||
|
instance_variable_set("@foo" + i.to_s, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Example
|
class Example
|
||||||
def initialize
|
def initialize
|
||||||
@a = 1
|
@a = 1
|
||||||
@ -109,6 +119,12 @@ class TestShapes < Test::Unit::TestCase
|
|||||||
assert_predicate RubyVM::Shape.of(tc), :too_complex?
|
assert_predicate RubyVM::Shape.of(tc), :too_complex?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ordered_alloc_is_not_complex
|
||||||
|
5.times { OrderedAlloc.new.add_ivars }
|
||||||
|
obj = JSON.parse(ObjectSpace.dump(OrderedAlloc))
|
||||||
|
assert_operator obj["variation_count"], :<, RubyVM::Shape::SHAPE_MAX_VARIATIONS
|
||||||
|
end
|
||||||
|
|
||||||
def test_too_many_ivs_on_obj
|
def test_too_many_ivs_on_obj
|
||||||
obj = Object.new
|
obj = Object.new
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user