[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