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

ruby-changes:70728

From: Peter <ko1@a...>
Date: Tue, 4 Jan 2022 23:46:56 +0900 (JST)
Subject: [ruby-changes:70728] 615e9b2865 (master): [Feature #18364] Add GC.stat_heap to get stats for memory heaps

https://git.ruby-lang.org/ruby.git/commit/?id=615e9b2865

From 615e9b28658c5b44a4474e04a53b84ae83b8e3fd Mon Sep 17 00:00:00 2001
From: Peter Zhu <peter@p...>
Date: Tue, 4 Jan 2022 08:59:32 -0500
Subject: [Feature #18364] Add GC.stat_heap to get stats for memory heaps

GC.stat_heap will return stats for memory heaps. This is
used for the Variable Width Allocation feature.
---
 gc.c                 | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gc.rb                |  34 ++++++++++++++
 test/ruby/test_gc.rb |  66 ++++++++++++++++++++++++++++
 3 files changed, 222 insertions(+)

diff --git a/gc.c b/gc.c
index 0f44ff5c985..d9ce489724b 100644
--- a/gc.c
+++ b/gc.c
@@ -10643,6 +10643,128 @@ rb_gc_stat(VALUE key) https://github.com/ruby/ruby/blob/trunk/gc.c#L10643
     }
 }
 
+
+enum gc_stat_heap_sym {
+    gc_stat_heap_sym_slot_size,
+    gc_stat_heap_sym_heap_allocatable_pages,
+    gc_stat_heap_sym_heap_eden_pages,
+    gc_stat_heap_sym_heap_eden_slots,
+    gc_stat_heap_sym_heap_tomb_pages,
+    gc_stat_heap_sym_heap_tomb_slots,
+    gc_stat_heap_sym_last
+};
+
+static VALUE gc_stat_heap_symbols[gc_stat_heap_sym_last];
+
+static void
+setup_gc_stat_heap_symbols(void)
+{
+    if (gc_stat_heap_symbols[0] == 0) {
+#define S(s) gc_stat_heap_symbols[gc_stat_heap_sym_##s] = ID2SYM(rb_intern_const(#s))
+        S(slot_size);
+        S(heap_allocatable_pages);
+        S(heap_eden_pages);
+        S(heap_eden_slots);
+        S(heap_tomb_pages);
+        S(heap_tomb_slots);
+#undef S
+    }
+}
+
+static size_t
+gc_stat_heap_internal(int size_pool_idx, VALUE hash_or_sym)
+{
+    rb_objspace_t *objspace = &rb_objspace;
+    VALUE hash = Qnil, key = Qnil;
+
+    setup_gc_stat_heap_symbols();
+
+    if (RB_TYPE_P(hash_or_sym, T_HASH)) {
+        hash = hash_or_sym;
+    }
+    else if (SYMBOL_P(hash_or_sym)) {
+        key = hash_or_sym;
+    }
+    else {
+        rb_raise(rb_eTypeError, "non-hash or symbol argument");
+    }
+
+    if (size_pool_idx < 0 || size_pool_idx >= SIZE_POOL_COUNT) {
+        rb_raise(rb_eArgError, "size pool index out of range");
+    }
+
+    rb_size_pool_t *size_pool = &size_pools[size_pool_idx];
+
+#define SET(name, attr) \
+    if (key == gc_stat_heap_symbols[gc_stat_heap_sym_##name]) \
+        return attr; \
+    else if (hash != Qnil) \
+        rb_hash_aset(hash, gc_stat_heap_symbols[gc_stat_heap_sym_##name], SIZET2NUM(attr));
+
+    SET(slot_size, size_pool->slot_size);
+    SET(heap_allocatable_pages, size_pool->allocatable_pages);
+    SET(heap_eden_pages, SIZE_POOL_EDEN_HEAP(size_pool)->total_pages);
+    SET(heap_eden_slots, SIZE_POOL_EDEN_HEAP(size_pool)->total_slots);
+    SET(heap_tomb_pages, SIZE_POOL_TOMB_HEAP(size_pool)->total_pages);
+    SET(heap_tomb_slots, SIZE_POOL_TOMB_HEAP(size_pool)->total_slots);
+#undef SET
+
+    if (!NIL_P(key)) { /* matched key should return above */
+        rb_raise(rb_eArgError, "unknown key: %"PRIsVALUE, rb_sym2str(key));
+    }
+
+    return 0;
+}
+
+static VALUE
+gc_stat_heap(rb_execution_context_t *ec, VALUE self, VALUE heap_name, VALUE arg)
+{
+    if (NIL_P(heap_name)) {
+        if (NIL_P(arg)) {
+            arg = rb_hash_new();
+        }
+        else if (RB_TYPE_P(arg, T_HASH)) {
+            // ok
+        }
+        else {
+            rb_raise(rb_eTypeError, "non-hash given");
+        }
+
+        for (int i = 0; i < SIZE_POOL_COUNT; i++) {
+            VALUE hash = rb_hash_aref(arg, INT2FIX(i));
+            if (NIL_P(hash)) {
+                hash = rb_hash_new();
+                rb_hash_aset(arg, INT2FIX(i), hash);
+            }
+            gc_stat_heap_internal(i, hash);
+        }
+    }
+    else if (FIXNUM_P(heap_name)) {
+        int size_pool_idx = FIX2INT(heap_name);
+
+        if (NIL_P(arg)) {
+            arg = rb_hash_new();
+        }
+        else if (SYMBOL_P(arg)) {
+            size_t value = gc_stat_heap_internal(size_pool_idx, arg);
+            return SIZET2NUM(value);
+        }
+        else if (RB_TYPE_P(arg, T_HASH)) {
+            // ok
+        }
+        else {
+            rb_raise(rb_eTypeError, "non-hash or symbol given");
+        }
+
+        gc_stat_heap_internal(size_pool_idx, arg);
+    }
+    else {
+        rb_raise(rb_eTypeError, "heap_name must be nil or an Integer");
+    }
+
+    return arg;
+}
+
 static VALUE
 gc_stress_get(rb_execution_context_t *ec, VALUE self)
 {
diff --git a/gc.rb b/gc.rb
index 887fc06d264..e9fb8f883b6 100644
--- a/gc.rb
+++ b/gc.rb
@@ -205,6 +205,40 @@ module GC https://github.com/ruby/ruby/blob/trunk/gc.rb#L205
     Primitive.gc_stat hash_or_key
   end
 
+  # call-seq:
+  #    GC.stat_heap -> Hash
+  #    GC.stat_heap(nil, hash) -> Hash
+  #    GC.stat_heap(heap_name) -> Hash
+  #    GC.stat_heap(heap_name, hash) -> Hash
+  #    GC.stat_heap(heap_name, :key) -> Numeric
+  #
+  # Returns information for memory pools in the GC.
+  #
+  # If the first optional argument, +heap_name+, is passed in and not +nil+, it
+  # returns a +Hash+ containing information about the particular memory pool.
+  # Otherwise, it will return a +Hash+ with memory pool names as keys and
+  # a +Hash+ containing information about the memory pool as values.
+  #
+  # If the second optional argument, +hash_or_key+, is given as +Hash+, it will
+  # be overwritten and returned. This is intended to avoid the probe effect.
+  #
+  # If both optional arguments are passed in and the second optional argument is
+  # a symbol, it will return a +Numeric+ of the value for the particular memory
+  # pool.
+  #
+  # On CRuby, +heap_name+ is of the type +Integer+ but may be of type +String+
+  # on other implementations.
+  #
+  # The contents of the hash are implementation specific and may change in
+  # the future without notice.
+  #
+  # If the optional argument, hash, is given, it is overwritten and returned.
+  #
+  # This method is only expected to work on CRuby.
+  def self.stat_heap heap_name = nil, hash_or_key = nil
+    Primitive.gc_stat_heap heap_name, hash_or_key
+  end
+
   #  call-seq:
   #     GC.latest_gc_info -> {:gc_by=>:newobj}
   #     GC.latest_gc_info(hash) -> hash
diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb
index b7f906bbddd..2e91b5eb2d5 100644
--- a/test/ruby/test_gc.rb
+++ b/test/ruby/test_gc.rb
@@ -139,6 +139,72 @@ class TestGc < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_gc.rb#L139
     end
   end
 
+  def test_stat_heap
+    skip 'stress' if GC.stress
+
+    stat_heap = {}
+    stat = {}
+    # Initialize to prevent GC in future calls
+    GC.stat_heap(0, stat_heap)
+    GC.stat(stat)
+
+    GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
+      GC.stat_heap(i, stat_heap)
+      GC.stat(stat)
+
+      assert_equal GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] * (2**i), stat_heap[:slot_size]
+      assert_operator stat_heap[:heap_allocatable_pages], :<=, stat[:heap_allocatable_pages]
+      assert_operator stat_heap[:heap_eden_pages], :<=, stat[:heap_eden_pages]
+      assert_operator stat_heap[:heap_eden_slots], :>=, 0
+      assert_operator stat_heap[:heap_tomb_pages], :<=, stat[:heap_tomb_pages]
+      assert_operator stat_heap[:heap_tomb_slots], :>=, 0
+    end
+
+    GC.stat_heap(0, stat_heap)
+    assert_equal stat_heap[:slot_size], GC.stat_heap(0, :slot_size)
+    assert_equal stat_heap[:slot_size], GC.stat_heap(0)[:slot_size]
+
+    assert_raise(ArgumentError) { GC.stat_heap(-1) }
+    assert_raise(ArgumentError) { GC.stat_heap(GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT]) }
+  end
+
+  def test_stat_heap_all
+    stat_heap_all = {}
+    stat_heap = {}
+
+    2.times do
+      GC.stat_heap(0, stat_heap)
+      GC.stat_heap(nil, stat_heap_all)
+    end
+
+    GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT].times do |i|
+      GC.stat_heap(i, stat_heap)
+
+      assert_equal stat_heap, stat_heap_all[i]
+    end
+
+    assert_raise(TypeError) { GC.stat_heap(nil, :slot_size) }
+  end
+
+  def test_stat_heap_constraints
+    skip 'stress' if GC.stress
+
+    stat = GC.stat
+    stat_heap = GC.stat_heap
+    GC.stat(stat)
+    GC.stat_heap(nil, stat_heap)
+
+    stat_heap_sum = Hash.new(0)
+    stat_heap.values.each do |hash|
+      hash.each { |k, v| stat_heap_sum[k] += v }
+    end
+
+    assert_equal stat[:heap_allocatable_pages], stat_heap_sum[:heap_allocatable_pages]
+    assert_equal stat[:heap_eden_pages], stat_heap_sum[:heap_eden_pages]
+    assert_equal stat[:heap_tomb_pages], stat_heap_sum[:heap_tomb_pages]
+    assert_equal stat[:heap_available_slots], stat_heap_sum[:heap_eden_slots] + stat_heap_sum[:heap_tomb_slots]
+  end
+
   def test_latest_gc_info
     omit 'stress' if GC.stress
 
-- 
cgit v1.2.1


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

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