[pypy-commit] pypy use-mmap: Trying to use mmap() instead of malloc() to get arenas. Advantage: munmap()
arigo
pypy.commits at gmail.com
Wed Jul 6 11:57:12 EDT 2016
Author: Armin Rigo <arigo at tunes.org>
Branch: use-mmap
Changeset: r85579:f63cad3be093
Date: 2016-07-06 16:43 +0200
http://bitbucket.org/pypy/pypy/changeset/f63cad3be093/
Log: Trying to use mmap() instead of malloc() to get arenas. Advantage:
munmap() really returns the 256K/512K of memory to the system.
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
@@ -727,9 +727,10 @@
return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF)
- def collect(self, gen=2):
+ def collect(self, gen=3):
"""Do a minor (gen=0), start a major (gen=1), or do a full
- major (gen>=2) collection."""
+ major (gen>=2) collection. If gen>=3 then ask minimarkpage
+ to free now its cache of arenas."""
if gen < 0:
self._minor_collection() # dangerous! no major GC cycle progress
elif gen <= 1:
@@ -738,6 +739,8 @@
self.major_collection_step()
else:
self.minor_and_major_collection()
+ if gen >= 3:
+ self.ac.kill_dying_arenas()
self.rrc_invoke_callback()
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,6 +124,16 @@
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
+ # arenas that were already in the 'dead_arenas' list at that
+ # point (from the previous major collection) are really
+ # returned to the OS.
+ self.dying_arenas = ARENA_NULL
+ self.dead_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
# not in any 'arenas_lists'. We will consume all its pages before
@@ -244,7 +254,7 @@
def _all_arenas(self):
- """For testing. Enumerates all arenas."""
+ """For testing. Enumerates all not-completely-free arenas."""
if self.current_arena:
yield self.current_arena
for arena in self.arenas_lists:
@@ -290,31 +300,58 @@
for a in self._all_arenas():
assert a.nfreepages == 0
#
- # 'arena_base' points to the start of malloced memory; it might not
- # be a page-aligned address
- arena_base = llarena.arena_malloc(self.arena_size, False)
- if not arena_base:
- out_of_memory("out of memory: couldn't allocate the next arena")
- arena_end = arena_base + self.arena_size
+ # Maybe we have a dying or a dead 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
+ else:
+ #
+ # 'arena_base' points to the start of malloced memory. It might
+ # not be a page-aligned address depending on the underlying call
+ # (although with mmap() it should be).
+ arena_base = llarena.arena_mmap(self.arena_size)
+ if not arena_base:
+ out_of_memory("out of memory: couldn't allocate the next arena")
+ arena_end = arena_base + self.arena_size
+ #
+ # 'firstpage' points to the first unused page
+ firstpage = start_of_page(arena_base + self.page_size - 1,
+ self.page_size)
+ # 'npages' is the number of full pages just allocated
+ npages = (arena_end - firstpage) // self.page_size
+ #
+ # Allocate an ARENA object and initialize it
+ arena = lltype.malloc(ARENA, flavor='raw', track_allocation=False)
+ arena.base = arena_base
+ arena.totalpages = npages
#
- # 'firstpage' points to the first unused page
- firstpage = start_of_page(arena_base + self.page_size - 1,
- self.page_size)
- # 'npages' is the number of full pages just allocated
- npages = (arena_end - firstpage) // self.page_size
- #
- # Allocate an ARENA object and initialize it
- arena = lltype.malloc(ARENA, flavor='raw', track_allocation=False)
- arena.base = arena_base
arena.nfreepages = 0 # they are all uninitialized pages
- arena.totalpages = npages
- arena.freepages = firstpage
- self.num_uninitialized_pages = npages
+ arena.freepages = start_of_page(arena.base + self.page_size - 1,
+ self.page_size)
+ arena.nextarena = ARENA_NULL
+ self.num_uninitialized_pages = arena.totalpages
self.current_arena = arena
#
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.
+ """
+ arena = self.dead_arenas
+ while arena:
+ nextarena = arena.nextarena
+ llarena.arena_munmap(arena.base, self.arena_size)
+ lltype.free(arena, flavor='raw', track_allocation=False)
+ arena = nextarena
+ self.dead_arenas = self.dying_arenas
+ self.dying_arenas = ARENA_NULL
+
+
def mass_free_prepare(self):
"""Prepare calls to mass_free_incremental(): moves the chained lists
into 'self.old_xxx'.
@@ -360,6 +397,7 @@
if size_class >= 0:
self._rehash_arenas_lists()
self.size_class_with_old_pages = -1
+ self.kill_dying_arenas()
#
return True
@@ -394,9 +432,12 @@
#
if arena.nfreepages == arena.totalpages:
#
- # The whole arena is empty. Free it.
- llarena.arena_free(arena.base)
- lltype.free(arena, flavor='raw', track_allocation=False)
+ # The whole arena is empty. Move it to the dying list.
+ arena.nextarena = self.dying_arenas
+ self.dying_arenas = arena
+ llarena.arena_reset(arena.base,
+ llarena.RESET_WHOLE_ARENA,
+ 0)
#
else:
# Insert 'arena' in the correct arenas_lists[n]
diff --git a/rpython/rlib/rmmap.py b/rpython/rlib/rmmap.py
--- a/rpython/rlib/rmmap.py
+++ b/rpython/rlib/rmmap.py
@@ -691,6 +691,22 @@
m.setdata(res, map_size)
return m
+ def allocate_memory_chunk(map_size):
+ # used by the memory allocator (in llarena.py, from minimarkpage.py)
+ flags = MAP_PRIVATE | MAP_ANONYMOUS
+ prot = PROT_READ | PROT_WRITE
+ if we_are_translated():
+ flags = NonConstant(flags)
+ prot = NonConstant(prot)
+ res = c_mmap_safe(rffi.cast(PTR, 0), map_size, prot, flags, -1, 0)
+ if res == rffi.cast(PTR, -1):
+ res = rffi.cast(PTR, 0)
+ return res
+
+ def free_memory_chunk(addr, map_size):
+ # used by the memory allocator (in llarena.py, from minimarkpage.py)
+ c_munmap_safe(addr, map_size)
+
def alloc_hinted(hintp, map_size):
flags = MAP_PRIVATE | MAP_ANONYMOUS
prot = PROT_EXEC | PROT_READ | PROT_WRITE
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
@@ -13,13 +13,15 @@
# it internally uses raw_malloc_usage() to estimate the number of bytes
# it needs to reserve.
+RESET_WHOLE_ARENA = -909
+
class ArenaError(Exception):
pass
class Arena(object):
_count_arenas = 0
- def __init__(self, nbytes, zero):
+ def __init__(self, nbytes, zero, mmaped=False):
Arena._count_arenas += 1
self._arena_index = Arena._count_arenas
self.nbytes = nbytes
@@ -29,13 +31,14 @@
self.freed = False
self.protect_inaccessible = None
self.reset(zero)
+ self.mmaped = mmaped
def __repr__(self):
return '<Arena #%d [%d bytes]>' % (self._arena_index, self.nbytes)
- def reset(self, zero, start=0, size=None):
+ def reset(self, zero, start=0, size=RESET_WHOLE_ARENA):
self.check()
- if size is None:
+ if type(size) is int and size == RESET_WHOLE_ARENA:
stop = self.nbytes
else:
stop = start + llmemory.raw_malloc_usage(size)
@@ -315,18 +318,33 @@
# 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.
+
def arena_malloc(nbytes, zero):
"""Allocate and return a new arena, optionally zero-initialized."""
return Arena(nbytes, zero).getaddr(0)
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
arena_addr.arena.reset(False)
assert not arena_addr.arena.objectptrs
arena_addr.arena.mark_freed()
+def arena_mmap(nbytes):
+ """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_reset(arena_addr, size, zero):
"""Free all objects in the arena, which can then be reused.
This can also be used on a subrange of the arena.
@@ -335,6 +353,7 @@
* 1: clear, optimized for a very large area of memory
* 2: clear, optimized for a small or medium area of memory
* 3: fill with garbage
+ If 'zero' is set to 0, then size can be RESET_WHOLE_ARENA.
"""
arena_addr = getfakearenaaddress(arena_addr)
arena_addr.arena.reset(zero, arena_addr.offset, size)
@@ -503,6 +522,26 @@
llfakeimpl=arena_free,
sandboxsafe=True)
+def llimpl_arena_mmap(nbytes):
+ from rpython.rlib import rmmap
+ return rffi.cast(llmemory.Address, rmmap.allocate_memory_chunk(nbytes))
+
+def llimpl_arena_munmap(addr, nbytes):
+ from rpython.rlib import rmmap
+ rmmap.free_memory_chunk(rffi.cast(rmmap.PTR, addr), nbytes)
+
+register_external(arena_mmap, [int], llmemory.Address,
+ 'll_arena.arena_mmap',
+ llimpl=llimpl_arena_mmap,
+ llfakeimpl=arena_mmap,
+ sandboxsafe=True)
+
+register_external(arena_munmap, [llmemory.Address, int], None,
+ 'll_arena.arena_munmap',
+ llimpl=llimpl_arena_munmap,
+ llfakeimpl=arena_munmap,
+ sandboxsafe=True)
+
def llimpl_arena_reset(arena_addr, size, zero):
if zero:
if zero == 1:
More information about the pypy-commit
mailing list