Skip to content

Commit 68ffc8d

Browse files
committed
Set allocator on class creation
Allocating an instance of a class uses the allocator for the class. When the class has no allocator set, Ruby looks for it in the super class (see rb_get_alloc_func()). It's uncommon for classes created from Ruby code to ever have an allocator set, so it's common during the allocation process to search all the way to BasicObject from the class with which the allocation is being performed. This makes creating instances of classes that have long ancestry chains more expensive than creating instances of classes have that shorter ancestry chains. Setting the allocator at class creation time removes the need to perform a search for the alloctor during allocation. This is a breaking change for C-extensions that assume that classes created from Ruby code have no allocator set. Libraries that setup a class hierarchy in Ruby code and then set the allocator on some parent class, for example, can experience breakage. This seems like an unusual use case and hopefully it is rare or non-existent in practice. Rails has many classes that have upwards of 60 elements in the ancestry chain and benchmark shows a significant improvement for allocating with a class that includes 64 modules. ``` pre: ruby 3.0.0dev (2020-11-12T14:39:27Z master 6325866) post: ruby 3.0.0dev (2020-11-12T20:15:30Z cut-allocator-lookup) Comparison: allocate_8_deep post: 10336985.6 i/s pre: 8691873.1 i/s - 1.19x slower allocate_32_deep post: 10423181.2 i/s pre: 6264879.1 i/s - 1.66x slower allocate_64_deep post: 10541851.2 i/s pre: 4936321.5 i/s - 2.14x slower allocate_128_deep post: 10451505.0 i/s pre: 3031313.5 i/s - 3.45x slower ```
1 parent ebb96fa commit 68ffc8d

File tree

2 files changed

+22
-0
lines changed

2 files changed

+22
-0
lines changed

benchmark/object_allocate.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
prelude: |
2+
class Eight
3+
8.times { include(Module.new) }
4+
end
5+
class ThirtyTwo
6+
32.times { include(Module.new) }
7+
end
8+
class SixtyFour
9+
64.times { include(Module.new) }
10+
end
11+
class OneTwentyEight
12+
128.times { include(Module.new) }
13+
end
14+
# Disable GC to see raw throughput:
15+
GC.disable
16+
benchmark:
17+
allocate_8_deep: Eight.new
18+
allocate_32_deep: ThirtyTwo.new
19+
allocate_64_deep: SixtyFour.new
20+
allocate_128_deep: OneTwentyEight.new
21+
loop_count: 100000

vm_insnhelper.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,6 +4034,7 @@ vm_declare_class(ID id, rb_num_t flags, VALUE cbase, VALUE super)
40344034
/* new class declaration */
40354035
VALUE s = VM_DEFINECLASS_HAS_SUPERCLASS_P(flags) ? super : rb_cObject;
40364036
VALUE c = declare_under(id, cbase, rb_define_class_id(id, s));
4037+
rb_define_alloc_func(c, rb_get_alloc_func(c));
40374038
rb_class_inherited(s, c);
40384039
return c;
40394040
}

0 commit comments

Comments
 (0)