diff options
author | John Hawthorn <john@hawthorn.email> | 2022-01-25 19:16:57 -0800 |
---|---|---|
committer | John Hawthorn <john@hawthorn.email> | 2022-02-23 19:57:42 -0800 |
commit | b13a7c8e36e9b00b5c6668846f31be4e25523111 (patch) | |
tree | 9de58995b2e66027b83cf13aeacc3eb781ddd848 /object.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 'object.c')
-rw-r--r-- | object.c | 43 |
1 files changed, 38 insertions, 5 deletions
@@ -757,6 +757,26 @@ rb_obj_is_instance_of(VALUE obj, VALUE c) return RBOOL(rb_obj_class(obj) == c); } +// Returns whether c is a proper (c != cl) subclass of cl +// Both c and cl must be T_CLASS +static VALUE +class_search_class_ancestor(VALUE cl, VALUE c) +{ + RUBY_ASSERT(RB_TYPE_P(c, T_CLASS)); + RUBY_ASSERT(RB_TYPE_P(cl, T_CLASS)); + + size_t c_depth = RCLASS_SUPERCLASS_DEPTH(c); + size_t cl_depth = RCLASS_SUPERCLASS_DEPTH(cl); + VALUE *classes = RCLASS_SUPERCLASSES(cl); + + // If c's inheritance chain is longer, it cannot be an ancestor + // We are checking for a proper subclass so don't check if they are equal + if (cl_depth <= c_depth) + return Qfalse; + + // Otherwise check that c is in cl's inheritance chain + return RBOOL(classes[c_depth] == c); +} /* * call-seq: @@ -791,21 +811,34 @@ rb_obj_is_kind_of(VALUE obj, VALUE c) { VALUE cl = CLASS_OF(obj); - RUBY_ASSERT(cl); + RUBY_ASSERT(RB_TYPE_P(cl, T_CLASS)); + + // Fastest path: If the object's class is an exact match we know `c` is a + // class without checking type and can return immediately. + if (cl == c) return Qtrue; + + // Fast path: Both are T_CLASS + if (LIKELY(RB_TYPE_P(c, T_CLASS))) { + return class_search_class_ancestor(cl, c); + } // Note: YJIT needs this function to never allocate and never raise when // `c` is a class or a module. c = class_or_module_required(c); - return RBOOL(class_search_ancestor(cl, RCLASS_ORIGIN(c))); + c = RCLASS_ORIGIN(c); + + // Slow path: check each ancestor in the linked list and its method table + return RBOOL(class_search_ancestor(cl, c)); } + static VALUE class_search_ancestor(VALUE cl, VALUE c) { while (cl) { - if (cl == c || RCLASS_M_TBL(cl) == RCLASS_M_TBL(c)) - return cl; - cl = RCLASS_SUPER(cl); + if (cl == c || RCLASS_M_TBL(cl) == RCLASS_M_TBL(c)) + return cl; + cl = RCLASS_SUPER(cl); } return 0; } |