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