[前][次][番号順一覧][スレッド一覧]

ruby-changes:69660

From: Yusuke <ko1@a...>
Date: Tue, 9 Nov 2021 16:11:28 +0900 (JST)
Subject: [ruby-changes:69660] 428227472f (master): class.c: calculate the length of Class.descendants in advance

https://git.ruby-lang.org/ruby.git/commit/?id=428227472f

From 428227472fc6563046d8138aab17f07bef6af753 Mon Sep 17 00:00:00 2001
From: Yusuke Endoh <mame@r...>
Date: Tue, 2 Nov 2021 19:23:36 +0900
Subject: class.c: calculate the length of Class.descendants in advance

GC must not be triggered during callback of rb_class_foreach_subclass.
To prevent GC, we can not use rb_ary_push. Instead, this changeset calls
rb_class_foreach_subclass twice: first counts the subclasses, then
allocates a buffer (which may cause GC and reduce subclasses, but not
increase), and finally stores the subclasses to the buffer.

[Bug #18282] [Feature #14394]
---
 class.c                 | 35 +++++++++++++++++++++++++++++------
 test/ruby/test_class.rb |  6 ++++++
 2 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/class.c b/class.c
index 6bf17aaa477..a11285fc97e 100644
--- a/class.c
+++ b/class.c
@@ -124,6 +124,8 @@ rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg) https://github.com/ruby/ruby/blob/trunk/class.c#L124
     while (cur) {
 	VALUE curklass = cur->klass;
 	cur = cur->next;
+        // do not trigger GC during f, otherwise the cur will become
+        // a dangling pointer if the subclass is collected
 	f(curklass, arg);
     }
 }
@@ -1334,13 +1336,24 @@ rb_mod_ancestors(VALUE mod) https://github.com/ruby/ruby/blob/trunk/class.c#L1336
     return ary;
 }
 
+struct subclass_traverse_data
+{
+    VALUE *buffer;
+    long count;
+};
+
 static void
-class_descendants_recursive(VALUE klass, VALUE ary)
+class_descendants_recursive(VALUE klass, VALUE v)
 {
+    struct subclass_traverse_data *data = (struct subclass_traverse_data *) v;
+
     if (BUILTIN_TYPE(klass) == T_CLASS && !FL_TEST(klass, FL_SINGLETON)) {
-        rb_ary_push(ary, klass);
+        if (data->buffer) {
+            data->buffer[data->count] = klass;
+        }
+        data->count++;
     }
-    rb_class_foreach_subclass(klass, class_descendants_recursive, ary);
+    rb_class_foreach_subclass(klass, class_descendants_recursive, v);
 }
 
 /*
@@ -1364,9 +1377,19 @@ class_descendants_recursive(VALUE klass, VALUE ary) https://github.com/ruby/ruby/blob/trunk/class.c#L1377
 VALUE
 rb_class_descendants(VALUE klass)
 {
-    VALUE ary = rb_ary_new();
-    rb_class_foreach_subclass(klass, class_descendants_recursive, ary);
-    return ary;
+    struct subclass_traverse_data data = { NULL, 0 };
+
+    // estimate the count of subclasses
+    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
+
+    // this allocation may cause GC which may reduce the subclasses
+    data.buffer = ALLOCA_N(VALUE, data.count);
+    data.count = 0;
+
+    // enumerate subclasses
+    rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
+
+    return rb_ary_new_from_values(data.count, data.buffer);
 }
 
 static void
diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb
index 96bca08601c..034f4c6d205 100644
--- a/test/ruby/test_class.rb
+++ b/test/ruby/test_class.rb
@@ -755,4 +755,10 @@ class TestClass < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_class.rb#L755
     assert_include(object_descendants, sc)
     assert_include(object_descendants, ssc)
   end
+
+  def test_descendants_gc
+    c = Class.new
+    100000.times { Class.new(c) }
+    assert(c.descendants.size <= 100000)
+  end
 end
-- 
cgit v1.2.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]