Skip to content

Commit 2156e9a

Browse files
committed
Implemented Window including test. Started Region implementation as well as test, but noticed that a critical feature, the mmap's offset, doesn't exist prior to python 2.6. Its total crap, so is python
1 parent b75a09b commit 2156e9a

4 files changed

Lines changed: 219 additions & 6 deletions

File tree

README.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ For convenience, a stream class is provided which hides the usage of the memory
1515
************
1616
LIMITATIONS
1717
************
18-
The access is readonly by design.
18+
* The access is readonly by design.
19+
* In python below 2.6, memory maps will be created in compatability mode which works, but creates inefficient memory maps as they always start at offset 0.
1920

2021
************
2122
REQUIREMENTS
2223
************
23-
* Python 2.4 or higher
24+
* runs Python 2.4 or higher, but needs Python 2.6 or higher to run properly as it needs the offset parameter of the mmap.mmap function.
2425

2526
*******
2627
Install

smmap/mman.py

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,111 @@
11
"""Module containnig a memory memory manager which provides a sliding window on a number of memory mapped files"""
22

3-
__all__ = []
3+
__all__ = ["MappedMemoryManager"]
4+
5+
import os
6+
import mmap
7+
8+
from mmap import PAGESIZE
9+
10+
#{ Utilities
11+
12+
def align_to_page(num, round_up):
13+
"""Align the given integer number to the closest page offset, which usually is 4096 bytes.
14+
:param round_up: if True, the next higher multiple of page size is used, otherwise
15+
the lower page_size will be used (i.e. if True, 1 becomes 4096, otherwise it becomes 0)
16+
:return: num rounded to closest page"""
17+
res = (num / PAGESIZE) * PAGESIZE;
18+
if round_up and (res != num):
19+
res += PAGESIZE;
20+
#END handle size
21+
return res;
22+
23+
#}END utilities
24+
25+
class Window(object):
26+
"""Utility type which is used to snap windows towards each other, and to adjust their size"""
27+
__slots__ = (
28+
'ofs', # offset into the file in bytes
29+
'size' # size of the window in bytes
30+
)
31+
32+
def __init__(self, offset, size):
33+
self.ofs = offset
34+
self.size = size
35+
36+
def __repr__(self):
37+
return "Window(%i, %i)" % (self.ofs, self.size)
38+
39+
@classmethod
40+
def from_region(cls, region):
41+
""":return: new window from a region"""
42+
return cls(region.ofs_begin(), region.size())
43+
44+
def ofs_end(self):
45+
return self.ofs + self.size
46+
47+
def align(self):
48+
self.ofs = align_to_page(self.ofs, 0)
49+
self.size = align_to_page(self.size, 1)
50+
51+
def extend_left_to(self, window, max_size):
52+
"""Adjust the offset to start where the given window on our left ends if possible,
53+
but don't make yourself larger than max_size.
54+
The resize will assure that the new window still contains the old window area"""
55+
rofs = self.ofs - window.ofs_end()
56+
nsize = rofs + self.size
57+
rofs -= nsize - min(nsize, max_size)
58+
self.ofs = self.ofs - rofs
59+
self.size += rofs
60+
61+
def extend_right_to(self, window, max_size):
62+
"""Adjust the size to make our window end where the right window begins, but don't
63+
get larger than max_size"""
64+
self.size = min(self.size + (window.ofs - self.ofs_end()), max_size)
65+
66+
67+
class Region(object):
68+
"""Defines a mapped region of memory, aligned to pagesizes
69+
:note: deallocates used region automatically on destruction"""
70+
__slots__ = (
71+
'_b' , # beginning of mapping
72+
'_mf', # mapped memory chunk (as returned by mmap)
73+
'_nc', # number of clients using this region
74+
'_uc' # total amount of usages
75+
)
76+
77+
78+
def __init__(self, path, ofs, size):
79+
"""Initialize a region, allocate the memory map
80+
:param path: path to the file to map
81+
:param ofs: **aligned** offset into the file to be mapped
82+
:param size: if size is larger then the file on disk, the whole file will be
83+
allocated the the size automatically adjusted
84+
:raise Exception: if no memory can be allocated"""
85+
self._b = ofs
86+
self._nc = 0
87+
self._uc = 0
88+
89+
fd = os.open(path, os.O_RDONLY|getattr(os, 'O_BINARY', 0))
90+
try:
91+
self._mf = mmap.mmap(fd, size, access=mmap.ACCESS_READ, offset=ofs)
92+
finally:
93+
os.close(fd)
94+
#END close file handle
95+
96+
97+
class MappedMemoryManager(object):
98+
"""Maintains a list of ranges of mapped memory regions in one or more files and allows to easily
99+
obtain additional regions assuring there is no overlap.
100+
Once a certain memory limit is reached globally, or if there cannot be more open file handles
101+
which result from each mmap call, the least recently used, and currently unused mapped regions
102+
are unloaded automatically.
103+
104+
:note: currently not thread-safe !
105+
:note: in the current implementation, we will automatically unload windows if we either cannot
106+
create more memory maps (as the open file handles limit is hit) or if we have allocated more than
107+
a safe amount of memory already, which would possibly cause memory allocations to fail as our address
108+
space is full."""
109+
110+
__slots__ = tuple()
111+

smmap/test/lib.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,50 @@
11
"""Provide base classes for the test system"""
22
from unittest import TestCase
3+
import os
4+
import tempfile
35

4-
__all__ = ['TestBase']
6+
__all__ = ['TestBase', 'FileCreator']
57

68

9+
#{ Utilities
10+
11+
class FileCreator(object):
12+
"""A instance which creates a temporary file with a prefix and a given size
13+
and provides this info to the user.
14+
Once it gets deleted, it will remove the temporary file as well."""
15+
__slots__ = ("_size", "_path")
16+
17+
def __init__(self, size, prefix=''):
18+
assert size, "Require size to be larger 0"
19+
20+
self._path = tempfile.mktemp(prefix=prefix)
21+
self._size = size
22+
23+
fp = open(self._path, "wb")
24+
fp.seek(size-1)
25+
fp.write('1')
26+
fp.close()
27+
28+
assert os.path.getsize(self.path) == size
29+
30+
def __del__(self):
31+
try:
32+
os.remove(self.path)
33+
except OSError:
34+
pass
35+
#END exception handling
36+
37+
38+
@property
39+
def path(self):
40+
return self._path
41+
42+
@property
43+
def size(self):
44+
return self._size
45+
46+
#} END utilities
47+
748
class TestBase(TestCase):
849
"""Foundation used by all tests"""
950

smmap/test/test_mman.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,70 @@
1-
from lib import TestBase
1+
from lib import TestBase, FileCreator
22

33
from smmap.mman import *
4+
from smmap.mman import Region
5+
from smmap.mman import Window
6+
7+
import sys
8+
import mmap
49

510
class TestMMan(TestBase):
11+
12+
_window_test_size = 1000 * 1000 * 8 + 5195
13+
14+
def test_window(self):
15+
wl = Window(0, 1) # left
16+
wc = Window(1, 1) # center
17+
wc2 = Window(10, 5) # another center
18+
wr = Window(8000, 50) # right
19+
20+
assert wl.ofs_end() == 1
21+
assert wc.ofs_end() == 2
22+
assert wr.ofs_end() == 8050
23+
24+
# extension does nothing if already in place
25+
maxsize = 100
26+
wc.extend_left_to(wl, maxsize)
27+
assert wc.ofs == 1 and wc.size == 1
28+
wl.extend_right_to(wc, maxsize)
29+
wl.extend_right_to(wc, maxsize)
30+
assert wl.ofs == 0 and wl.size == 1
31+
32+
# an actual left extension
33+
pofs_end = wc2.ofs_end()
34+
wc2.extend_left_to(wc, maxsize)
35+
assert wc2.ofs == wc.ofs_end() and pofs_end == wc2.ofs_end()
36+
37+
38+
# respects maxsize
39+
wc.extend_right_to(wr, maxsize)
40+
assert wc.ofs == 1 and wc.size == maxsize
41+
wc.extend_right_to(wr, maxsize)
42+
assert wc.ofs == 1 and wc.size == maxsize
43+
44+
# without maxsize
45+
wc.extend_right_to(wr, sys.maxint)
46+
assert wc.ofs_end() == wr.ofs and wc.ofs == 1
47+
48+
# extend left
49+
wr.extend_left_to(wc2, maxsize)
50+
wr.extend_left_to(wc2, maxsize)
51+
assert wr.size == maxsize
52+
53+
wr.extend_left_to(wc2, sys.maxint)
54+
assert wr.ofs == wc2.ofs_end()
55+
56+
wc.align()
57+
assert wc.ofs == 0 and wc.size == mmap.PAGESIZE*2
58+
59+
60+
def test_region(self):
61+
fc = FileCreator(self._window_test_size, "window_test")
62+
rfull = Region(fc.path, 0, fc.size)
63+
64+
65+
66+
Window.from_region # todo
67+
pass
68+
669
def test_basics(self):
7-
assert False
70+
pass

0 commit comments

Comments
 (0)