Commit 60185c00 authored by Guido van Rossum's avatar Guido van Rossum

Rewrite the simple free list allocator using Tim's suggestion of

keeping track of the beginning and end address of all free blocks, to
make merging of adjacent blocks a breeze.  The free list is no longer
kept sorted order.

Unfortunately, this still performs poorly.  It has a higher hit rate
(with the 38 minute trace I'm using; the longer traces take too long)
than the buddy system allocator with the same arena size, it is *much*
slower, the memory usage is under 20% (!!!), and despite a roving
pointer, the allocation loop is taken an average of 692 times per
allocation.  At the end of the simulation the free list contains 1039
blocks; I presume this is a steady state approximating the average
behavior.  (There are 2280 allocated blocks at that point, roughly
confirming Knuth's "50% rule" and suggesting that the block merge code
works properly.)

I guess the next idea is separate free lists per size.
parent be27fc06
...@@ -539,20 +539,21 @@ class SimpleAllocator: ...@@ -539,20 +539,21 @@ class SimpleAllocator:
self.rover = self.avail self.rover = self.avail
node = BlockNode(None, arenasize, 0) node = BlockNode(None, arenasize, 0)
node.linkbefore(self.avail) node.linkbefore(self.avail)
self.taglo = {0: node}
self.taghi = {arenasize: node}
# Allocator statistics # Allocator statistics
self.nallocs = 0 self.nallocs = 0
self.nfrees = 0 self.nfrees = 0
self.allocloops = 0 self.allocloops = 0
self.freeloops = 0
self.freebytes = arenasize self.freebytes = arenasize
self.freeblocks = 1 self.freeblocks = 1
self.allocbytes = 0 self.allocbytes = 0
self.allocblocks = 0 self.allocblocks = 0
def report(self): def report(self):
print ("NA=%d AL=%d NF=%d FL=%d ABy=%d ABl=%d FBy=%d FBl=%d" % print ("NA=%d AL=%d NF=%d ABy=%d ABl=%d FBy=%d FBl=%d" %
(self.nallocs, self.allocloops, (self.nallocs, self.allocloops,
self.nfrees, self.freeloops, self.nfrees,
self.allocbytes, self.allocblocks, self.allocbytes, self.allocblocks,
self.freebytes, self.freeblocks)) self.freebytes, self.freeblocks))
...@@ -560,29 +561,30 @@ class SimpleAllocator: ...@@ -560,29 +561,30 @@ class SimpleAllocator:
self.nallocs += 1 self.nallocs += 1
# First fit algorithm # First fit algorithm
rover = stop = self.rover rover = stop = self.rover
free = None
while 1: while 1:
self.allocloops += 1 self.allocloops += 1
if rover.size >= size: if rover.size >= size:
if rover.size == size:
self.rover = rover.next
rover.unlink()
self.freeblocks -= 1
self.allocblocks += 1
self.freebytes -= size
self.allocbytes += size
return rover
free = rover
break break
rover = rover.next rover = rover.next
if rover is stop: if rover is stop:
break return None # We went round the list without finding space
if free is None: # We ran out of space if rover.size == size:
return None self.rover = rover.next
# Take space from the end of the roving pointer rover.unlink()
assert free.size > size del self.taglo[rover.addr]
node = BlockNode(None, size, free.addr + free.size - size) del self.taghi[rover.addr + size]
free.size -= size self.freeblocks -= 1
self.allocblocks += 1
self.freebytes -= size
self.allocbytes += size
return rover
# Take space from the beginning of the roving pointer
assert rover.size > size
node = BlockNode(None, size, rover.addr)
del self.taglo[rover.addr]
rover.size -= size
rover.addr += size
self.taglo[rover.addr] = rover
#self.freeblocks += 0 # No change here #self.freeblocks += 0 # No change here
self.allocblocks += 1 self.allocblocks += 1
self.freebytes -= size self.freebytes -= size
...@@ -591,32 +593,37 @@ class SimpleAllocator: ...@@ -591,32 +593,37 @@ class SimpleAllocator:
def free(self, node): def free(self, node):
self.nfrees += 1 self.nfrees += 1
self.freeblocks += 1
self.allocblocks -= 1
self.freebytes += node.size self.freebytes += node.size
self.allocbytes -= node.size self.allocbytes -= node.size
self.allocblocks -= 1 node.linkbefore(self.avail)
x = self.avail.next self.taglo[node.addr] = node
while x is not self.avail and x.addr < node.addr: self.taghi[node.addr + node.size] = node
self.freeloops += 1 x = self.taghi.get(node.addr)
x = x.next if x is not None:
if node.addr + node.size == x.addr: # Merge with next # Merge x into node
x.addr -= node.size x.unlink()
x.size += node.size self.freeblocks -= 1
node = x del self.taglo[x.addr]
else: # Insert new node into free list del self.taghi[x.addr + x.size]
node.linkbefore(x) del self.taglo[node.addr]
self.freeblocks += 1 node.addr = x.addr
x = node.prev node.size += x.size
if node.addr == x.addr + x.size and x is not self.avail: self.taglo[node.addr] = node
# Merge with previous node in free list x = self.taglo.get(node.addr + node.size)
node.unlink() if x is not None:
x.size += node.size # Merge x into node
node = x x.unlink()
self.freeblocks -= 1 self.freeblocks -= 1
del self.taglo[x.addr]
del self.taghi[x.addr + x.size]
del self.taghi[node.addr + node.size]
node.size += x.size
self.taghi[node.addr + node.size] = node
# It's possible that either one of the merges above invalidated # It's possible that either one of the merges above invalidated
# the rover. # the rover.
# It's simplest to simply reset the rover to the newly freed block. # It's simplest to simply reset the rover to the newly freed block.
# It also seems optimal; if I only move the rover when it's
# become invalid, there performance goes way down.
self.rover = node self.rover = node
def dump(self, msg=""): def dump(self, msg=""):
...@@ -654,6 +661,7 @@ def testallocator(factory=BuddyAllocator): ...@@ -654,6 +661,7 @@ def testallocator(factory=BuddyAllocator):
while queue and queue[0][0] <= T: while queue and queue[0][0] <= T:
time, node = heapq.heappop(queue) time, node = heapq.heappop(queue)
assert time == T assert time == T
##print "free addr=%d, size=%d" % (node.addr, node.size)
cache.free(node) cache.free(node)
blocks -= 1 blocks -= 1
size = random.randint(100, 2000) size = random.randint(100, 2000)
...@@ -664,6 +672,7 @@ def testallocator(factory=BuddyAllocator): ...@@ -664,6 +672,7 @@ def testallocator(factory=BuddyAllocator):
cache.dump("T=%4d: %d blocks;" % (T, blocks)) cache.dump("T=%4d: %d blocks;" % (T, blocks))
break break
else: else:
##print "alloc addr=%d, size=%d" % (node.addr, node.size)
blocks += 1 blocks += 1
heapq.heappush(queue, (T + lifetime, node)) heapq.heappush(queue, (T + lifetime, node))
T = T+1 T = T+1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment