[pypy-commit] pypy concurrent-marksweep: Tweak tweak tweak.

arigo noreply at buildbot.pypy.org
Sat Jan 7 19:08:40 CET 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: concurrent-marksweep
Changeset: r51120:73514c0443a5
Date: 2012-01-07 19:01 +0100
http://bitbucket.org/pypy/pypy/changeset/73514c0443a5/

Log:	Tweak tweak tweak.

diff --git a/pypy/rpython/memory/gc/concurrentgen.py b/pypy/rpython/memory/gc/concurrentgen.py
--- a/pypy/rpython/memory/gc/concurrentgen.py
+++ b/pypy/rpython/memory/gc/concurrentgen.py
@@ -63,20 +63,19 @@
         # Automatically adjust the remaining parameters from the environment.
         "read_from_env": True,
 
-        # The default size of the nursery: use 6 MB by default.
-        # Environment variable: PYPY_GC_NURSERY
-        "nursery_size": 6*1024*1024,
+        # The minimal RAM usage: use 24 MB by default.
+        # Environment variable: PYPY_GC_MIN
+        "min_heap_size": 6*1024*1024,
         }
 
 
     def __init__(self, config,
                  read_from_env=False,
-                 nursery_size=32*WORD,
-                 fill_factor=2.0,   # xxx kill
+                 min_heap_size=128*WORD,
                  **kwds):
         GCBase.__init__(self, config, **kwds)
         self.read_from_env = read_from_env
-        self.minimal_nursery_size = nursery_size
+        self.min_heap_size = r_uint(min_heap_size)
         #
         self.main_thread_ident = ll_thread.get_ident() # non-transl. debug only
         #
@@ -93,14 +92,6 @@
         # is a collection running and the mutator tries to change an object
         # that was not scanned yet.
         self._init_writebarrier_logic()
-        #
-        def _nursery_full(additional_size):
-            # a hack to reduce the code size in _account_for_nursery():
-            # avoids the 'self' argument.
-            assert self.nursery_size_still_available < 0
-            self.nursery_full(additional_size)
-        _nursery_full._dont_inline_ = True
-        self._nursery_full = _nursery_full
 
     def _initialize(self):
         # Initialize the GC.  In normal translated program, this function
@@ -118,7 +109,9 @@
         # contains the objects that the write barrier re-marked as young
         # (so they are "old young objects").
         self.new_young_objects = self.NULL
+        self.new_young_objects_size = r_uint(0)
         self.old_objects = self.NULL
+        self.old_objects_size = r_uint(0)    # total size of self.old_objects
         #
         # See concurrentgen.txt for more information about these fields.
         self.current_young_marker = MARK_BYTE_1
@@ -148,37 +141,48 @@
         #
         self.collector.setup()
         #
-        self.set_minimal_nursery_size(self.minimal_nursery_size)
+        self.set_min_heap_size(self.min_heap_size)
         if self.read_from_env:
             #
-            newsize = env.read_from_env('PYPY_GC_NURSERY')
+            newsize = env.read_from_env('PYPY_GC_MIN')
             if newsize > 0:
-                self.set_minimal_nursery_size(newsize)
+                self.set_min_heap_size(r_uint(newsize))
         #
-        debug_print("minimal nursery size:", self.minimal_nursery_size)
+        debug_print("minimal heap size:", self.min_heap_size)
         debug_stop("gc-startup")
 
-    def set_minimal_nursery_size(self, newsize):
-        # See concurrentgen.txt.  At the start of the process, 'newsize' is
-        # a quarter of the total memory size.
-        newsize = min(newsize, (sys.maxint - 65535) // 4)
-        self.minimal_nursery_size = r_uint(newsize)
-        self.total_memory_size = r_uint(4 * newsize)  # total size
-        self.nursery_size = r_uint(newsize)           # size of the '->new...' box
-        self.old_objects_size = r_uint(0)             # approx size of 'old objs' box
-        self.nursery_size_still_available = intmask(self.nursery_size)
+    def set_min_heap_size(self, newsize):
+        # See concurrentgen.txt.
+        self.min_heap_size = newsize
+        self.total_memory_size = newsize     # total heap size
+        self.nursery_limit = newsize >> 2    # total size of the '->new...' box
+        #
+        # The in-use portion of the '->new...' box contains the objs
+        # that are in the 'new_young_objects' list.  The total of their
+        # size is 'new_young_objects_size'.
+        #
+        # The 'old objects' box contains the objs that are in the
+        # 'old_objects' list.  The total of their size is 'old_objects_size'.
+        #
+        # The write barrier occasionally resets the mark byte of objects
+        # to 'young'.  This is done without adding or removing objects
+        # to the above lists, and consequently without correcting the
+        # '*_size' variables.  Because of that, the 'old_objects' lists
+        # may contain a few objects that are not marked 'old' any more,
+        # and conversely, prebuilt objects may end up marked 'old' but
+        # are never added to the 'old_objects' list.
 
     def update_total_memory_size(self):
         # compute the new value for 'total_memory_size': it should be
         # twice old_objects_size, but never less than 2/3rd of the old value,
-        # and at least 4 * minimal_nursery_size.
+        # and at least 'min_heap_size'
         absolute_maximum = r_uint(-1)
         if self.old_objects_size < absolute_maximum // 2:
             tms = self.old_objects_size * 2
         else:
             tms = absolute_maximum
         tms = max(tms, self.total_memory_size // 3 * 2)
-        tms = max(tms, 4 * self.minimal_nursery_size)
+        tms = max(tms, self.min_heap_size)
         self.total_memory_size = tms
         debug_print("total memory size:", tms)
 
@@ -228,7 +232,6 @@
         size_gc_header = self.gcheaderbuilder.size_gc_header
         totalsize = size_gc_header + size
         rawtotalsize = raw_malloc_usage(totalsize)
-        self._account_for_nursery(rawtotalsize)
         adr = llarena.arena_malloc(rawtotalsize, 2)
         if adr == llmemory.NULL:
             raise MemoryError
@@ -237,7 +240,11 @@
         hdr = self.header(obj)
         hdr.tid = self.combine(typeid, self.current_young_marker, 0)
         hdr.next = self.new_young_objects
+        debug_print("malloc:", rawtotalsize, obj)
         self.new_young_objects = hdr
+        self.new_young_objects_size += r_uint(rawtotalsize)
+        if self.new_young_objects_size > self.nursery_limit:
+            self.nursery_overflowed(obj)
         return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
     def malloc_varsize_clear(self, typeid, length, size, itemsize,
@@ -253,7 +260,6 @@
             raise MemoryError
         #
         rawtotalsize = raw_malloc_usage(totalsize)
-        self._account_for_nursery(rawtotalsize)
         adr = llarena.arena_malloc(rawtotalsize, 2)
         if adr == llmemory.NULL:
             raise MemoryError
@@ -263,17 +269,13 @@
         hdr = self.header(obj)
         hdr.tid = self.combine(typeid, self.current_young_marker, 0)
         hdr.next = self.new_young_objects
+        debug_print("malloc:", rawtotalsize, obj)
         self.new_young_objects = hdr
+        self.new_young_objects_size += r_uint(rawtotalsize)
+        if self.new_young_objects_size > self.nursery_limit:
+            self.nursery_overflowed(obj)
         return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
-    def _account_for_nursery(self, additional_size):
-        self.nursery_size_still_available -= additional_size
-        debug_print("malloc:", additional_size,
-                    "still_available:", self.nursery_size_still_available)
-        if self.nursery_size_still_available < 0:
-            self._nursery_full(additional_size)
-    _account_for_nursery._always_inline_ = True
-
     # ----------
     # Other functions in the GC API
 
@@ -322,7 +324,7 @@
             cym = self.current_young_marker
             com = self.current_old_marker
             mark = self.get_mark(obj)
-            #debug_print("deletion_barrier:", mark, obj)
+            debug_print("deletion_barrier:", mark, obj)
             #
             if mark == com:     # most common case, make it fast
                 #
@@ -385,16 +387,12 @@
 
     # ----------
 
-    def nursery_full(self, additional_size):
-        # See concurrentgen.txt.
+    def nursery_overflowed(self, newest_obj):
+        # See concurrentgen.txt.  Called after the nursery overflowed.
         #
-        # Handle big allocations specially
-        if additional_size > intmask(self.total_memory_size >> 4):
-            xxxxxxxxxxxx
-            self.handle_big_allocation(additional_size)
-            return
+        debug_start("gc-nursery-full")
         #
-        if self.collector.running == 0 or self.stop_collection():
+        if self.previous_collection_finished():
             # The previous collection finished; no collection is running now.
             #
             # Expand the nursery if we can, up to 25% of total_memory_size.
@@ -404,46 +402,51 @@
             expand_to = self.total_memory_size >> 2
             expand_to = min(expand_to, self.total_memory_size -
                                        self.old_objects_size)
-            if expand_to > self.nursery_size:
-                debug_print("expanded nursery size:", expand_to)
-                self.nursery_size_still_available += intmask(expand_to -
-                                                             self.nursery_size)
-                self.nursery_size = expand_to
+            if expand_to > self.nursery_limit:
+                debug_print("expanding nursery limit to:", expand_to)
+                self.nursery_limit = expand_to
                 #
-                # If 'nursery_size_still_available' has been increased to a
-                # nonnegative number, then we are done: we can just continue
-                # filling the nursery.
-                if self.nursery_size_still_available >= 0:
+                # If 'new_young_objects_size' is not greater than this
+                # expanded 'nursery_size', then we are done: we can just
+                # continue filling the nursery.
+                if self.new_young_objects_size <= self.nursery_limit:
+                    debug_stop("gc-nursery-full")
                     return
             #
             # Else, we trigger the next minor collection now.
+            self.flagged_objects.append(newest_obj)
             self._start_minor_collection()
             #
-            # Now there is no new object left.  Reset the nursery size to
-            # be at most 25% of total_memory_size, and initially no more than
-            # 3/4*total_memory_size - old_objects_size.  If that value is not
-            # positive, then we immediately go into major collection mode.
+            # Now there is no new object left.
+            ll_assert(self.new_young_objects_size == r_uint(0),
+                      "new object left behind?")
+            #
+            # Reset the nursery size to be at most 25% of
+            # total_memory_size, and initially no more than
+            # 3/4*total_memory_size - old_objects_size.  If that value
+            # is not positive, then we immediately go into major
+            # collection mode.
             three_quarters = (self.total_memory_size >> 2) * 3
             if self.old_objects_size < three_quarters:
                 newsize = three_quarters - self.old_objects_size
                 newsize = min(newsize, self.total_memory_size >> 2)
-                self.nursery_size = newsize
-                self.nursery_size_still_available = intmask(newsize)
-                debug_print("nursery size:", self.nursery_size)
+                self.nursery_limit = newsize
                 debug_print("total memory size:", self.total_memory_size)
+                debug_print("initial nursery limit:", self.nursery_limit)
+                debug_stop("gc-nursery-full")
                 return
 
         # The previous collection is likely not finished yet.
         # At this point we want a full collection to occur.
-        debug_start("gc-major")
+        debug_print("starting a major collection")
         #
         # We have to first wait for the previous minor collection to finish:
         self.wait_for_the_end_of_collection()
         #
         # Start the major collection.
-        self._start_major_collection()
+        self._start_major_collection(newest_obj)
         #
-        debug_stop("gc-major")
+        debug_stop("gc-nursery-full")
 
 
     def wait_for_the_end_of_collection(self):
@@ -451,23 +454,27 @@
             self.stop_collection(wait=True)
 
 
-    def stop_collection(self, wait=False):
+    def previous_collection_finished(self):
+        return self.collector.running == 0 or self.stop_collection(wait=False)
+
+
+    def stop_collection(self, wait):
         ll_assert(self.collector.running != 0, "stop_collection: running == 0")
         #
+        debug_start("gc-stop")
         major_collection = (self.collector.major_collection_phase == 2)
-        debug_start("gc-stop")
-        try:
-            debug_print("wait:", int(wait))
-            if major_collection:
-                debug_print("ending a major collection")
-            if wait or major_collection:
-                self.acquire(self.finished_lock)
-            else:
-                if not self.try_acquire(self.finished_lock):
-                    return False
-        finally:
-            debug_print("old objects size:", self.old_objects_size)
-            debug_stop("gc-stop")
+        if major_collection or wait:
+            debug_print("waiting for the end of collection, major =",
+                        int(major_collection))
+            self.acquire(self.finished_lock)
+        else:
+            if not self.try_acquire(self.finished_lock):
+                debug_print("minor collection not finished!")
+                debug_stop("gc-stop")
+                return False
+        #
+        debug_print("old objects size:", self.old_objects_size)
+        debug_stop("gc-stop")
         self.collector.running = 0
         #debug_print("collector.running = 0")
         #
@@ -513,8 +520,8 @@
     def collect(self, gen=4):
         debug_start("gc-forced-collect")
         self.wait_for_the_end_of_collection()
-        self._start_major_collection()
-        self.nursery_full(0)
+        self._start_major_collection(llmemory.NULL)
+        self.wait_for_the_end_of_collection()
         self.execute_finalizers_ll()
         debug_stop("gc-forced-collect")
         return
@@ -541,7 +548,7 @@
 
     def _start_minor_collection(self, major_collection_phase=0):
         #
-        debug_start("gc-start")
+        debug_start("gc-minor-start")
         #
         # Scan the stack roots and the refs in non-GC objects
         self.root_walker.walk_roots(
@@ -575,19 +582,22 @@
         # Copy a few 'mutator' fields to 'collector' fields
         self.collector.aging_objects = self.new_young_objects
         self.new_young_objects = self.NULL
+        self.new_young_objects_size = r_uint(0)
         #self.collect_weakref_pages = self.weakref_pages
         #self.collect_finalizer_pages = self.finalizer_pages
         #
         # Start the collector thread
         self._start_collection_common(major_collection_phase)
         #
-        debug_stop("gc-start")
+        debug_stop("gc-minor-start")
 
-    def _start_major_collection(self):
+    def _start_major_collection(self, newest_obj):
         #
         debug_start("gc-major-collection")
         #
         # Force a minor collection's marking step to occur now
+        if newest_obj:
+            self.flagged_objects.append(newest_obj)
         self._start_minor_collection(major_collection_phase=1)
         #
         # Wait for it to finish
@@ -600,6 +610,10 @@
         ll_assert(self.new_young_objects == self.NULL,
                   "new_young_obejcts should be empty here")
         #
+        # Keep this newest_obj alive
+        if newest_obj:
+            self.collector.gray_objects.append(newest_obj)
+        #
         # Scan again the stack roots and the refs in non-GC objects
         self.root_walker.walk_roots(
             ConcurrentGenGC._add_stack_root,  # stack roots
@@ -621,12 +635,10 @@
         self.collector.delayed_aging_objects = self.collector.aging_objects
         self.collector.aging_objects = self.old_objects
         self.old_objects = self.NULL
+        self.old_objects_size = r_uint(0)
         #self.collect_weakref_pages = self.weakref_pages
         #self.collect_finalizer_pages = self.finalizer_pages
         #
-        # Now there are no more old objects
-        self.old_objects_size = r_uint(0)
-        #
         # Start again the collector thread
         self._start_collection_common(major_collection_phase=2)
         #
@@ -647,7 +659,8 @@
         # NB. it's ok to edit 'gray_objects' from the mutator thread here,
         # because the collector thread is not running yet
         obj = root.address[0]
-        #debug_print("_add_stack_root", obj)
+        debug_print("_add_stack_root", obj)
+        assert 'DEAD' not in repr(obj)
         self.get_mark(obj)
         self.collector.gray_objects.append(obj)
 
@@ -672,19 +685,28 @@
 
     def debug_check_lists(self):
         # just check that they are correct, non-infinite linked lists
-        self.debug_check_list(self.new_young_objects)
-        self.debug_check_list(self.old_objects)
+        self.debug_check_list(self.new_young_objects,
+                              self.new_young_objects_size)
+        self.debug_check_list(self.old_objects, self.old_objects_size)
 
-    def debug_check_list(self, list):
+    def debug_check_list(self, list, totalsize):
         previous = self.NULL
         count = 0
+        size = r_uint(0)
+        size_gc_header = self.gcheaderbuilder.size_gc_header
         while list != self.NULL:
-            # prevent constant-folding, and detects loops
+            obj = llmemory.cast_ptr_to_adr(list) + size_gc_header
+            size1 = size_gc_header + self.get_size(obj)
+            print "debug:", llmemory.raw_malloc_usage(size1)
+            size += llmemory.raw_malloc_usage(size1)
+            # detect loops
             ll_assert(list != previous, "loop!")
             count += 1
             if count & (count-1) == 0:    # only on powers of two, to
                 previous = list           # detect loops of any size
             list = list.next
+        print "\tTOTAL:", size
+        ll_assert(size == totalsize, "bogus total size in linked list")
         return count
 
     def acquire(self, lock):
@@ -890,14 +912,12 @@
 
 
     def collector_mark(self):
-        surviving_size = r_uint(0)
-        #
         while True:
             #
             # Do marking.  The following function call is interrupted
             # if the mutator's write barrier adds new objects to
             # 'extra_objects_to_mark'.
-            surviving_size += self._collect_mark()
+            self._collect_mark()
             #
             # Move the objects from 'extra_objects_to_mark' to
             # 'gray_objects'.  This requires the mutex lock.
@@ -923,31 +943,11 @@
             # Else release mutex_lock and try again.
             self.release(self.mutex_lock)
         #
-        # When we sweep during minor collections, we add the size of
-        # the surviving now-old objects to the following field.  Note
-        # that the write barrier may make objects young again, without
-        # decreasing the value here.  During the following minor
-        # collection this variable will be increased *again*.  When the
-        # write barrier triggers on an aging object, it is random whether
-        # its size ends up being accounted here or not --- but it will
-        # be at the following minor collection, because the object is
-        # young again.  So, careful about overflows.
-        if surviving_size > self.gc.total_memory_size:
-            debug_print("surviving_size too large!",
-                        surviving_size, self.gc.total_memory_size)
-            ll_assert(False, "surviving_size too large")
-        limit = self.gc.total_memory_size - surviving_size
-        if self.gc.old_objects_size <= limit:
-            self.gc.old_objects_size += surviving_size
-        else:
-            self.gc.old_objects_size = self.gc.total_memory_size
-        #
         self.running = 2
         #debug_print("collection_running = 2")
         self.release(self.mutex_lock)
 
     def _collect_mark(self):
-        surviving_size = r_uint(0)
         extra_objects_to_mark = self.gc.extra_objects_to_mark
         cam = self.current_aging_marker
         com = self.current_old_marker
@@ -956,9 +956,6 @@
             if self.get_mark(obj) != cam:
                 continue
             #
-            # Record the object's size
-            surviving_size += raw_malloc_usage(self.gc.get_size(obj))
-            #
             # Scan the content of 'obj'.  We use a snapshot-at-the-
             # beginning order, meaning that we want to scan the state
             # of the object as it was at the beginning of the current
@@ -980,6 +977,7 @@
             # we scan a modified content --- and the original content
             # is never scanned.
             #
+            debug_print("mark:", obj)
             self.gc.trace(obj, self._collect_add_pending, None)
             self.set_mark(obj, com)
             #
@@ -991,8 +989,6 @@
             # reference further objects that will soon be accessed too.
             if extra_objects_to_mark.non_empty():
                 break
-        #
-        return surviving_size
 
     def _collect_add_pending(self, root, ignored):
         obj = root.address[0]
@@ -1004,10 +1000,12 @@
 
     def collector_sweep(self):
         if self.major_collection_phase != 1:  # no sweeping during phase 1
+            self.update_size = self.gc.old_objects_size
             lst = self._collect_do_sweep(self.aging_objects,
                                          self.current_aging_marker,
                                          self.gc.old_objects)
             self.gc.old_objects = lst
+            self.gc.old_objects_size = self.update_size
         #
         self.running = -1
         #debug_print("collection_running = -1")
@@ -1017,6 +1015,7 @@
             # Finish the delayed sweep from the previous minor collection.
             # The objects left unmarked were left with 'cam', which is
             # now 'com' because we switched their values.
+            self.update_size = r_uint(0)
             lst = self._collect_do_sweep(self.delayed_aging_objects,
                                          self.current_old_marker,
                                          self.aging_objects)
@@ -1024,6 +1023,7 @@
             self.delayed_aging_objects = self.NULL
 
     def _collect_do_sweep(self, hdr, still_not_marked, linked_list):
+        size_gc_header = self.gc.gcheaderbuilder.size_gc_header
         #
         while hdr != self.NULL:
             nexthdr = hdr.next
@@ -1031,6 +1031,7 @@
             if mark == still_not_marked:
                 # the object is still not marked.  Free it.
                 blockadr = llmemory.cast_ptr_to_adr(hdr)
+                debug_print("free:", blockadr + size_gc_header)
                 blockadr = llarena.getfakearenaaddress(blockadr)
                 llarena.arena_free(blockadr)
                 #
@@ -1043,6 +1044,11 @@
                 hdr.next = linked_list
                 linked_list = hdr
                 #
+                # count its size
+                obj = llmemory.cast_ptr_to_adr(hdr) + size_gc_header
+                size1 = size_gc_header + self.gc.get_size(obj)
+                self.update_size += llmemory.raw_malloc_usage(size1)
+                #
             hdr = nexthdr
         #
         return linked_list


More information about the pypy-commit mailing list