This makes `RBobject` `4B` larger on 32 bit systems
but simplifies the implementation a lot.
[Feature #21353]
Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
The intial complex shape implementation never allowed objects
other than T_OBJECT to become too complex, unless we run out of
shapes.
I don't see any reason to prevent that.
Ref: https://github.com/ruby/ruby/pull/6931
As well as `RB_OBJ_SHAPE_ID` -> `rb_obj_shape_id`
and `RSHAPE` is now a simple alias for `rb_shape_lookup`.
I tried to turn all these into `static inline` but I'm having
trouble with `RUBY_EXTERN rb_shape_tree_t *rb_shape_tree_ptr;`
not being exposed as I'd expect.
Introduced in: https://github.com/ruby/ruby/pull/13159
Now that there is no longer a unique TOO_COMPLEX shape with
no children, checking `shape->type == TOO_COMPLEX` is incorrect.
And get rid of the `obj_to_id_tbl`
It's no longer needed, the `object_id` is now stored inline
in the object alongside instance variables.
We still need the inverse table in case `_id2ref` is invoked, but
we lazily build it by walking the heap if that happens.
The `object_id` concern is also no longer a GC implementation
concern, but a generic implementation.
Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com>
This opens the door to store more informations in shapes, such
as the `object_id` or object address in case it has been observed
and the object has to be moved.
Ivars will longer be the only thing stored inline
via shapes, so keeping the `iv_index` and `ivptr` names
would be confusing.
Instance variables won't be the only thing stored inline
via shapes, so keeping the `ivptr` name would be confusing.
`field` encompass anything that can be stored in a VALUE array.
Similarly, `gen_ivtbl` becomes `gen_fields_tbl`.
If the shape has only one child, we check it lock-free without
compromising thread safety.
I haven't computed hard data as to how often that it the case,
but we can assume that it's not too rare for shapes to have
a single child that is often requested, typically when freezing
and object.
Most of this code use the `type * name` style, while the
overwhemling majority of the rest of ruby use the `type *name`
style.
This is a cosmetic change, but helps with readability.
Use PR_SET_VMA_ANON_NAME to set human-readable names for anonymous
virtual memory areas mapped by `mmap()` when compiled and run on Linux
5.17 or higher. This makes it convenient for developers to debug mmap.
Now that we've inlined the eden_heap into the size_pool, we should
rename the size_pool to heap. So that Ruby contains multiple heaps, with
different sized objects.
The term heap as a collection of memory pages is more in memory
management nomenclature, whereas size_pool was a name chosen out of
necessity during the development of the Variable Width Allocation
features of Ruby.
The concept of size pools was introduced in order to facilitate
different sized objects (other than the default 40 bytes). They wrapped
the eden heap and the tomb heap, and some related state, and provided a
reasonably simple way of duplicating all related concerns, to provide
multiple pools that all shared the same structure but held different
objects.
Since then various changes have happend in Ruby's memory layout:
* The concept of tomb heaps has been replaced by a global free pages list,
with each page having it's slot size reconfigured at the point when it
is resurrected
* the eden heap has been inlined into the size pool itself, so that now
the size pool directly controls the free_pages list, the sweeping
page, the compaction cursor and the other state that was previously
being managed by the eden heap.
Now that there is no need for a heap wrapper, we should refer to the
collection of pages containing Ruby objects as a heap again rather than
a size pool
If there is a shape cache, then we should believe the results instead of
doing a linear search for non-existent items
This fixes a case where checking the index of an undefined ivar would
result in an O(n) search. Now we get O(log n).
Benchmark is as follows:
```ruby
N = ARGV[0].to_i
class ManyIVs
class_eval "def initialize;" +
N.times.map { "@a#{_1} = #{_1}" }.join("\n") +
"end"
def check
defined?(@not)
end
end
class Subclass < ManyIVs
def initialize
super
@foo = 123
end
end
def t
s = Process.clock_gettime Process::CLOCK_MONOTONIC
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
end
def test
a = ManyIVs.new
b = Subclass.new
t { 200000.times { a.check; b.check } }
end
puts "#{N},#{test}"
```
On the master branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015619999991031364
4,0.013061000005109236
7,0.013365999999223277
10,0.015474999992875382
13,0.017674999980954453
16,0.020055999979376793
19,0.02260500000556931
22,0.0254080000158865
25,0.02806599999894388
28,0.031244999991031364
31,0.034568000002764165
```
On this branch:
```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015848999988520518
4,0.013225000002421439
7,0.013049000001046807
10,0.010697999998228624
13,0.010902000009082258
16,0.011448000004747882
19,0.01151199999731034
22,0.011539999977685511
25,0.01173300002119504
28,0.011900000012246892
31,0.012278999987756833
```