Author: Armin Rigo <[email protected]>
Branch: use-mmap
Changeset: r85661:9247cf241910
Date: 2016-07-11 20:47 +0200
http://bitbucket.org/pypy/pypy/changeset/9247cf241910/

Log:    Trying a slightly different approach with "mreset" instead of
        munmap. This should avoid all fragmentation of the 256-KB mmaped
        regions by not returning them to the OS, but returning only the
        memory within.

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
@@ -739,9 +739,9 @@
                 self.major_collection_step()
         else:
             self.minor_and_major_collection()
-            if gen >= 3:
-                self.ac.kill_dying_arenas()
         self.rrc_invoke_callback()
+        if gen >= 3:
+            self.ac.mreset_dead_arenas()
 
 
     def minor_collection_with_major_progress(self, extrasize=0):
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
@@ -634,11 +634,13 @@
         return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
 
 
-    def collect(self, gen=1):
+    def collect(self, gen=2):
         """Do a minor (gen=0) or major (gen>0) collection."""
         self.minor_collection()
         if gen > 0:
             self.major_collection()
+            if gen > 1:
+                self.ac.mreset_dead_arenas()
 
     def move_nursery_top(self, totalsize):
         size = self.nursery_cleanup
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
@@ -124,20 +124,31 @@
                                               flavor='raw', zero=True,
                                               immortal=True)
         #
-        # two lists of completely free arenas.  These arenas are
-        # pending to be returned to the OS.  They are first added to
-        # the 'dying_arenas' list when the major collection runs.  At
-        # the end, they are moved to the 'dead_arenas' list, and all
+        # three lists of completely free arenas, classified in terms of
+        # their age, measured as the number of "end major collection"
+        # since they are completely free:
+        #
+        # * 'dying_arenas': age = 0
+        # * 'dead_arenas': age = 1
+        # * 'mreset_arenas': age >= 2
+        #
+        # The 'mreset_arenas' have been returned to the OS with
+        # llarena.arena_mreset().
+        #
+        # The idea is that free arenas are first added to the
+        # 'dying_arenas' list when the major collection runs.  At the
+        # end, they are moved to the 'dead_arenas' list, and all
         # arenas that were already in the 'dead_arenas' list at that
         # point (from the previous major collection) are really
-        # returned to the OS.
-        #   Memory usage goes down quickly during the incremental
-        # major collection and up slowly the rest of the time.  The
-        # point of these two lists is to avoid constantly freeing and
+        # returned to the OS, and moved to the 'mreset_arenas' list.
+        # Memory usage goes down quickly during the incremental major
+        # collection and up slowly the rest of the time.  The point of
+        # these three lists is to avoid constantly freeing and
         # re-allocating arenas: we return to the OS the arenas that
         # have been unused for a complete cycle already.
         self.dying_arenas = ARENA_NULL
         self.dead_arenas = ARENA_NULL
+        self.mreset_arenas = ARENA_NULL
         #
         # the arena currently consumed; it must have at least one page
         # available, or be NULL.  The arena object that we point to is
@@ -305,13 +316,16 @@
             for a in self._all_arenas():
                 assert a.nfreepages == 0
         #
-        # Maybe we have a dying or a dead arena.
+        # Maybe we have a dying or a dead or a mreset arena.
         if self.dying_arenas:
             arena = self.dying_arenas
             self.dying_arenas = arena.nextarena
         elif self.dead_arenas:
             arena = self.dead_arenas
             self.dead_arenas = arena.nextarena
+        elif self.mreset_arenas:
+            arena = self.mreset_arenas
+            self.mreset_arenas = arena.nextarena
         else:
             #
             # 'arena_base' points to the start of malloced memory.  It might
@@ -343,16 +357,20 @@
     allocate_new_arena._dont_inline_ = True
 
 
-    def kill_dying_arenas(self):
-        """Return to the OS all dead arenas, and then move the 'dying'
-        arenas to the 'dead' arenas list.
+    def mreset_dead_arenas(self):
+        """Return to the OS all 'dead' arenas and move them to the
+        'mreset' arenas list, and then move the 'dying' arenas to the
+        'dead' arenas list.
         """
         arena = self.dead_arenas
+        mreset_head = self.mreset_arenas
         while arena:
             nextarena = arena.nextarena
-            llarena.arena_munmap(arena.base, self.arena_size)
-            lltype.free(arena, flavor='raw', track_allocation=False)
+            llarena.arena_mreset(arena.base, self.arena_size)
+            arena.nextarena = mreset_head
+            mreset_head = arena
             arena = nextarena
+        self.mreset_arenas = mreset_head
         self.dead_arenas = self.dying_arenas
         self.dying_arenas = ARENA_NULL
 
@@ -402,7 +420,7 @@
         if size_class >= 0:
             self._rehash_arenas_lists()
             self.size_class_with_old_pages = -1
-            self.kill_dying_arenas()
+            self.mreset_dead_arenas()
         #
         return True
 
diff --git a/rpython/memory/gc/minimarktest.py 
b/rpython/memory/gc/minimarktest.py
--- a/rpython/memory/gc/minimarktest.py
+++ b/rpython/memory/gc/minimarktest.py
@@ -57,5 +57,5 @@
         res = self.mass_free_incremental(ok_to_free_func, sys.maxint)
         assert res
 
-    def kill_dying_arenas(self):
+    def mreset_dead_arenas(self):
         pass
diff --git a/rpython/memory/gc/test/test_minimarkpage.py 
b/rpython/memory/gc/test/test_minimarkpage.py
--- a/rpython/memory/gc/test/test_minimarkpage.py
+++ b/rpython/memory/gc/test/test_minimarkpage.py
@@ -421,12 +421,13 @@
         print '-' * 80
         ac.__class__.allocate_new_arena(ac)
         a = ac.current_arena.base.arena
-        def my_mark_freed():
-            a.freed = True
+        def my_mreset():
+            a.__class__.mreset(a)
             DoneTesting.counter += 1
             if DoneTesting.counter > 3:
                 raise DoneTesting
-        a.mark_freed = my_mark_freed
+        a.mark_freed = "not callable"
+        a.mreset = my_mreset
     ac.allocate_new_arena = my_allocate_new_arena
 
     def allocate_object(live_objects):
diff --git a/rpython/rlib/rmmap.py b/rpython/rlib/rmmap.py
--- a/rpython/rlib/rmmap.py
+++ b/rpython/rlib/rmmap.py
@@ -76,7 +76,8 @@
     constant_names = ['PAGE_READONLY', 'PAGE_READWRITE', 'PAGE_WRITECOPY',
                       'FILE_MAP_READ', 'FILE_MAP_WRITE', 'FILE_MAP_COPY',
                       'DUPLICATE_SAME_ACCESS', 'MEM_COMMIT', 'MEM_RESERVE',
-                      'MEM_RELEASE', 'PAGE_EXECUTE_READWRITE', 'PAGE_NOACCESS']
+                      'MEM_RELEASE', 'PAGE_EXECUTE_READWRITE', 'PAGE_NOACCESS',
+                      'MEM_RESET']
     for name in constant_names:
         setattr(CConfig, name, rffi_platform.ConstantInteger(name))
 
@@ -703,9 +704,17 @@
             res = rffi.cast(PTR, 0)
         return res
 
-    def free_memory_chunk(addr, map_size):
+    def reset_memory_chunk(addr, map_size):
         # used by the memory allocator (in llarena.py, from minimarkpage.py)
-        c_munmap_safe(addr, rffi.cast(size_t, map_size))
+        # to release the memory currently held in a memory chunk, possibly
+        # zeroing it, but keep that memory chunk available for future use.
+        # should only be used on allocate_memory_chunk() memory.
+        flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED
+        prot = PROT_READ | PROT_WRITE
+        if we_are_translated():
+            flags = NonConstant(flags)
+            prot = NonConstant(prot)
+        c_mmap_safe(rffi.cast(PTR, addr), map_size, prot, flags, -1, 0)
 
     def alloc_hinted(hintp, map_size):
         flags = MAP_PRIVATE | MAP_ANONYMOUS
@@ -912,10 +921,12 @@
         case of a sandboxed process
         """
         null = lltype.nullptr(rffi.VOIDP.TO)
+        flags = MEM_COMMIT | MEM_RESERVE
         prot = PAGE_EXECUTE_READWRITE
         if we_are_translated():
+            flags = NonConstant(flags)
             prot = NonConstant(prot)
-        res = VirtualAlloc_safe(null, map_size, MEM_COMMIT | MEM_RESERVE, prot)
+        res = VirtualAlloc_safe(null, map_size, flags, prot)
         if not res:
             raise MemoryError
         arg = lltype.malloc(LPDWORD.TO, 1, zero=True, flavor='raw')
@@ -931,12 +942,19 @@
     def allocate_memory_chunk(map_size):
         # used by the memory allocator (in llarena.py, from minimarkpage.py)
         null = lltype.nullptr(rffi.VOIDP.TO)
+        flags = MEM_COMMIT | MEM_RESERVE
         prot = PAGE_READWRITE
         if we_are_translated():
+            flags = NonConstant(flags)
             prot = NonConstant(prot)
-        res = VirtualAlloc_safe(null, map_size, MEM_COMMIT | MEM_RESERVE, prot)
+        res = VirtualAlloc_safe(null, map_size, flags, prot)
         return res
 
-    def free_memory_chunk(addr, map_size):
+    def reset_memory_chunk(addr, map_size):
         # used by the memory allocator (in llarena.py, from minimarkpage.py)
-        VirtualFree_safe(addr, 0, MEM_RELEASE)
+        flags = MEM_RESET
+        prot = PAGE_READWRITE
+        if we_are_translated():
+            flags = NonConstant(flags)
+            prot = NonConstant(prot)
+        VirtualAlloc_safe(rffi.cast(rffi.VOIDP, addr), map_size, flags, prot)
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
@@ -133,6 +133,11 @@
     def mark_freed(self):
         self.freed = True    # this method is a hook for tests
 
+    def mreset(self):        # this method is a hook for tests
+        assert self.mmaped
+        self.reset(False)
+        assert not self.objectptrs
+
     def set_protect(self, inaccessible):
         if inaccessible:
             assert self.protect_inaccessible is None
@@ -318,8 +323,9 @@
 # arena_new_view(ptr) is a no-op when translated, returns fresh view
 # on previous arena when run on top of llinterp
 
-# arena_mmap() and arena_munmap() are similar to arena_malloc(zero=True)
-# and arena_free(), but use the OS's mmap() function.
+# arena_mmap() is similar to arena_malloc(zero=True), but uses the
+# OS's mmap() function.  We can then use arena_mreset() on the result
+# to reset memory content (i.e. mark as free, possibly reset to zero)
 
 def arena_malloc(nbytes, zero):
     """Allocate and return a new arena, optionally zero-initialized."""
@@ -327,12 +333,9 @@
 
 def arena_free(arena_addr):
     """Release an arena."""
-    _arena_free(arena_addr, mmaped=False)
-
-def _arena_free(arena_addr, mmaped):
     assert isinstance(arena_addr, fakearenaaddress)
     assert arena_addr.offset == 0
-    assert arena_addr.arena.mmaped == mmaped
+    assert not arena_addr.arena.mmaped
     arena_addr.arena.reset(False)
     assert not arena_addr.arena.objectptrs
     arena_addr.arena.mark_freed()
@@ -341,9 +344,15 @@
     """Allocate a new arena using mmap()."""
     return Arena(nbytes, zero=True, mmaped=True).getaddr(0)
 
-def arena_munmap(arena_addr, original_nbytes):
-    """Release an arena allocated with arena_mmap()."""
-    _arena_free(arena_addr, mmaped=True)
+def arena_mreset(arena_addr, original_nbytes):
+    """Reset a complete arena allocated with arena_mmap().
+    Equivalent to arena_reset(..., zero=0), but really tries
+    to return the memory to the OS until the next access.
+    """
+    assert isinstance(arena_addr, fakearenaaddress)
+    assert arena_addr.offset == 0
+    assert arena_addr.arena.nbytes == original_nbytes
+    arena_addr.arena.mreset()
 
 def arena_reset(arena_addr, size, zero):
     """Free all objects in the arena, which can then be reused.
@@ -526,9 +535,9 @@
     from rpython.rlib import rmmap
     return rffi.cast(llmemory.Address, rmmap.allocate_memory_chunk(nbytes))
 
-def llimpl_arena_munmap(addr, nbytes):
+def llimpl_arena_mreset(addr, nbytes):
     from rpython.rlib import rmmap
-    rmmap.free_memory_chunk(rffi.cast(rmmap.PTR, addr), nbytes)
+    rmmap.reset_memory_chunk(rffi.cast(rmmap.PTR, addr), nbytes)
 
 register_external(arena_mmap, [int], llmemory.Address,
                   'll_arena.arena_mmap',
@@ -536,10 +545,10 @@
                   llfakeimpl=arena_mmap,
                   sandboxsafe=True)
 
-register_external(arena_munmap, [llmemory.Address, int], None,
-                  'll_arena.arena_munmap',
-                  llimpl=llimpl_arena_munmap,
-                  llfakeimpl=arena_munmap,
+register_external(arena_mreset, [llmemory.Address, int], None,
+                  'll_arena.arena_mreset',
+                  llimpl=llimpl_arena_mreset,
+                  llfakeimpl=arena_mreset,
                   sandboxsafe=True)
 
 def llimpl_arena_reset(arena_addr, size, zero):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to