Skip to content

Commit 04b9ec1

Browse files
committed
Fully implemented the manager - now the cursor can be implenented as well
1 parent 226f428 commit 04b9ec1

4 files changed

Lines changed: 131 additions & 5 deletions

File tree

smmap/mman.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
MemoryWindow,
44
MappedRegion,
55
MappedRegionList,
6+
is_64_bit,
7+
PAGESIZE
68
)
79

810
from exc import RegionCollectionError
@@ -150,17 +152,113 @@ class MappedMemoryManager(object):
150152

151153
__slots__ = [
152154
'_fdict', # mapping of path -> MappedRegionList
153-
'_max_window_size', # maximum size of a window
155+
'_window_size', # maximum size of a window
154156
'_max_memory_size', # maximum amount ofmemory we may allocate
155-
'_max_handles', # maximum amount of handles to keep open
157+
'_max_handle_count', # maximum amount of handles to keep open
156158
'_memory_size', # currently allocated memory size
157159
'_handle_count', # amount of currently allocated file handles
158160
]
159161

162+
_MB_in_bytes = 1024 * 1024
163+
164+
def __init__(self, window_size = 0, max_memory_size = 0, max_open_handles = ~0):
165+
"""initialize the manager with the given parameters.
166+
:param window_size: if 0, a default window size will be chosen depending on
167+
the operating system's architechture. It will internally be quantified to a multiple of the page size
168+
:param max_memory_size: maximum amount of memory we may map at once before releasing mapped regions.
169+
If 0, a viable default iwll be set dependning on the system's architecture.
170+
:param max_open_handles: if not ~0, lmit the amount of open file handles to the given number.
171+
Otherwise the amount is only limited by the system iteself. If a system or soft limit is hit,
172+
the manager will free as many handles as posisble"""
173+
self._fdict = dict()
174+
self._window_size = window_size
175+
self._max_memory_size = max_memory_size
176+
self._max_handle_count = max_open_handles
177+
self._memory_size = 0
178+
self._handle_count = 0
179+
180+
if window_size == 0:
181+
coeff = 32
182+
if is_64_bit():
183+
coeff = 1024
184+
#END handle arch
185+
self._window_size = coeff * self._MB_in_bytes
186+
# END handle max window size
187+
188+
if max_memory_size == 0:
189+
coeff = 512
190+
if is_64_bit():
191+
coeff = 8192
192+
#END handle arch
193+
self._max_memory_size = coeff * self._MB_in_bytes
194+
#END handle max memory size
195+
160196
def _collect_one_lru_region(self, size):
161197
"""Unmap the region which was least-recently used and has no client
162198
:param size: size of the region we want to map next (assuming its not already mapped partially or full
163199
if 0, we try to free any available region
164200
:raise RegionCollectionError:
165201
:todo: implement a case where all unusued regions are discarded efficiently. Currently its only brute force"""
202+
num_found = 0
203+
while (size == 0) or (self._memory_size + size > self._max_memory_size):
204+
lru_region = None
205+
lru_list = None
206+
for regions in self._fdict.itervalues():
207+
for region in regions:
208+
# check client count - consider that we keep one reference ourselves !
209+
if (region.client_count()-1 == 0 and
210+
(lru_region is None or region.usage_count() < lru_region.usage_count())):
211+
lru_region = region
212+
lru_list = regions
213+
# END update lru_region
214+
#END for each region
215+
#END for each regions list
216+
217+
if lru_region is None:
218+
if num_found == 0 and size != 0:
219+
raise RegionCollectionError("Didn't find any region to free")
220+
#END raise if necessary
221+
break
222+
#END handle region not found
223+
224+
num_found += 1
225+
del(lru_list[lru_list.index(lru_region)])
226+
self._memory_size -= lru_region.size()
227+
self._handle_count -= 1
228+
#END while there is more memory to free
229+
230+
#{ Interface
231+
def make_cursor(self, path):
232+
""":return: a cursor pointing to the given path. It can be used to map new regions of the file into memory"""
233+
regions = self._fdict.get(path)
234+
if regions is None:
235+
regions = MappedRegionList(path)
236+
self._fdict[path] = regions
237+
# END obtain region for path
238+
return MemoryCursor(self, regions)
166239

240+
def num_file_handles(self):
241+
""":return: amount of file handles in use. Each mapped region uses one file handle"""
242+
return self._handle_count
243+
244+
def num_open_files(self):
245+
"""Amount of opened files in the system"""
246+
return reduce(lambda x,y: x+y, (1 for rlist in self._fdict.itervalues() if len(rlist) > 0), 0)
247+
248+
def window_size(self):
249+
""":return: size of each window when allocating new regions"""
250+
return self._window_size
251+
252+
def mapped_memory_size(self):
253+
""":return: amount of bytes currently mapped in total"""
254+
return self._memory_size
255+
256+
def max_mapped_memory_size(self):
257+
""":return: maximum amount of memory we may allocate"""
258+
return self._max_memory_size
259+
260+
def page_size(self):
261+
""":return: size of a single memory page in bytes"""
262+
return PAGESIZE
263+
264+
#} END interface

smmap/test/test_mman.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from lib import TestBase, FileCreator
22

3-
from copy import copy
43
from smmap.mman import *
54
from smmap.mman import MemoryCursor
5+
from smmap.util import PAGESIZE
6+
7+
from smmap.exc import RegionCollectionError
8+
9+
import sys
10+
from copy import copy
611

712
class TestMMan(TestBase):
813

@@ -19,4 +24,17 @@ def test_cursor(self):
1924

2025

2126
def test_memory_manager(self):
22-
pass
27+
man = MappedMemoryManager()
28+
assert man.num_file_handles() == 0
29+
assert man.num_open_files() == 0
30+
assert man.window_size() > 0
31+
assert man.mapped_memory_size() == 0
32+
assert man.max_mapped_memory_size() > 0
33+
assert man.page_size() == PAGESIZE
34+
35+
# collection doesn't raise in 'any' mode
36+
man._collect_one_lru_region(0)
37+
# doesn't raise if we are within the limit
38+
man._collect_one_lru_region(10)
39+
# raises outside of limit
40+
self.failUnlessRaises(RegionCollectionError, man._collect_one_lru_region, sys.maxint)

smmap/test/test_util.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,8 @@ def test_region_list(self):
9292
assert ml.path() == fc.path
9393
assert ml.file_size() == fc.size
9494

95+
def test_util(self):
96+
assert isinstance(is_64_bit(), bool) # just call it
97+
assert align_to_page(1, False) == 0
98+
assert align_to_page(1, True) == PAGESIZE
99+

smmap/util.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from mmap import PAGESIZE
77
from sys import getrefcount
88

9-
__all__ = ["align_to_page", "MemoryWindow", "MappedRegion", "MappedRegionList", "PAGESIZE"]
9+
__all__ = [ "align_to_page", "is_64_bit",
10+
"MemoryWindow", "MappedRegion", "MappedRegionList", "PAGESIZE"]
1011

1112
#{ Utilities
1213

@@ -20,6 +21,10 @@ def align_to_page(num, round_up):
2021
res += PAGESIZE;
2122
#END handle size
2223
return res;
24+
25+
def is_64_bit():
26+
""":return: True if the system is 64 bit. Otherwise it can be assumed to be 32 bit"""
27+
return sys.maxint > (1<<32) - 1
2328

2429
#}END utilities
2530

0 commit comments

Comments
 (0)