[pypy-commit] pypy cpyext-gc-cycle: Fixed performance issues in incmark

stevie_92 pypy.commits at gmail.com
Thu Oct 24 15:23:16 EDT 2019


Author: Stefan Beyer <home at sbeyer.at>
Branch: cpyext-gc-cycle
Changeset: r97853:4dc7f472f3df
Date: 2019-10-24 21:22 +0200
http://bitbucket.org/pypy/pypy/changeset/4dc7f472f3df/

Log:	Fixed performance issues in incmark Added debugging code

diff --git a/rpython/memory/gc/rrc/base.py b/rpython/memory/gc/rrc/base.py
--- a/rpython/memory/gc/rrc/base.py
+++ b/rpython/memory/gc/rrc/base.py
@@ -97,6 +97,8 @@
          self.GCFLAG_NO_HEAP_PTRS, self.GCFLAG_GARBAGE) = gc_flags
         self.p_list_young = self.gc.AddressStack()
         self.p_list_old = self.gc.AddressStack()
+        self.p_list_old_added = self.gc.AddressStack()
+        self.p_dict_old_free = self.gc.AddressDict()
         self.o_list_young = self.gc.AddressStack()
         self.o_list_old = self.gc.AddressStack()
         self.p_dict = self.gc.AddressDict()  # non-nursery keys only
@@ -139,6 +141,9 @@
             dct = self.p_dict
             if not self.gc.is_young_object(obj):
                 lst = self.p_list_old
+                if self.state == self.STATE_MARKING:
+                    debug_print("added p_list", pyobject)
+                    self.p_list_old_added.append(pyobject)
         lst.append(pyobject)
         dct.setitem(obj, pyobject)
 
@@ -259,14 +264,15 @@
         ll_assert(self.p_dict_nurs.length() == 0,
                   "p_dict_nurs not empty 1")
         lst = self.p_list_young
+        working_set = self.state == self.STATE_MARKING
         while lst.non_empty():
-            self._minor_free(lst.pop(), self.p_list_old, self.p_dict)
+            self._minor_free(lst.pop(), self.p_list_old, self.p_dict, working_set)
         lst = self.o_list_young
         no_o_dict = self.gc.null_address_dict()
         while lst.non_empty():
-            self._minor_free(lst.pop(), self.o_list_old, no_o_dict)
+            self._minor_free(lst.pop(), self.o_list_old, no_o_dict, False)
 
-    def _minor_free(self, pyobject, surviving_list, surviving_dict):
+    def _minor_free(self, pyobject, surviving_list, surviving_dict, working_set):
         intobj = self._pyobj(pyobject).c_ob_pypy_link
         obj = llmemory.cast_int_to_adr(intobj)
         if self.gc.is_in_nursery(obj):
@@ -299,6 +305,9 @@
         #
         if surviving:
             surviving_list.append(pyobject)
+            if working_set:
+                debug_print("added p_list", pyobject)
+                self.p_list_old_added.append(pyobject)
         else:
             self._free(pyobject)
 
@@ -309,6 +318,7 @@
         rc = self._pyobj(pyobject).c_ob_refcnt
         if rc >= REFCNT_FROM_PYPY_LIGHT:
             rc -= REFCNT_FROM_PYPY_LIGHT
+            debug_print("major_free", "free", pyobject, "refcnt", rc, "light")
             if rc == 0:
                 pygchdr = self.pyobj_as_gc(self._pyobj(pyobject))
                 if pygchdr <> lltype.nullptr(self.PYOBJ_GC_HDR):
@@ -325,6 +335,7 @@
             ll_assert(rc < int(REFCNT_FROM_PYPY_LIGHT * 0.99),
                       "refcount underflow from REFCNT_FROM_PYPY_LIGHT?")
             rc -= REFCNT_FROM_PYPY
+            debug_print("major_free", "free", pyobject, "refcnt", rc, "nonlight")
             self._pyobj(pyobject).c_ob_pypy_link = 0
             if rc == 0:
                 self.dealloc_pending.append(pyobject)
@@ -376,13 +387,15 @@
             pass  # the corresponding object may die
         else:
             # force the corresponding object to be alive
-            debug_print("pyobj stays alive", pyobj, "rc", rc, "cyclic_rc",
-                        cyclic_rc)
             if use_dict:
                 obj = self.pypy_link_dict.get(pyobject)
             else:
                 intobj = pyobj.c_ob_pypy_link
+                if intobj == 0:
+                    return
                 obj = llmemory.cast_int_to_adr(intobj)
+            debug_print("pyobj stays alive", pyobj, "rc", rc, "cyclic_rc",
+                        cyclic_rc)
             self.gc.objects_to_trace.append(obj)
             self.gc.visit_all_objects() # TODO: execute incrementally?
 
@@ -444,12 +457,14 @@
         #  * GCFLAG_NO_HEAP_PTRS: immortal object never traced (so far)
         intobj = self._pyobj(pyobject).c_ob_pypy_link
         obj = llmemory.cast_int_to_adr(intobj)
-        if self.gc.header(obj).tid & \
+        if intobj != 0 and self.gc.header(obj).tid & \
                 (self.GCFLAG_VISITED | self.GCFLAG_NO_HEAP_PTRS):
+            debug_print("major_free", "survive", pyobject)
             surviving_list.append(pyobject)
             if surviving_dict:
                 surviving_dict.insertclean(obj, pyobject)
         else:
+            debug_print("major_free", "free", pyobject)
             self._free(pyobject, True)
 
     def _untrack_tuples(self):
diff --git a/rpython/memory/gc/rrc/incmark.py b/rpython/memory/gc/rrc/incmark.py
--- a/rpython/memory/gc/rrc/incmark.py
+++ b/rpython/memory/gc/rrc/incmark.py
@@ -2,6 +2,7 @@
 from rpython.rtyper.lltypesystem import rffi
 from rpython.memory.gc.rrc.base import RawRefCountBaseGC
 from rpython.rlib.debug import ll_assert, debug_print, debug_start, debug_stop
+import time
 
 class RawRefCountIncMarkGC(RawRefCountBaseGC):
 
@@ -60,13 +61,21 @@
         # we are finished with marking, now finish things up
         ll_assert(self.state == self.STATE_GARBAGE_MARKING, "invalid state")
 
+        start = time.time()
+
         # sync snapshot
         self._sync_snapshot()
         self._debug_check_consistency(print_label="before-snap-discard")
 
+        debug_print("time snapshot sync", time.time() - start)
+        start = time.time()
+
         # now the snapshot is not needed any more, discard it
         self._discard_snapshot()
 
+        debug_print("time discard snapshot", time.time() - start)
+        start = time.time()
+
         # handle legacy finalizers (assumption: not a lot of legacy finalizers,
         # so no need to do it incrementally)
         if self._find_garbage(False):
@@ -74,17 +83,25 @@
             self._debug_check_consistency(print_label="end-legacy-fin")
         self.state = self.STATE_DEFAULT
 
+        debug_print("time legacy finalizer", time.time() - start)
+        start = time.time()
+
         # handle modern finalizers
         found_finalizer = self._find_finalizer()
         if found_finalizer:
             self._gc_list_move(self.pyobj_old_list, self.pyobj_isolate_list)
         use_cylicrc = not found_finalizer
 
+        debug_print("time modern finalizer", time.time() - start)
+        start = time.time()
+
         # now mark all pypy objects at the border, depending on the results
         self._debug_check_consistency(print_label="end-mark-cyclic")
         debug_print("use_cylicrc", use_cylicrc)
         self.p_list_old.foreach(self._major_trace, (use_cylicrc, False))
         self._debug_check_consistency(print_label="end-mark")
+
+        debug_print("time mark p_list_old", time.time() - start)
         return True
 
     def _sync_snapshot(self):
@@ -98,11 +115,12 @@
         consistent = True
         self.snapshot_consistent = True
 
+        start = time.time()
+
         # sync p_list_old (except gc-objects)
         # simply iterate the snapshot for objects in p_list, as linked objects
         # might not be freed, except by the gc; p_list is always at the
         # beginning of the snapshot, so break if we reached a different pyobj
-        free_p_list = self.gc.AddressStack()
         for i in range(0, self.total_objs):
             snapobj = self.snapshot_objs[i]
             if snapobj.pypy_link == 0:
@@ -123,8 +141,17 @@
                 if not consistent:
                     break
                 # move to separate list
-                self.p_list_old.remove(snapobj.pyobj)
-                free_p_list.append(snapobj.pyobj)
+                #self.p_list_old.remove(snapobj.pyobj) # TODO: this might be evil... do something different... -> unlink? special link?
+
+                # remove link, to free non-gc (so they won't get marked and are freed)
+                pyobj = llmemory.cast_adr_to_ptr(snapobj.pyobj, self.PYOBJ_HDR_PTR)
+                link = llmemory.cast_int_to_adr(pyobj.c_ob_pypy_link)
+                self.p_dict_old_free.setitem(snapobj.pyobj, link)
+                pyobj.c_ob_pypy_link = 0
+                debug_print("free p_list", pyobj, "refcnt", pyobj.c_ob_refcnt)
+
+        debug_print("time sync p_list_old", time.time() - start)
+        start = time.time()
 
         # look if there is a (newly) linked non-gc proxy, where the non-rc obj
         # is unmarked
@@ -132,6 +159,9 @@
         self.p_list_old.foreach(self._check_consistency_p_list_old, None)
         consistent &= self.p_list_old_consistent
 
+        debug_print("time consistency p_list_old", time.time() - start)
+        start = time.time()
+
         # sync gc objects
         pygchdr = self.pyobj_list.c_gc_next
         while pygchdr <> self.pyobj_list and consistent:
@@ -152,11 +182,18 @@
                     if not (self.gc.header(addr).tid &
                             (self.GCFLAG_VISITED | self.GCFLAG_NO_HEAP_PTRS)):
                         consistent = False
+                        debug_print("inconsistency found",
+                                    "marked new object", pyobj)
                         break
             pygchdr = next_old
         pygchdr_continue_gc = pygchdr
 
+
+        debug_print("time sync gc objs", time.time() - start)
+        start = time.time()
+
         # sync isolate objs
+        debug_print("check isolate")
         isolate_consistent = True
         pygchdr = self.pyobj_isolate_old_list.c_gc_next
         while pygchdr <> self.pyobj_isolate_old_list and isolate_consistent:
@@ -169,12 +206,15 @@
         consistent &= isolate_consistent
         self._debug_check_consistency(print_label="end-check-consistency")
 
+        debug_print("time sync isolate objs", time.time() - start)
+        start = time.time()
+
         if consistent:
-            free_p_list.foreach(self._free_p_list, None)
+            debug_print("consistent")
         else:
-            # keep linked non-gc alive
-            while free_p_list.non_empty():
-                self.p_list_old.append(free_p_list.pop())
+            debug_print("inconsistent")
+            # fix link
+            self.p_dict_old_free.foreach(self._fix_p_list, None)
             # continue previous loop, keep objects alive
             pygchdr = pygchdr_continue_gc
             while pygchdr <> self.pyobj_list:
@@ -191,6 +231,12 @@
             if not self._gc_list_is_empty(self.pyobj_old_list):
                 self._gc_list_merge(self.pyobj_old_list, self.pyobj_list)
 
+        self.p_dict_old_free.delete()
+        self.p_dict_old_free = self.gc.AddressDict()
+
+        debug_print("time free/inconsistent", time.time() - start)
+        start = time.time()
+
         if isolate_consistent:
             if not self._gc_list_is_empty(self.pyobj_isolate_old_list):
                 self._gc_list_merge(self.pyobj_isolate_old_list,
@@ -218,6 +264,8 @@
                 self._gc_list_merge(self.pyobj_isolate_dead_list,
                                     self.pyobj_list)
 
+        debug_print("time sync isolate", time.time() - start)
+
     def _check_consistency_gc(self, pygchdr, pylist_dead_target):
         c_gc_refs = self._pyobj_gc_refcnt_get(pygchdr)
         snapobj = self.snapshot_objs[c_gc_refs - 1]
@@ -230,6 +278,7 @@
             # refcount equal
             consistent = snapobj.refcnt_original == pyobj.c_ob_refcnt
             if not consistent:
+                debug_print("inconsistency found", "refcount not equal", pyobj)
                 return False
             # outgoing (internal) references equal
             self.snapshot_curr = snapobj
@@ -237,6 +286,7 @@
             self._check_snapshot_traverse(pyobj)
             consistent = self.snapshot_consistent
             if not consistent:
+                debug_print("inconsistency found", "references not equal", pyobj)
                 return False
             # consistent -> prepare object for collection
             self._gc_list_remove(pygchdr)
@@ -262,18 +312,11 @@
             if not (self.gc.header(addr).tid &
                     (self.GCFLAG_VISITED | self.GCFLAG_NO_HEAP_PTRS)):
                 self.p_list_old_consistent = False
+                debug_print("inconsistency found", "marked p_list", pyobj)
 
-    def _free_p_list(self, pyobject, foo):
-        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY
-        from rpython.rlib.rawrefcount import REFCNT_FROM_PYPY_LIGHT
+    def _fix_p_list(self, pyobject, addr_link, foo):
         pyobj = llmemory.cast_adr_to_ptr(pyobject, self.PYOBJ_HDR_PTR)
-        refcnt = pyobj.c_ob_refcnt
-        if refcnt >= REFCNT_FROM_PYPY_LIGHT:
-            refcnt -= REFCNT_FROM_PYPY_LIGHT
-        elif refcnt >= REFCNT_FROM_PYPY:
-            refcnt -= REFCNT_FROM_PYPY
-        pyobj.c_ob_refcnt = refcnt
-        pyobj.c_ob_pypy_link = 0
+        pyobj.c_ob_pypy_link = llmemory.cast_adr_to_int(addr_link, "symbolic")
 
     def _collect_roots(self):
         # Subtract all internal refcounts from the cyclic refcount
@@ -298,6 +341,13 @@
         simple_limit = 0
         first = True # rescan proxies, in case only non-rc have been marked
         #
+        while self.p_list_old_added.non_empty():
+            addr = self.p_list_old_added.pop()
+            pyobj = llmemory.cast_adr_to_ptr(addr, self.PYOBJ_HDR_PTR)
+            obj = llmemory.cast_int_to_adr(pyobj.c_ob_pypy_link)
+            debug_print("trace p_list", pyobj)
+            self.gc.objects_to_trace.append(obj)
+        #
         while first or (self.pyobj_to_trace.non_empty() and not reached_limit):
             while self.pyobj_to_trace.non_empty() and not reached_limit:
                 addr = self.pyobj_to_trace.pop()
@@ -308,7 +358,7 @@
                 simple_limit += 1
                 if simple_limit > self.inc_limit: # TODO: add test
                     reached_limit = True
-
+            self.gc.visit_all_objects()  # TODO: implement sane limit (ex. half of normal limit), retrace proxies
             self.p_list_old.foreach(self._mark_rawrefcount_linked, None)
             self.o_list_old.foreach(self._mark_rawrefcount_linked, None)
             first = False
@@ -343,7 +393,6 @@
                 intobj = snapobj.pypy_link
                 obj = llmemory.cast_int_to_adr(intobj)
                 self.gc.objects_to_trace.append(obj)
-                self.gc.visit_all_objects()  # TODO: move to outer loop, implement sane limit (ex. half of normal limit), retrace proxies
             # mark as processed
             snapobj.status = 0
         return alive
@@ -553,6 +602,7 @@
         pygchdr = self.pyobj_as_gc(pyobj)
         # only for non-gc
         if pygchdr == lltype.nullptr(self.PYOBJ_GC_HDR):
+            debug_print("clear link", pyobj)
             link = llmemory.cast_int_to_adr(pyobj.c_ob_pypy_link)
             self.pypy_link_dict.setitem(pyobject, link)
             pyobj.c_ob_pypy_link = 0


More information about the pypy-commit mailing list