Issue #21856 has been updated by jhawthorn (John Hawthorn). I think I've found a solution to this: we can return to the Ruby 3.4 `O(1)` removal, remove box/namespacing from it, and actually make it even simpler by skipping the CLASS -> ICLASS (and ICLASS -> ICLASS) relationship and directly associating T_CLASS with it's true T_CLASS "superclass". --- Currently we maintain the subclasses list for two separate purposes (we essentially have to different relationships we're putting into the same list): 1. On a T_MODULE, we track the T_ICLASSes created to include it into other classes. Used for method invalidation and propagating includes on the module that happen after it's been used 2. On a T_CLASS/T_ICLASS, we track the T_CLASS/T_ICLASS which are the immediate children of the class. We use this for method invalidation, some cvar things, and to iterate through subclasses. Purpose 1 does not have any issues with box, the T_ICLASS always belongs to one specific module and that's immutable. This list can be box-global (always use the prime classext or hoist it out) and only needs to be pruned during free. If we care about behaviour under a particular box (ie. the propagating includes), we should look up the current box being modified on the ICLASS itself. Purpose 2 is more complicated. It currently tracks the immediate children, the T_CLASS or T_ICLASS whose super points back. Because super is per-box and is mutable (include/prepend insert ICLASSes into the chain) we need to update the list on include/prepend, entries must be per-box, and we can have multiple entries per-box. *I propose we simplify this by no longer tracking the immediate subclass*, but instead tracking the T_CLASS -> ... -> T_CLASS relationship, ie. the inverse of rb_class_superclass. That relationship is the same across all boxes and immutable after Class creation. As a special case the ICLASS for refinements are also added to the purpose 2 list (on T_CLASS). As those ICLASS do not chain to an eventual leaf T_CLASS. When we need to find the classes which have included a module, we can use the module subclasses list to find the ICLASS and then use RCLASS_INCLUDER. If we needed to iterate all T_ICLASS, we could then walk up the CLASS_SUPER chain, but I didn't find anywhere we needed to do that. --- https://github.com/ruby/ruby/pull/16363 ---------------------------------------- Bug #21856: Massive performance degradation of `rb_obj_free` for `T_CLASS` since Ruby 4.0 https://bugs.ruby-lang.org/issues/21856#change-116665 * Author: ahorek (Pavel Rosický) * Status: Open * ruby -v: ruby 4.1.0dev (2026-01-31T09:41:30Z master 7ef8c470d2) +PRISM [x86_64-linux] * Backport: 3.2: DONTNEED, 3.3: DONTNEED, 3.4: DONTNEED, 4.0: REQUIRED ---------------------------------------- Loofah sanitization is noticeably slower ``` Ruby: 3.4.8 Loofah: 2.25.0 Nokogiri: 1.19.0 Iterations: 100000 user system total real Loofah.fragment + scrub!(:prune) 26.091872 0.000000 26.091872 ( 25.110925) Loofah.scrub_fragment(:prune) 25.913185 0.010392 25.923577 ( 24.948464) Nokogiri HTML parse only 3.852690 0.000000 3.852690 ( 3.705930) Ruby: 4.0.0 & 4.0.1 Loofah: 2.25.0 Nokogiri: 1.19.0 Iterations: 100000 user system total real Loofah.fragment + scrub!(:prune) 38.094207 0.041753 38.135960 ( 36.669463) Loofah.scrub_fragment(:prune) 40.168795 0.000045 40.168840 ( 38.561806) Nokogiri HTML parse only 4.012936 0.052024 4.064960 ( 3.913272) Ruby: 4.1.0 (ruby 4.1.0dev (2026-01-31T09:41:30Z master 7ef8c470d2) +PRISM [x86_64-linux]) Loofah: 2.25.0 Nokogiri: 1.19.0 Iterations: 100000 user system total real Loofah.fragment + scrub!(:prune) 39.004228 0.000000 39.004228 ( 37.694873) Loofah.scrub_fragment(:prune) 39.043199 0.031284 39.074483 ( 37.182785) Nokogiri HTML parse only 3.889100 0.010427 3.899527 ( 3.741622) ``` Originally reported https://www.redmine.org/issues/43737 ---Files-------------------------------- benchmark.rb (1002 Bytes) -- https://bugs.ruby-lang.org/