diff options
author | John Hawthorn <[email protected]> | 2022-01-25 19:16:57 -0800 |
---|---|---|
committer | John Hawthorn <[email protected]> | 2022-02-23 19:57:42 -0800 |
commit | b13a7c8e36e9b00b5c6668846f31be4e25523111 (patch) | |
tree | 9de58995b2e66027b83cf13aeacc3eb781ddd848 /class.c | |
parent | 764e4fa850de749790e5ed11c8a4ab86a4499ac0 (diff) |
Constant time class to class ancestor lookup
Previously when checking ancestors, we would walk all the way up the
ancestry chain checking each parent for a matching class or module.
I believe this was especially unfriendly to CPU cache since for each
step we need to check two cache lines (the class and class ext).
This check is used quite often in:
* case statements
* rescue statements
* Calling protected methods
* Class#is_a?
* Module#===
* Module#<=>
I believe it's most common to check a class against a parent class, to
this commit aims to improve that (unfortunately does not help checking
for an included Module).
This is done by storing on each class the number and an array of all
parent classes, in order (BasicObject is at index 0). Using this we can
check whether a class is a subclass of another in constant time since we
know the location to expect it in the hierarchy.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/5568
Diffstat (limited to 'class.c')
-rw-r--r-- | class.c | 60 |
1 files changed, 60 insertions, 0 deletions
@@ -260,6 +260,63 @@ rb_class_boot(VALUE super) } void +rb_class_remove_superclasses(VALUE klass) +{ + if (!RB_TYPE_P(klass, T_CLASS)) + return; + + if (RCLASS_SUPERCLASSES(klass)) + xfree(RCLASS_SUPERCLASSES(klass)); + + RCLASS_SUPERCLASSES(klass) = NULL; + RCLASS_SUPERCLASS_DEPTH(klass) = 0; +} + +void +rb_class_update_superclasses(VALUE klass) +{ + VALUE super = RCLASS_SUPER(klass); + + if (!RB_TYPE_P(klass, T_CLASS)) return; + if (super == Qundef) return; + + // If the superclass array is already built + if (RCLASS_SUPERCLASSES(klass)) + return; + + // find the proper superclass + while (super != Qfalse && !RB_TYPE_P(super, T_CLASS)) { + super = RCLASS_SUPER(super); + } + + // For BasicObject and uninitialized classes, depth=0 and ary=NULL + if (super == Qfalse) + return; + + // Sometimes superclasses are set before the full ancestry tree is built + // This happens during metaclass construction + if (super != rb_cBasicObject && !RCLASS_SUPERCLASS_DEPTH(super)) { + rb_class_update_superclasses(super); + + // If it is still unset we need to try later + if (!RCLASS_SUPERCLASS_DEPTH(super)) + return; + } + + size_t parent_num = RCLASS_SUPERCLASS_DEPTH(super); + size_t num = parent_num + 1; + + VALUE *superclasses = xmalloc(sizeof(VALUE) * num); + superclasses[parent_num] = super; + if (parent_num > 0) { + memcpy(superclasses, RCLASS_SUPERCLASSES(super), sizeof(VALUE) * parent_num); + } + + RCLASS_SUPERCLASSES(klass) = superclasses; + RCLASS_SUPERCLASS_DEPTH(klass) = num; +} + +void rb_check_inheritable(VALUE super) { if (!RB_TYPE_P(super, T_CLASS)) { @@ -667,6 +724,9 @@ make_metaclass(VALUE klass) while (RB_TYPE_P(super, T_ICLASS)) super = RCLASS_SUPER(super); RCLASS_SET_SUPER(metaclass, super ? ENSURE_EIGENCLASS(super) : rb_cClass); + // Full class ancestry may not have been filled until we reach here. + rb_class_update_superclasses(METACLASS_OF(metaclass)); + return metaclass; } |