Author: fijal
Branch: 
Changeset: r93768:f23eec5d0d6d
Date: 2018-02-06 10:49 +0100
http://bitbucket.org/pypy/pypy/changeset/f23eec5d0d6d/

Log:    Merge memory-accounting which adds extra functions to gc that let
        you describe the whole memory

diff --git a/lib_pypy/_sqlite3.py b/lib_pypy/_sqlite3.py
--- a/lib_pypy/_sqlite3.py
+++ b/lib_pypy/_sqlite3.py
@@ -35,7 +35,7 @@
 except ImportError:
     assert '__pypy__' not in sys.builtin_module_names
     newlist_hint = lambda sizehint: []
-    add_memory_pressure = lambda size: None
+    add_memory_pressure = lambda size, obj: None
 
 if sys.version_info[0] >= 3:
     StandardError = Exception
@@ -153,9 +153,10 @@
     factory = Connection if not factory else factory
     # an sqlite3 db seems to be around 100 KiB at least (doesn't matter if
     # backed by :memory: or a file)
-    add_memory_pressure(100 * 1024)
-    return factory(database, timeout, detect_types, isolation_level,
+    res = factory(database, timeout, detect_types, isolation_level,
                     check_same_thread, factory, cached_statements)
+    add_memory_pressure(100 * 1024, res)
+    return res
 
 
 def _unicode_text_factory(x):
diff --git a/pypy/module/__pypy__/interp_magic.py 
b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -142,11 +142,14 @@
                   space.newbool(debug))
 
 @unwrap_spec(estimate=int)
-def add_memory_pressure(estimate):
+def add_memory_pressure(space, estimate, w_obj=None):
     """ Add memory pressure of estimate bytes. Useful when calling a C function
     that internally allocates a big chunk of memory. This instructs the GC to
     garbage collect sooner than it would otherwise."""
+    #if space.is_none(w_obj):
     rgc.add_memory_pressure(estimate)
+    #else:
+    #    rgc.add_memory_pressure(estimate, w_obj)
 
 @unwrap_spec(w_frame=PyFrame)
 def locals_to_fast(space, w_frame):
diff --git a/pypy/module/_cffi_backend/allocator.py 
b/pypy/module/_cffi_backend/allocator.py
--- a/pypy/module/_cffi_backend/allocator.py
+++ b/pypy/module/_cffi_backend/allocator.py
@@ -21,13 +21,13 @@
         if self.w_alloc is None:
             if self.should_clear_after_alloc:
                 ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
-                                    flavor='raw', zero=True,
-                                    add_memory_pressure=True)
+                                    flavor='raw', zero=True)
             else:
                 ptr = lltype.malloc(rffi.CCHARP.TO, datasize,
-                                    flavor='raw', zero=False,
-                                    add_memory_pressure=True)
-            return cdataobj.W_CDataNewStd(space, ptr, ctype, length)
+                                    flavor='raw', zero=False)
+            w_res = cdataobj.W_CDataNewStd(space, ptr, ctype, length)
+            rgc.add_memory_pressure(datasize, w_res)
+            return w_res
         else:
             w_raw_cdata = space.call_function(self.w_alloc,
                                               space.newint(datasize))
@@ -53,7 +53,7 @@
             if self.w_free is not None:
                 res.w_free = self.w_free
                 res.register_finalizer(space)
-            rgc.add_memory_pressure(datasize)
+            rgc.add_memory_pressure(datasize, res)
             return res
 
     @unwrap_spec(w_init=WrappedDefault(None))
diff --git a/pypy/module/_cffi_backend/cdataobj.py 
b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -447,7 +447,10 @@
             with self as ptr:
                 w_res = W_CDataGCP(space, ptr, self.ctype, self, w_destructor)
         if size != 0:
-            rgc.add_memory_pressure(size)
+            if isinstance(w_res, W_CDataGCP):
+                rgc.add_memory_pressure(size, w_res)
+            else:
+                rgc.add_memory_pressure(size, self)
         return w_res
 
     def unpack(self, length):
diff --git a/pypy/module/_hashlib/interp_hashlib.py 
b/pypy/module/_hashlib/interp_hashlib.py
--- a/pypy/module/_hashlib/interp_hashlib.py
+++ b/pypy/module/_hashlib/interp_hashlib.py
@@ -61,7 +61,8 @@
         ctx = ropenssl.EVP_MD_CTX_new()
         if ctx is None:
             raise MemoryError
-        rgc.add_memory_pressure(ropenssl.HASH_MALLOC_SIZE + self.digest_size)
+        rgc.add_memory_pressure(ropenssl.HASH_MALLOC_SIZE + self.digest_size,
+                                self)
         try:
             if copy_from:
                 if not ropenssl.EVP_MD_CTX_copy(ctx, copy_from):
diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py
--- a/pypy/module/_ssl/interp_ssl.py
+++ b/pypy/module/_ssl/interp_ssl.py
@@ -1316,8 +1316,9 @@
         if not ctx:
             raise ssl_error(space, "failed to allocate SSL context")
 
-        rgc.add_memory_pressure(10 * 1024 * 1024)
         self = space.allocate_instance(_SSLContext, w_subtype)
+        assert isinstance(self, _SSLContext)
+        rgc.add_memory_pressure(10 * 1024 * 1024, self)
         self.ctx = ctx
         self.check_hostname = False
         self.register_finalizer(space)
diff --git a/pypy/module/gc/__init__.py b/pypy/module/gc/__init__.py
--- a/pypy/module/gc/__init__.py
+++ b/pypy/module/gc/__init__.py
@@ -19,6 +19,7 @@
                 space.config.translation.gctransformer == "framework"):
             self.appleveldefs.update({
                 'dump_rpy_heap': 'app_referents.dump_rpy_heap',
+                'get_stats': 'app_referents.get_stats',
                 })
             self.interpleveldefs.update({
                 'get_rpy_roots': 'referents.get_rpy_roots',
@@ -28,6 +29,7 @@
                 'get_objects': 'referents.get_objects',
                 'get_referents': 'referents.get_referents',
                 'get_referrers': 'referents.get_referrers',
+                '_get_stats': 'referents.get_stats',
                 '_dump_rpy_heap': 'referents._dump_rpy_heap',
                 'get_typeids_z': 'referents.get_typeids_z',
                 'get_typeids_list': 'referents.get_typeids_list',
diff --git a/pypy/module/gc/app_referents.py b/pypy/module/gc/app_referents.py
--- a/pypy/module/gc/app_referents.py
+++ b/pypy/module/gc/app_referents.py
@@ -48,3 +48,49 @@
                 file.flush()
             fd = file.fileno()
         gc._dump_rpy_heap(fd)
+
+class GcStats(object):
+    def __init__(self, s):
+        self._s = s
+        for item in ('total_gc_memory', 'jit_backend_used',
+                     'total_memory_pressure',
+                     'total_allocated_memory', 'jit_backend_allocated',
+                     'peak_memory', 'peak_allocated_memory'):
+            setattr(self, item, self._format(getattr(self._s, item)))
+        self.memory_used_sum = self._format(self._s.total_gc_memory + 
self._s.total_memory_pressure +
+                                            self._s.jit_backend_used)
+        self.memory_allocated_sum = 
self._format(self._s.total_allocated_memory + self._s.total_memory_pressure +
+                                            self._s.jit_backend_allocated)
+
+    def _format(self, v):
+        if v < 1000000:
+            # bit unlikely ;-)
+            return "%.1fkB" % (v / 1024.)
+        return "%.1fMB" % (v / 1024. / 1024.)
+
+    def repr(self):
+        return """Total memory consumed:
+GC used:            %s (peak: %s)
+raw assembler used: %s
+memory pressure:    %s
+-----------------------------
+Total:              %s
+
+Total memory allocated:
+GC allocated:            %s (peak: %s)
+raw assembler allocated: %s
+memory pressure:         %s
+-----------------------------
+Total:                   %s
+""" % (self.total_gc_memory, self.peak_memory,
+       self.jit_backend_used,
+       self.total_memory_pressure,
+       self.memory_used_sum,
+
+       self.total_allocated_memory, self.peak_allocated_memory,
+       self.jit_backend_allocated,
+       self.total_memory_pressure,
+       self.memory_allocated_sum)
+
+def get_stats():
+    return GcStats(gc._get_stats())
diff --git a/pypy/module/gc/referents.py b/pypy/module/gc/referents.py
--- a/pypy/module/gc/referents.py
+++ b/pypy/module/gc/referents.py
@@ -1,7 +1,7 @@
-from rpython.rlib import rgc
+from rpython.rlib import rgc, jit_hooks
 from pypy.interpreter.baseobjspace import W_Root
-from pypy.interpreter.typedef import TypeDef
-from pypy.interpreter.gateway import unwrap_spec
+from pypy.interpreter.typedef import TypeDef, interp_attrproperty
+from pypy.interpreter.gateway import unwrap_spec, interp2app
 from pypy.interpreter.error import oefmt, wrap_oserror
 from rpython.rlib.objectmodel import we_are_translated
 
@@ -170,3 +170,33 @@
     l = rgc.get_typeids_list()
     list_w = [space.newint(l[i]) for i in range(len(l))]
     return space.newlist(list_w)
+
+class W_GcStats(W_Root):
+    def __init__(self):
+        self.total_memory_pressure = rgc.get_stats(rgc.TOTAL_MEMORY_PRESSURE)
+        self.total_gc_memory = rgc.get_stats(rgc.TOTAL_MEMORY)
+        self.total_allocated_memory = rgc.get_stats(rgc.TOTAL_ALLOCATED_MEMORY)
+        self.peak_memory = rgc.get_stats(rgc.PEAK_MEMORY)
+        self.peak_allocated_memory = rgc.get_stats(rgc.PEAK_ALLOCATED_MEMORY)
+        self.jit_backend_allocated = jit_hooks.stats_asmmemmgr_allocated(None)
+        self.jit_backend_used = jit_hooks.stats_asmmemmgr_used(None)
+
+W_GcStats.typedef = TypeDef("GcStats",
+    total_memory_pressure=interp_attrproperty("total_memory_pressure",
+        cls=W_GcStats, wrapfn="newint"),
+    total_gc_memory=interp_attrproperty("total_gc_memory",
+        cls=W_GcStats, wrapfn="newint"),
+    peak_allocated_memory=interp_attrproperty("peak_allocated_memory",
+        cls=W_GcStats, wrapfn="newint"),
+    peak_memory=interp_attrproperty("peak_memory",
+        cls=W_GcStats, wrapfn="newint"),
+    total_allocated_memory=interp_attrproperty("total_allocated_memory",
+        cls=W_GcStats, wrapfn="newint"),
+    jit_backend_allocated=interp_attrproperty("jit_backend_allocated",
+        cls=W_GcStats, wrapfn="newint"),
+    jit_backend_used=interp_attrproperty("jit_backend_used",
+        cls=W_GcStats, wrapfn="newint"),
+)
+
+def get_stats(space):
+    return W_GcStats()
diff --git a/pypy/module/pyexpat/interp_pyexpat.py 
b/pypy/module/pyexpat/interp_pyexpat.py
--- a/pypy/module/pyexpat/interp_pyexpat.py
+++ b/pypy/module/pyexpat/interp_pyexpat.py
@@ -840,11 +840,11 @@
     # Currently this is just the size of the pointer and some estimated bytes.
     # The struct isn't actually defined in expat.h - it is in xmlparse.c
     # XXX: find a good estimate of the XML_ParserStruct
-    rgc.add_memory_pressure(XML_Parser_SIZE + 300)
     if not xmlparser:
         raise oefmt(space.w_RuntimeError, "XML_ParserCreate failed")
 
     parser = W_XMLParserType(space, xmlparser, w_intern)
+    rgc.add_memory_pressure(XML_Parser_SIZE + 300, parser)
     XML_SetUnknownEncodingHandler(
         parser.itself, UnknownEncodingHandlerData_callback,
         rffi.cast(rffi.VOIDP, parser.id))
diff --git a/rpython/annotator/bookkeeper.py b/rpython/annotator/bookkeeper.py
--- a/rpython/annotator/bookkeeper.py
+++ b/rpython/annotator/bookkeeper.py
@@ -71,6 +71,7 @@
 
         self.needs_generic_instantiate = {}
         self.thread_local_fields = set()
+        self.memory_pressure_types = set()
 
         self.register_builtins()
 
diff --git a/rpython/jit/codewriter/support.py 
b/rpython/jit/codewriter/support.py
--- a/rpython/jit/codewriter/support.py
+++ b/rpython/jit/codewriter/support.py
@@ -675,6 +675,8 @@
 
     def _ll_1_gc_add_memory_pressure(num):
         llop.gc_add_memory_pressure(lltype.Void, num)
+    def _ll_2_gc_add_memory_pressure(num, obj):
+        llop.gc_add_memory_pressure(lltype.Void, num, obj)
 
 
 def setup_extra_builtin(rtyper, oopspec_name, nb_args, extra=None):
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
@@ -72,6 +72,7 @@
 from rpython.rlib.rarithmetic import LONG_BIT_SHIFT
 from rpython.rlib.debug import ll_assert, debug_print, debug_start, debug_stop
 from rpython.rlib.objectmodel import specialize
+from rpython.rlib import rgc
 from rpython.memory.gc.minimarkpage import out_of_memory
 
 #
@@ -371,6 +372,7 @@
         self.old_rawmalloced_objects = self.AddressStack()
         self.raw_malloc_might_sweep = self.AddressStack()
         self.rawmalloced_total_size = r_uint(0)
+        self.rawmalloced_peak_size = r_uint(0)
 
         self.gc_state = STATE_SCANNING
         #
@@ -996,6 +998,8 @@
             # Record the newly allocated object and its full malloced size.
             # The object is young or old depending on the argument.
             self.rawmalloced_total_size += r_uint(allocsize)
+            self.rawmalloced_peak_size = max(self.rawmalloced_total_size,
+                                             self.rawmalloced_peak_size)
             if alloc_young:
                 if not self.young_rawmalloced_objects:
                     self.young_rawmalloced_objects = self.AddressDict()
@@ -1023,7 +1027,7 @@
             if self.max_heap_size < self.next_major_collection_threshold:
                 self.next_major_collection_threshold = self.max_heap_size
 
-    def raw_malloc_memory_pressure(self, sizehint):
+    def raw_malloc_memory_pressure(self, sizehint, adr):
         # Decrement by 'sizehint' plus a very little bit extra.  This
         # is needed e.g. for _rawffi, which may allocate a lot of tiny
         # arrays.
@@ -1183,6 +1187,24 @@
         """
         return self.ac.total_memory_used + self.rawmalloced_total_size
 
+    def get_total_memory_alloced(self):
+        """ Return the total memory allocated
+        """
+        return self.ac.total_memory_alloced + self.rawmalloced_total_size
+
+    def get_peak_memory_alloced(self):
+        """ Return the peak memory ever allocated. The peaks
+        can be at different times, but we just don't worry for now
+        """
+        return self.ac.peak_memory_alloced + self.rawmalloced_peak_size
+
+    def get_peak_memory_used(self):
+        """ Return the peak memory GC felt ever responsible for
+        """
+        mem_allocated = max(self.ac.peak_memory_used,
+                            self.ac.total_memory_used)
+        return mem_allocated + self.rawmalloced_peak_size
+
     def threshold_reached(self, extra=0):
         return (self.next_major_collection_threshold -
                 float(self.get_total_memory_used())) < float(extra)
@@ -2155,6 +2177,8 @@
         #
         size_gc_header = self.gcheaderbuilder.size_gc_header
         self.rawmalloced_total_size += r_uint(raw_malloc_usage(totalsize))
+        self.rawmalloced_peak_size = max(self.rawmalloced_total_size,
+                                         self.rawmalloced_peak_size)
         self.old_rawmalloced_objects.append(arena + size_gc_header)
         return arena
 
@@ -2932,6 +2956,21 @@
         self.old_objects_with_weakrefs.delete()
         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 intmask(self.get_total_memory_used() + self.nursery_size)
+        elif stats_no == rgc.PEAK_MEMORY:
+            return intmask(self.get_peak_memory_used() + self.nursery_size)
+        elif stats_no == rgc.PEAK_ALLOCATED_MEMORY:
+            return intmask(self.get_peak_memory_alloced() + self.nursery_size)
+        elif stats_no == rgc.TOTAL_ALLOCATED_MEMORY:
+            return intmask(self.get_total_memory_alloced() + self.nursery_size)
+        elif stats_no == rgc.TOTAL_MEMORY_PRESSURE:
+            return inspector.count_memory_pressure(self)
+        return 0
+
 
     # ----------
     # RawRefCount
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
@@ -2,6 +2,7 @@
 Utility RPython functions to inspect objects in the GC.
 """
 from rpython.rtyper.lltypesystem import lltype, llmemory, rffi, llgroup
+from rpython.rtyper.lltypesystem.lloperation import llop
 from rpython.rlib.objectmodel import free_non_gc_object
 from rpython.rlib import rposix, rgc, jit
 
@@ -92,17 +93,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 +107,107 @@
         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
+        gc.trace(obj, self._ref, None)
+
+    def _ref(self, pointer, _):
+        obj = pointer.address[0]
+        self.add(obj)
+
+
+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 +238,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 +248,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 +264,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/gc/minimark.py b/rpython/memory/gc/minimark.py
--- a/rpython/memory/gc/minimark.py
+++ b/rpython/memory/gc/minimark.py
@@ -828,7 +828,7 @@
             if self.max_heap_size < self.next_major_collection_threshold:
                 self.next_major_collection_threshold = self.max_heap_size
 
-    def raw_malloc_memory_pressure(self, sizehint):
+    def raw_malloc_memory_pressure(self, sizehint, adr):
         self.next_major_collection_threshold -= sizehint
         if self.next_major_collection_threshold < 0:
             # cannot trigger a full collection now, but we can ensure
diff --git a/rpython/memory/gc/minimarkpage.py 
b/rpython/memory/gc/minimarkpage.py
--- a/rpython/memory/gc/minimarkpage.py
+++ b/rpython/memory/gc/minimarkpage.py
@@ -141,6 +141,9 @@
         # the total memory used, counting every block in use, without
         # the additional bookkeeping stuff.
         self.total_memory_used = r_uint(0)
+        self.peak_memory_used = r_uint(0)
+        self.total_memory_alloced = r_uint(0)
+        self.peak_memory_alloced = r_uint(0)
 
 
     def _new_page_ptr_list(self, length):
@@ -293,7 +296,11 @@
         #
         # 'arena_base' points to the start of malloced memory; it might not
         # be a page-aligned address
-        arena_base = llarena.arena_malloc(self.arena_size, False)
+        arena_base = llarena.arena_mmap(self.arena_size)
+        self.total_memory_alloced += self.arena_size
+        self.peak_memory_alloced = max(self.total_memory_alloced,
+                                       self.peak_memory_alloced)
+
         if not arena_base:
             out_of_memory("out of memory: couldn't allocate the next arena")
         arena_end = arena_base + self.arena_size
@@ -321,6 +328,8 @@
         """Prepare calls to mass_free_incremental(): moves the chained lists
         into 'self.old_xxx'.
         """
+        self.peak_memory_used = max(self.peak_memory_used,
+                                    self.total_memory_used)
         self.total_memory_used = r_uint(0)
         #
         size_class = self.small_request_threshold >> WORD_POWER_2
@@ -397,8 +406,8 @@
                 if arena.nfreepages == arena.totalpages:
                     #
                     # The whole arena is empty.  Free it.
-                    llarena.arena_reset(arena.base, self.arena_size, 4)
-                    llarena.arena_free(arena.base)
+                    llarena.arena_munmap(arena.base, self.arena_size)
+                    self.total_memory_alloced -= self.arena_size
                     lltype.free(arena, flavor='raw', track_allocation=False)
                     self.arenas_count -= 1
                     #
diff --git a/rpython/memory/gctransform/framework.py 
b/rpython/memory/gctransform/framework.py
--- a/rpython/memory/gctransform/framework.py
+++ b/rpython/memory/gctransform/framework.py
@@ -392,23 +392,30 @@
                 inline = True)
 
         if getattr(GCClass, 'raw_malloc_memory_pressure', False):
-            def raw_malloc_memory_pressure_varsize(length, itemsize):
+            def raw_malloc_memory_pressure_varsize(length, itemsize, adr):
                 totalmem = length * itemsize
                 if totalmem > 0:
-                    gcdata.gc.raw_malloc_memory_pressure(totalmem)
+                    gcdata.gc.raw_malloc_memory_pressure(totalmem, adr)
                 #else: probably an overflow -- the following rawmalloc
                 #      will fail then
-            def raw_malloc_memory_pressure(sizehint):
-                gcdata.gc.raw_malloc_memory_pressure(sizehint)
+            def raw_malloc_memory_pressure(sizehint, adr):
+                gcdata.gc.raw_malloc_memory_pressure(sizehint, adr)
             self.raw_malloc_memory_pressure_varsize_ptr = getfn(
                 raw_malloc_memory_pressure_varsize,
-                [annmodel.SomeInteger(), annmodel.SomeInteger()],
+                [annmodel.SomeInteger(), annmodel.SomeInteger(),
+                 SomeAddress()],
                 annmodel.s_None, minimal_transform = False)
             self.raw_malloc_memory_pressure_ptr = getfn(
                 raw_malloc_memory_pressure,
-                [annmodel.SomeInteger()],
+                [annmodel.SomeInteger(), SomeAddress()],
                 annmodel.s_None, minimal_transform = False)
 
+        if getattr(GCClass, 'get_stats', False):
+            def get_stats(stats_no):
+                return gcdata.gc.get_stats(stats_no)
+            self.get_stats_ptr = getfn(get_stats, [annmodel.SomeInteger()],
+                annmodel.SomeInteger())
+
 
         self.identityhash_ptr = getfn(GCClass.identityhash.im_func,
                                       [s_gc, s_gcref],
@@ -831,6 +838,39 @@
 
     gct_fv_gc_malloc_varsize = gct_fv_gc_malloc
 
+    def gct_gc_add_memory_pressure(self, hop):
+        def _find_correct_type(TP):
+            T = TP.TO
+            while 'special_memory_pressure' not in T._flds:
+                T = T._flds['super']
+            return T
+
+        if hasattr(self, 'raw_malloc_memory_pressure_ptr'):
+            op = hop.spaceop
+            size = op.args[0]
+            if len(op.args) == 2:
+                v_fld = rmodel.inputconst(lltype.Void, 
"special_memory_pressure")
+                T = _find_correct_type(op.args[1].concretetype)
+                v_inst = hop.genop("cast_pointer", [op.args[1]],
+                    resulttype=lltype.Ptr(T))
+                hop.genop("bare_setfield", [v_inst, v_fld, size])
+                v_adr = hop.genop("cast_ptr_to_adr", [op.args[1]],
+                    resulttype=llmemory.Address)
+            else:
+                v_adr = rmodel.inputconst(llmemory.Address, llmemory.NULL)
+            hop.genop("direct_call", [self.raw_malloc_memory_pressure_ptr,
+                               size, v_adr])
+
+
+    def gct_gc_get_stats(self, hop):
+        if hasattr(self, 'get_stats_ptr'):
+            return hop.genop("direct_call",
+                [self.get_stats_ptr, hop.spaceop.args[0]],
+                resultvar=hop.spaceop.result)
+        hop.genop("same_as", [rmodel.inputconst(lltype.Signed, 0)],
+            resultvar=hop.spaceop.result)
+
+
     def gct_gc__collect(self, hop):
         op = hop.spaceop
         if len(op.args) == 1:
diff --git a/rpython/memory/gctransform/transform.py 
b/rpython/memory/gctransform/transform.py
--- a/rpython/memory/gctransform/transform.py
+++ b/rpython/memory/gctransform/transform.py
@@ -535,12 +535,7 @@
         return self.varsize_malloc_helper(hop, flags, meth, [])
 
     def gct_gc_add_memory_pressure(self, hop):
-        if hasattr(self, 'raw_malloc_memory_pressure_ptr'):
-            op = hop.spaceop
-            size = op.args[0]
-            return hop.genop("direct_call",
-                          [self.raw_malloc_memory_pressure_ptr,
-                           size])
+        pass
 
     def varsize_malloc_helper(self, hop, flags, meth, extraargs):
         def intconst(c): return rmodel.inputconst(lltype.Signed, c)
@@ -574,9 +569,10 @@
                                                                     
c_offset_to_length):
         if flags.get('add_memory_pressure', False):
             if hasattr(self, 'raw_malloc_memory_pressure_varsize_ptr'):
+                v_adr = rmodel.inputconst(llmemory.Address, llmemory.NULL)
                 hop.genop("direct_call",
                           [self.raw_malloc_memory_pressure_varsize_ptr,
-                           v_length, c_item_size])
+                           v_length, c_item_size, v_adr])
         if c_offset_to_length is None:
             if flags.get('zero'):
                 fnptr = self.raw_malloc_varsize_no_length_zero_ptr
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/memory/gcwrapper.py b/rpython/memory/gcwrapper.py
--- a/rpython/memory/gcwrapper.py
+++ b/rpython/memory/gcwrapper.py
@@ -83,9 +83,9 @@
     def gettypeid(self, obj):
         return self.get_type_id(lltype.typeOf(obj).TO)
 
-    def add_memory_pressure(self, size):
+    def add_memory_pressure(self, size, adr):
         if hasattr(self.gc, 'raw_malloc_memory_pressure'):
-            self.gc.raw_malloc_memory_pressure(size)
+            self.gc.raw_malloc_memory_pressure(size, adr)
 
     def shrink_array(self, p, smallersize):
         if hasattr(self.gc, 'shrink_array'):
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -598,21 +598,31 @@
         return False
     return type(x).__module__ != '__builtin__'   # keep non-builtins
 
-def add_memory_pressure(estimate):
+def add_memory_pressure(estimate, object=None):
     """Add memory pressure for OpaquePtrs."""
     pass
 
 class AddMemoryPressureEntry(ExtRegistryEntry):
     _about_ = add_memory_pressure
 
-    def compute_result_annotation(self, s_nbytes):
+    def compute_result_annotation(self, s_nbytes, s_object=None):
         from rpython.annotator import model as annmodel
+        if s_object is not None:
+            if not isinstance(s_object, annmodel.SomeInstance):
+                raise Exception("Wrong kind of object passed to "
+                                "add memory pressure")
+            self.bookkeeper.memory_pressure_types.add(s_object.classdef)
         return annmodel.s_None
 
     def specialize_call(self, hop):
-        [v_size] = hop.inputargs(lltype.Signed)
+        v_size = hop.inputarg(lltype.Signed, 0)
+        if len(hop.args_v) == 2:
+            v_obj = hop.inputarg(hop.args_r[1], 1)
+            args = [v_size, v_obj]
+        else:
+            args = [v_size]
         hop.exception_cannot_occur()
-        return hop.genop('gc_add_memory_pressure', [v_size],
+        return hop.genop('gc_add_memory_pressure', args,
                          resulttype=lltype.Void)
 
 
@@ -640,6 +650,15 @@
     else:
         return id(gcref._x)
 
+(TOTAL_MEMORY, TOTAL_ALLOCATED_MEMORY, TOTAL_MEMORY_PRESSURE,
+ PEAK_MEMORY, PEAK_ALLOCATED_MEMORY) = range(5)
+
+@not_rpython
+def get_stats(stat_no):
+    """ Long docstring goes here
+    """
+    raise NotImplementedError
+
 @not_rpython
 def dump_rpy_heap(fd):
     raise NotImplementedError
@@ -834,6 +853,18 @@
         return hop.genop('gc_get_rpy_type_index', vlist,
                          resulttype = hop.r_result)
 
+class Entry(ExtRegistryEntry):
+    _about_ = get_stats
+    def compute_result_annotation(self, s_no):
+        from rpython.annotator.model import SomeInteger
+        if not isinstance(s_no, SomeInteger):
+            raise Exception("expecting an integer")
+        return SomeInteger()
+    def specialize_call(self, hop):
+        args = hop.inputargs(lltype.Signed)
+        hop.exception_cannot_occur()
+        return hop.genop('gc_get_stats', args, resulttype=lltype.Signed)
+
 @not_rpython
 def _is_rpy_instance(gcref):
     raise NotImplementedError
diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py
--- a/rpython/rlib/rthread.py
+++ b/rpython/rlib/rthread.py
@@ -85,7 +85,11 @@
 
 
 def allocate_lock():
-    return Lock(allocate_ll_lock())
+    # Add some memory pressure for the size of the lock because it is an
+    # Opaque object
+    lock = Lock(allocate_ll_lock())
+    rgc.add_memory_pressure(TLOCKP_SIZE, lock)
+    return lock
 
 @specialize.arg(0)
 def ll_start_new_thread(func):
@@ -248,9 +252,6 @@
     if rffi.cast(lltype.Signed, res) <= 0:
         lltype.free(ll_lock, flavor='raw', track_allocation=False)
         raise error("out of resources")
-    # Add some memory pressure for the size of the lock because it is an
-    # Opaque object
-    rgc.add_memory_pressure(TLOCKP_SIZE)
     return ll_lock
 
 def free_ll_lock(ll_lock):
diff --git a/rpython/rlib/rzlib.py b/rpython/rlib/rzlib.py
--- a/rpython/rlib/rzlib.py
+++ b/rpython/rlib/rzlib.py
@@ -269,7 +269,6 @@
     compress data.
     """
     stream = lltype.malloc(z_stream, flavor='raw', zero=True)
-    rgc.add_memory_pressure(rffi.sizeof(z_stream))
     err = _deflateInit2(stream, level, method, wbits, memLevel, strategy)
     if err == Z_OK:
         if zdict is not None:
@@ -304,7 +303,6 @@
     decompress data.
     """
     stream = lltype.malloc(z_stream, flavor='raw', zero=True)
-    rgc.add_memory_pressure(rffi.sizeof(z_stream))
     err = _inflateInit2(stream, wbits)
     if err == Z_OK:
         if zdict is not None and wbits < 0:
diff --git a/rpython/rtyper/lltypesystem/llarena.py 
b/rpython/rtyper/lltypesystem/llarena.py
--- a/rpython/rtyper/lltypesystem/llarena.py
+++ b/rpython/rtyper/lltypesystem/llarena.py
@@ -327,6 +327,17 @@
     assert not arena_addr.arena.objectptrs
     arena_addr.arena.mark_freed()
 
+def arena_mmap(nbytes):
+    """Allocate and return a new arena, zero-initialized by the
+    system, calling mmap()."""
+    return arena_malloc(nbytes, True)
+
+def arena_munmap(arena_addr, nbytes):
+    """Release an arena allocated with arena_mmap()."""
+    arena_free(arena_addr)
+    assert nbytes == arena_addr.arena.nbytes
+
+
 def arena_reset(arena_addr, size, zero):
     """Free all objects in the arena, which can then be reused.
     This can also be used on a subrange of the arena.
@@ -530,6 +541,31 @@
                   llfakeimpl=arena_free,
                   sandboxsafe=True)
 
+def llimpl_arena_mmap(nbytes):
+    from rpython.rlib import rmmap
+    flags = rmmap.MAP_PRIVATE | rmmap.MAP_ANONYMOUS
+    prot = rmmap.PROT_READ | rmmap.PROT_WRITE
+    p = rffi.cast(llmemory.Address, rmmap.c_mmap_safe(
+        lltype.nullptr(rmmap.PTR.TO), nbytes, prot, flags, -1, 0))
+    if p == rffi.cast(llmemory.Address, -1):
+        p = rffi.cast(llmemory.Address, 0)
+    return p
+register_external(arena_mmap, [int], llmemory.Address,
+                  'll_arena.arena_mmap',
+                  llimpl=llimpl_arena_mmap,
+                  llfakeimpl=arena_mmap,
+                  sandboxsafe=True)
+
+def llimpl_arena_munmap(arena_addr, nbytes):
+    from rpython.rlib import rmmap
+    assert nbytes >= 0
+    rmmap.c_munmap_safe(rffi.cast(rmmap.PTR, arena_addr), nbytes)
+register_external(arena_munmap, [llmemory.Address, int], None,
+                  'll_arena.arena_munmap',
+                  llimpl=llimpl_arena_munmap,
+                  llfakeimpl=arena_munmap,
+                  sandboxsafe=True)
+
 def llimpl_arena_reset(arena_addr, size, zero):
     if zero:
         if zero == 1:
diff --git a/rpython/rtyper/lltypesystem/lloperation.py 
b/rpython/rtyper/lltypesystem/lloperation.py
--- a/rpython/rtyper/lltypesystem/lloperation.py
+++ b/rpython/rtyper/lltypesystem/lloperation.py
@@ -485,6 +485,7 @@
     'gc_gettypeid'        : LLOp(),
     'gc_gcflag_extra'     : LLOp(),
     'gc_add_memory_pressure': LLOp(),
+    'gc_get_stats'        : LLOp(),
     'gc_fq_next_dead'     : LLOp(),
     'gc_fq_register'      : LLOp(),
     'gc_ignore_finalizer' : LLOp(canrun=True),
diff --git a/rpython/rtyper/rclass.py b/rpython/rtyper/rclass.py
--- a/rpython/rtyper/rclass.py
+++ b/rpython/rtyper/rclass.py
@@ -15,6 +15,7 @@
     RuntimeTypeInfo, getRuntimeTypeInfo, typeOf, Void, FuncType, Bool, Signed,
     functionptr, attachRuntimeTypeInfo)
 from rpython.rtyper.lltypesystem.lloperation import llop
+from rpython.rtyper.llannotation import lltype_to_annotation
 from rpython.rtyper.llannotation import SomePtr
 from rpython.rtyper.lltypesystem import rstr
 from rpython.rtyper.rmodel import (
@@ -475,6 +476,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
@@ -523,6 +531,16 @@
                 if not attrdef.readonly and self.is_quasi_immutable(name):
                     llfields.append(('mutate_' + name, OBJECTPTR))
 
+            bookkeeper = self.rtyper.annotator.bookkeeper
+            if self.classdef in bookkeeper.memory_pressure_types:
+                # 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))
+                    fields['special_memory_pressure'] = (
+                        'special_memory_pressure',
+                        
self.rtyper.getrepr(lltype_to_annotation(lltype.Signed)))
+
             object_type = MkStruct(self.classdef.name,
                                    ('super', self.rbase.object_type),
                                    hints=hints,
@@ -663,6 +681,8 @@
         while base.classdef is not None:
             base = base.rbase
             for fieldname in base.fields:
+                if fieldname == 'special_memory_pressure':
+                    continue
                 try:
                     mangled, r = base._get_field(fieldname)
                 except KeyError:
@@ -717,6 +737,9 @@
                            resulttype=Ptr(self.object_type))
         ctypeptr = inputconst(CLASSTYPE, self.rclass.getvtable())
         self.setfield(vptr, '__class__', ctypeptr, llops)
+        if self.has_special_memory_pressure(self.object_type):
+            self.setfield(vptr, 'special_memory_pressure',
+                inputconst(lltype.Signed, 0), llops)
         # initialize instance attributes from their defaults from the class
         if self.classdef is not None:
             flds = self.allinstancefields.keys()
diff --git a/rpython/translator/backendopt/writeanalyze.py 
b/rpython/translator/backendopt/writeanalyze.py
--- a/rpython/translator/backendopt/writeanalyze.py
+++ b/rpython/translator/backendopt/writeanalyze.py
@@ -65,6 +65,11 @@
         elif op.opname == "gc_store_indexed":
             if graphinfo is None or not graphinfo.is_fresh_malloc(op.args[0]):
                 return self._gc_store_indexed_result(op)
+        elif op.opname == 'gc_add_memory_pressure':
+            # special_memory_pressure would be overwritten by zero, because
+            # the JIT cannot see the field write, which is why we assume
+            # it can write anything
+            return top_set
         return empty_set
 
     def _array_result(self, 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
@@ -1613,7 +1613,7 @@
                 digest = ropenssl.EVP_get_digestbyname('sha1')
                 self.ctx = ropenssl.EVP_MD_CTX_new()
                 ropenssl.EVP_DigestInit(self.ctx, digest)
-                rgc.add_memory_pressure(ropenssl.HASH_MALLOC_SIZE + 64)
+                rgc.add_memory_pressure(ropenssl.HASH_MALLOC_SIZE + 64, self)
 
             def __del__(self):
                 ropenssl.EVP_MD_CTX_free(self.ctx)
@@ -1624,12 +1624,16 @@
                 am3 = am2
                 am2 = am1
                 am1 = A()
+            am1 = am2 = am3 = None
             # what can we use for the res?
-            return 0
+            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):
@@ -1668,6 +1672,38 @@
 class TestIncrementalMiniMarkGC(TestMiniMarkGC):
     gcpolicy = "incminimark"
 
+    def define_total_memory_pressure(cls):
+        class A(object):
+            def __init__(self):
+                rgc.add_memory_pressure(10, self)
+
+            def __del__(self):
+                pass
+
+        class B(A):
+            def __init__(self):
+                rgc.add_memory_pressure(10, self)
+
+        class C(A):
+            pass
+
+        class Glob(object):
+            pass
+        glob = Glob()
+
+        def f():
+            glob.l = [None] * 3
+            for i in range(10000):
+                glob.l[i % 3] = A()
+                glob.l[(i + 1) % 3] = B()
+                glob.l[(i + 2) % 3] = C()
+            return rgc.get_stats(rgc.TOTAL_MEMORY_PRESSURE)
+        return f
+
+    def test_total_memory_pressure(self):
+        res = self.run("total_memory_pressure")
+        assert res == 30 # total reachable is 3
+
     def define_random_pin(self):
         class A:
             foo = None
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to