[pypy-commit] pypy memory-accounting: (arigo, fijal) add accounting for basic memory pressure done by raw mallocs

fijal pypy.commits at gmail.com
Fri Sep 29 07:01:44 EDT 2017


Author: fijal
Branch: memory-accounting
Changeset: r92501:f0e56ca3a8d6
Date: 2017-09-29 13:01 +0200
http://bitbucket.org/pypy/pypy/changeset/f0e56ca3a8d6/

Log:	(arigo, fijal) add accounting for basic memory pressure done by raw
	mallocs

diff --git a/rpython/memory/gc/base.py b/rpython/memory/gc/base.py
--- a/rpython/memory/gc/base.py
+++ b/rpython/memory/gc/base.py
@@ -83,7 +83,9 @@
                             has_custom_trace,
                             fast_path_tracing,
                             has_gcptr,
-                            cannot_pin):
+                            cannot_pin,
+                            has_memory_pressure,
+                            get_memory_pressure_ofs):
         self.finalizer_handlers = finalizer_handlers
         self.destructor_or_custom_trace = destructor_or_custom_trace
         self.is_old_style_finalizer = is_old_style_finalizer
@@ -103,6 +105,8 @@
         self.fast_path_tracing = fast_path_tracing
         self.has_gcptr = has_gcptr
         self.cannot_pin = cannot_pin
+        self.has_memory_pressure = has_memory_pressure
+        self.get_memory_pressure_ofs = get_memory_pressure_ofs
 
     def get_member_index(self, type_id):
         return self.member_index(type_id)
diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py
--- a/rpython/memory/gc/incminimark.py
+++ b/rpython/memory/gc/incminimark.py
@@ -2920,6 +2920,12 @@
         self.old_objects_with_weakrefs = new_with_weakref
 
     def get_stats(self, stats_no):
+        from rpython.memory.gc import inspector
+
+        if stats_no == rgc.TOTAL_MEMORY:
+            return 0
+        elif stats_no == rgc.TOTAL_MEMORY_PRESSURE:
+            return inspector.count_memory_pressure(self)
         return 0
 
 
diff --git a/rpython/memory/gc/inspector.py b/rpython/memory/gc/inspector.py
--- a/rpython/memory/gc/inspector.py
+++ b/rpython/memory/gc/inspector.py
@@ -92,17 +92,12 @@
 
 AddressStack = get_address_stack()
 
-class HeapDumper(object):
-    _alloc_flavor_ = "raw"
-    BUFSIZE = 8192     # words
+class BaseWalker(object):
+    _alloc_flavor_ = 'raw'
 
-    def __init__(self, gc, fd):
+    def __init__(self, gc):
         self.gc = gc
         self.gcflag = gc.gcflag_extra
-        self.fd = rffi.cast(rffi.INT, fd)
-        self.writebuffer = lltype.malloc(rffi.SIGNEDP.TO, self.BUFSIZE,
-                                         flavor='raw')
-        self.buf_count = 0
         if self.gcflag == 0:
             self.seen = AddressDict()
         self.pending = AddressStack()
@@ -111,8 +106,102 @@
         if self.gcflag == 0:
             self.seen.delete()
         self.pending.delete()
+        free_non_gc_object(self)
+
+    def add_roots(self):
+        self.gc.enumerate_all_roots(_hd_add_root, self)
+        pendingroots = self.pending
+        self.pending = AddressStack()
+        self.walk(pendingroots)
+        pendingroots.delete()
+        self.end_add_roots_marker()
+
+    def end_add_roots_marker(self):
+        pass
+
+    def add(self, obj):
+        if self.gcflag == 0:
+            if not self.seen.contains(obj):
+                self.seen.setitem(obj, obj)
+                self.pending.append(obj)
+        else:
+            hdr = self.gc.header(obj)
+            if (hdr.tid & self.gcflag) == 0:
+                hdr.tid |= self.gcflag
+                self.pending.append(obj)
+
+    def walk(self, pending):
+        while pending.non_empty():
+            self.processobj(pending.pop())
+
+    # ----------
+    # A simplified copy of the above, to make sure we walk again all the
+    # objects to clear the 'gcflag'.
+
+    def unobj(self, obj):
+        gc = self.gc
+        gc.trace(obj, self._unref, None)
+
+    def _unref(self, pointer, _):
+        obj = pointer.address[0]
+        self.unadd(obj)
+
+    def unadd(self, obj):
+        assert self.gcflag != 0
+        hdr = self.gc.header(obj)
+        if (hdr.tid & self.gcflag) != 0:
+            hdr.tid &= ~self.gcflag
+            self.pending.append(obj)
+
+    def clear_gcflag_again(self):
+        self.gc.enumerate_all_roots(_hd_unadd_root, self)
+        pendingroots = self.pending
+        self.pending = AddressStack()
+        self.unwalk(pendingroots)
+        pendingroots.delete()
+
+    def unwalk(self, pending):
+        while pending.non_empty():
+            self.unobj(pending.pop())
+
+    def finish_processing(self):
+        if self.gcflag != 0:
+            self.clear_gcflag_again()
+            self.unwalk(self.pending)
+
+    def process(self):
+        self.add_roots()
+        self.walk(self.pending)
+
+
+class MemoryPressureCounter(BaseWalker):
+
+    def __init__(self, gc):
+        self.count = 0
+        BaseWalker.__init__(self, gc)
+
+    def processobj(self, obj):
+        gc = self.gc
+        typeid = gc.get_type_id(obj)
+        if gc.has_memory_pressure(typeid):
+            ofs = gc.get_memory_pressure_ofs(typeid)
+            val = (obj + ofs).signed[0]
+            self.count += val
+
+
+class HeapDumper(BaseWalker):
+    BUFSIZE = 8192     # words
+
+    def __init__(self, gc, fd):
+        BaseWalker.__init__(self, gc)
+        self.fd = rffi.cast(rffi.INT, fd)
+        self.writebuffer = lltype.malloc(rffi.SIGNEDP.TO, self.BUFSIZE,
+                                         flavor='raw')
+        self.buf_count = 0
+
+    def delete(self):
         lltype.free(self.writebuffer, flavor='raw')
-        free_non_gc_object(self)
+        BaseWalker.delete(self)
 
     @jit.dont_look_inside
     def flush(self):
@@ -143,6 +232,7 @@
         self.write(0)
         self.write(0)
         self.write(-1)
+    end_add_roots_marker = write_marker
 
     def writeobj(self, obj):
         gc = self.gc
@@ -152,64 +242,13 @@
         self.write(gc.get_size_incl_hash(obj))
         gc.trace(obj, self._writeref, None)
         self.write(-1)
+    processobj = writeobj
 
     def _writeref(self, pointer, _):
         obj = pointer.address[0]
         self.write(llmemory.cast_adr_to_int(obj))
         self.add(obj)
 
-    def add(self, obj):
-        if self.gcflag == 0:
-            if not self.seen.contains(obj):
-                self.seen.setitem(obj, obj)
-                self.pending.append(obj)
-        else:
-            hdr = self.gc.header(obj)
-            if (hdr.tid & self.gcflag) == 0:
-                hdr.tid |= self.gcflag
-                self.pending.append(obj)
-
-    def add_roots(self):
-        self.gc.enumerate_all_roots(_hd_add_root, self)
-        pendingroots = self.pending
-        self.pending = AddressStack()
-        self.walk(pendingroots)
-        pendingroots.delete()
-        self.write_marker()
-
-    def walk(self, pending):
-        while pending.non_empty():
-            self.writeobj(pending.pop())
-
-    # ----------
-    # A simplified copy of the above, to make sure we walk again all the
-    # objects to clear the 'gcflag'.
-
-    def unwriteobj(self, obj):
-        gc = self.gc
-        gc.trace(obj, self._unwriteref, None)
-
-    def _unwriteref(self, pointer, _):
-        obj = pointer.address[0]
-        self.unadd(obj)
-
-    def unadd(self, obj):
-        assert self.gcflag != 0
-        hdr = self.gc.header(obj)
-        if (hdr.tid & self.gcflag) != 0:
-            hdr.tid &= ~self.gcflag
-            self.pending.append(obj)
-
-    def clear_gcflag_again(self):
-        self.gc.enumerate_all_roots(_hd_unadd_root, self)
-        pendingroots = self.pending
-        self.pending = AddressStack()
-        self.unwalk(pendingroots)
-        pendingroots.delete()
-
-    def unwalk(self, pending):
-        while pending.non_empty():
-            self.unwriteobj(pending.pop())
 
 def _hd_add_root(obj, heap_dumper):
     heap_dumper.add(obj)
@@ -219,15 +258,20 @@
 
 def dump_rpy_heap(gc, fd):
     heapdumper = HeapDumper(gc, fd)
-    heapdumper.add_roots()
-    heapdumper.walk(heapdumper.pending)
+    heapdumper.process()
     heapdumper.flush()
-    if heapdumper.gcflag != 0:
-        heapdumper.clear_gcflag_again()
-        heapdumper.unwalk(heapdumper.pending)
+    heapdumper.finish_processing()
     heapdumper.delete()
     return True
 
+def count_memory_pressure(gc):
+    counter = MemoryPressureCounter(gc)
+    counter.process()
+    counter.finish_processing()
+    res = counter.count
+    counter.delete()
+    return res
+
 def get_typeids_z(gc):
     srcaddress = gc.root_walker.gcdata.typeids_z
     return llmemory.cast_adr_to_ptr(srcaddress, lltype.Ptr(rgc.ARRAY_OF_CHAR))
diff --git a/rpython/memory/gctypelayout.py b/rpython/memory/gctypelayout.py
--- a/rpython/memory/gctypelayout.py
+++ b/rpython/memory/gctypelayout.py
@@ -21,13 +21,21 @@
     # A destructor is called when the object is about to be freed.
     # A custom tracer (CT) enumerates the addresses that contain GCREFs.
     # Both are called with the address of the object as only argument.
+    # They're embedded in a struct that has raw_memory_offset as another
+    # argument, which is only valid if T_HAS_MEMORY_PRESSURE is set
     CUSTOM_FUNC = lltype.FuncType([llmemory.Address], lltype.Void)
     CUSTOM_FUNC_PTR = lltype.Ptr(CUSTOM_FUNC)
+    CUSTOM_DATA_STRUCT = lltype.Struct('custom_data',
+        ('customfunc', CUSTOM_FUNC_PTR),
+        ('memory_pressure_offset', lltype.Signed), # offset to where the amount
+                                           # of owned memory pressure is stored
+        )
+    CUSTOM_DATA_STRUCT_PTR = lltype.Ptr(CUSTOM_DATA_STRUCT)
 
     # structure describing the layout of a typeid
     TYPE_INFO = lltype.Struct("type_info",
         ("infobits",       lltype.Signed),    # combination of the T_xxx consts
-        ("customfunc",     CUSTOM_FUNC_PTR),
+        ("customdata",     CUSTOM_DATA_STRUCT_PTR),
         ("fixedsize",      lltype.Signed),
         ("ofstoptrs",      lltype.Ptr(OFFSETS_TO_GC_PTR)),
         hints={'immutable': True},
@@ -81,14 +89,14 @@
     def q_cannot_pin(self, typeid):
         typeinfo = self.get(typeid)
         ANY = (T_HAS_GCPTR | T_IS_WEAKREF)
-        return (typeinfo.infobits & ANY) != 0 or bool(typeinfo.customfunc)
+        return (typeinfo.infobits & ANY) != 0 or bool(typeinfo.customdata)
 
     def q_finalizer_handlers(self):
         adr = self.finalizer_handlers   # set from framework.py or gcwrapper.py
         return llmemory.cast_adr_to_ptr(adr, lltype.Ptr(FIN_HANDLER_ARRAY))
 
     def q_destructor_or_custom_trace(self, typeid):
-        return self.get(typeid).customfunc
+        return self.get(typeid).customdata.customfunc
 
     def q_is_old_style_finalizer(self, typeid):
         typeinfo = self.get(typeid)
@@ -139,6 +147,15 @@
         infobits = self.get(typeid).infobits
         return infobits & T_ANY_SLOW_FLAG == 0
 
+    def q_has_memory_pressure(self, typeid):
+        infobits = self.get(typeid).infobits
+        return infobits & T_HAS_MEMORY_PRESSURE != 0
+
+    def q_get_memory_pressure_ofs(self, typeid):
+        infobits = self.get(typeid).infobits
+        assert infobits & T_HAS_MEMORY_PRESSURE != 0
+        return self.get(typeid).customdata.memory_pressure_offset
+
     def set_query_functions(self, gc):
         gc.set_query_functions(
             self.q_is_varsize,
@@ -159,7 +176,9 @@
             self.q_has_custom_trace,
             self.q_fast_path_tracing,
             self.q_has_gcptr,
-            self.q_cannot_pin)
+            self.q_cannot_pin,
+            self.q_has_memory_pressure,
+            self.q_get_memory_pressure_ofs)
 
     def _has_got_custom_trace(self, typeid):
         type_info = self.get(typeid)
@@ -176,8 +195,9 @@
 T_HAS_CUSTOM_TRACE          = 0x200000
 T_HAS_OLDSTYLE_FINALIZER    = 0x400000
 T_HAS_GCPTR                 = 0x1000000
-T_KEY_MASK                  = intmask(0xFE000000) # bug detection only
-T_KEY_VALUE                 = intmask(0x5A000000) # bug detection only
+T_HAS_MEMORY_PRESSURE       = 0x2000000 # first field is memory pressure field
+T_KEY_MASK                  = intmask(0xFC000000) # bug detection only
+T_KEY_VALUE                 = intmask(0x58000000) # bug detection only
 
 def _check_valid_type_info(p):
     ll_assert(p.infobits & T_KEY_MASK == T_KEY_VALUE, "invalid type_id")
@@ -192,6 +212,25 @@
     ll_assert(llop.is_group_member_nonzero(lltype.Bool, typeid),
               "invalid type_id")
 
+def has_special_memory_pressure(TYPE):
+    if TYPE._is_varsize():
+        return False
+    T = TYPE
+    while True:
+        if 'special_memory_pressure' in T._flds:
+            return True
+        if 'super' not in T._flds:
+            return False
+        T = T._flds['super']
+
+def get_memory_pressure_ofs(TYPE):
+    T = TYPE
+    while True:
+        if 'special_memory_pressure' in T._flds:
+            return llmemory.offsetof(T, 'special_memory_pressure')
+        if 'super' not in T._flds:
+            assert False, "get_ and has_memory_pressure disagree"
+        T = T._flds['super']    
 
 def encode_type_shape(builder, info, TYPE, index):
     """Encode the shape of the TYPE into the TYPE_INFO structure 'info'."""
@@ -202,12 +241,18 @@
         infobits |= T_HAS_GCPTR
     #
     fptrs = builder.special_funcptr_for_type(TYPE)
-    if fptrs:
+    if fptrs or has_special_memory_pressure(TYPE):
+        customdata = lltype.malloc(GCData.CUSTOM_DATA_STRUCT, flavor='raw',
+                                   immortal=True)
+        info.customdata = customdata
         if "destructor" in fptrs:
-            info.customfunc = fptrs["destructor"]
+            customdata.customfunc = fptrs["destructor"]
         if "old_style_finalizer" in fptrs:
-            info.customfunc = fptrs["old_style_finalizer"]
+            customdata.customfunc = fptrs["old_style_finalizer"]
             infobits |= T_HAS_OLDSTYLE_FINALIZER
+        if has_special_memory_pressure(TYPE):
+            infobits |= T_HAS_MEMORY_PRESSURE
+            info.customdata.memory_pressure_offset = get_memory_pressure_ofs(TYPE)
     #
     if not TYPE._is_varsize():
         info.fixedsize = llarena.round_up_for_allocation(
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -650,10 +650,12 @@
     else:
         return id(gcref._x)
 
-TOTAL_MEMORY, = range(1)
+TOTAL_MEMORY, TOTAL_MEMORY_PRESSURE = range(2)
 
 @not_rpython
 def get_stats(stat_no):
+    """ Long docstring goes here
+    """
     raise NotImplementedError
 
 @not_rpython
diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py
--- a/rpython/rtyper/rclass.py
+++ b/rpython/rtyper/rclass.py
@@ -475,6 +475,13 @@
         self.lowleveltype = Ptr(self.object_type)
         self.gcflavor = gcflavor
 
+    def has_special_memory_pressure(self, tp):
+        if 'special_memory_pressure' in tp._flds:
+            return True
+        if 'super' in tp._flds:
+            return self.has_special_memory_pressure(tp._flds['super'])
+        return False
+
     def _setup_repr(self, llfields=None, hints=None, adtmeths=None):
         # NOTE: don't store mutable objects like the dicts below on 'self'
         #       before they are fully built, to avoid strange bugs in case
@@ -525,7 +532,10 @@
 
             bookkeeper = self.rtyper.annotator.bookkeeper
             if self.classdef in bookkeeper.memory_pressure_types:
-                llfields = [('special_memory_pressure', lltype.Signed)] + llfields
+                # we don't need to add it if it's already there for some of
+                # the parent type
+                if not self.has_special_memory_pressure(self.rbase.object_type):
+                    llfields.append(('special_memory_pressure', lltype.Signed))
 
             object_type = MkStruct(self.classdef.name,
                                    ('super', self.rbase.object_type),
diff --git a/rpython/translator/c/test/test_newgc.py b/rpython/translator/c/test/test_newgc.py
--- a/rpython/translator/c/test/test_newgc.py
+++ b/rpython/translator/c/test/test_newgc.py
@@ -1624,12 +1624,16 @@
                 am3 = am2
                 am2 = am1
                 am1 = A()
+            am1 = am2 = am3 = None
             # what can we use for the res?
-            return rgc.get_stats(rgc.TOTAL_MEMORY)
+            for i in range(10):
+                gc.collect()
+            return rgc.get_stats(rgc.TOTAL_MEMORY_PRESSURE)
         return f
 
     def test_nongc_opaque_attached_to_gc(self):
         res = self.run("nongc_opaque_attached_to_gc")
+        # the res is 0 for non-memory-pressure-accounting GC
         assert res == 0
 
     def define_limited_memory(self):


More information about the pypy-commit mailing list