Author: Armin Rigo <ar...@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: _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit