Skip to content

Commit a51b65d

Browse files
committed
Finished tutorial section, umproved capabilities of the buffer implementation to be more pythonic. Unfortunately, not all docs build yet because of some typical sphinx issue that results in an error which doesn't at all tell what the culprit actually is
1 parent 78cdb21 commit a51b65d

6 files changed

Lines changed: 221 additions & 9 deletions

File tree

doc/source/intro.rst

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ Limitations
3434
* In python below 2.6, memory maps will be created in compatibility mode which works, but creates inefficient memory mappings as they always start at offset 0.
3535
* It wasn't tested on python 2.7 and 3.x.
3636

37-
###############
38-
Getting Started
39-
###############
40-
It is advised to have a look at the :ref:`Usage Guide <tutorial-label>` for a brief introduction on the different database implementations.
41-
4237
################
4338
Installing smmap
4439
################
@@ -53,7 +48,9 @@ As the command will install smmap in your respective python distribution, you wi
5348
If you have downloaded the source archive, the package can be installed by running the ``setup.py`` script::
5449
5550
$ python setup.py install
56-
51+
52+
It is advised to have a look at the :ref:`Usage Guide <tutorial-label>` for a brief introduction on the different database implementations.
53+
5754
##################
5855
Homepage and Links
5956
##################

doc/source/tutorial.rst

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
.. _tutorial-label:
2+
3+
###########
4+
Usage Guide
5+
###########
6+
This text briefly introduces you to the basic design decisions and accompanying classes.
7+
8+
******
9+
Design
10+
******
11+
Per application, there is *MemoryManager* which is held as static instance and used throughout the application. It can be configured to keep your resources within certain limits.
12+
13+
To access mapped regions, you require a cursor. Cursors point to exactly one file and serve as handles into it. As long as it exists, the respective memory region will remain available.
14+
15+
For convenience, a buffer implementation is provided which handles cursors and resource allocation behind its simple buffer like interface.
16+
17+
***************
18+
Memory Managers
19+
***************
20+
There are two types of memory managers, one uses *static* windows, the other one uses *sliding* windows. A window is a region of a file mapped into memory. Although the names might be somewhat misleading as technically windows are always static, the *sliding* version will allocate relatively small windows whereas the *static* version will always map the whole file.
21+
22+
The *static* manager does nothing more than keeping a client count on the respective memory maps which always map the whole file, which allows to make some assumptions that can lead to simplified data access and increased performance, but reduces the compatibility to 32 bit systems or giant files.
23+
24+
The *sliding* memory manager therefore should be the default manager when preparing an application for handling huge amounts of data on 32 bit and 64 bit platforms::
25+
26+
import smmap
27+
# This instance should be globally available in your application
28+
# It is configured to be well suitable for 32-bit or 64 bit applications.
29+
mman = smmap.SlidingWindowMapManager()
30+
31+
# the manager provides much useful information about its current state
32+
# like the amount of open file handles or the amount of mapped memory
33+
mman.num_file_handles()
34+
mman.mapped_memory_size()
35+
# and many more ...
36+
37+
38+
Cursors
39+
*******
40+
*Cursors* are handles that point onto a window, i.e. a region of a file mapped into memory. From them you may obtain a buffer through which the data of that window can actually be accessed::
41+
42+
import smmap.test.lib
43+
fc = smmap.test.lib.FileCreator(1024*1024*8, "test_file")
44+
45+
# obtain a cursor to access some file.
46+
c = mman.make_cursor(fc.path)
47+
48+
# the cursor is now associated with the file, but not yet usable
49+
assert c.is_associated()
50+
assert not c.is_valid()
51+
52+
# before you can use the cursor, you have to specify a window you want to
53+
# access. The following just says you want as much data as possible starting
54+
# from offset 0.
55+
# To be sure your region could be mapped, query for validity
56+
assert c.use_region().is_valid() # use_region returns self
57+
58+
# once a region was mapped, you must query its dimension regularly
59+
# to assure you don't try to access its buffer out of its bounds
60+
assert c.size()
61+
c.buffer()[0] # first byte
62+
c.buffer()[1:10] # first 9 bytes
63+
c.buffer()[c.size()-1] # last byte
64+
65+
# its recommended not to create big slices when feeding the buffer
66+
# into consumers (e.g. struct or zlib).
67+
# Instead, either give the buffer directly, or use pythons buffer command.
68+
buffer(c.buffer(), 1, 9) # first 9 bytes without copying them
69+
70+
# you can query absolute offsets, and check whether an offset is included
71+
# in the cursor's data.
72+
assert c.ofs_begin() < c.ofs_end()
73+
assert c.includes_ofs(100)
74+
75+
# If you are over out of bounds with one of your region requests, the
76+
# cursor will be come invalid. It cannot be used in that state
77+
assert not c.use_region(fc.size, 100).is_valid()
78+
# map as much as possible after skipping the first 100 bytes
79+
assert c.use_region(100).is_valid()
80+
81+
# You can explicitly free cursor resources by unusing the cursor's region
82+
c.unuse_region()
83+
assert not c.is_valid()
84+
85+
86+
Now you would have to write your algorithms around this interface to properly slide through huge amounts of data.
87+
88+
Alternatively you can use a convenience interface.
89+
90+
*******
91+
Buffers
92+
*******
93+
To make first use easier, at the expense of performance, there is a Buffer implementation which uses a cursor underneath.
94+
95+
With it, you can access all data in a possibly huge file without having to take care of setting the cursor to different regions yourself::
96+
97+
# Create a default buffer which can operate on the whole file
98+
buf = smmap.SlidingWindowMapBuffer(mman.make_cursor(fc.path))
99+
100+
# you can use it right away
101+
assert buf.cursor().is_valid()
102+
103+
buf[0] # access the first byte
104+
buf[-1] # access the last ten bytes on the file
105+
buf[-10:]# access the last ten bytes
106+
107+
# If you want to keep the instance between different accesses, use the
108+
# dedicated methods
109+
buf.end_access()
110+
assert not buf.cursor().is_valid() # you cannot use the buffer anymore
111+
assert buf.begin_access(offset=10) # start using the buffer at an offset
112+
113+
# it will stop using resources automatically once it goes out of scope
114+
115+
Disadvantages
116+
*************
117+
Buffers cannot be used in place of strings or maps, hence you have to slice them to have valid input for the sorts of struct and zlib. A slice means a lot of data handling overhead which makes buffers slower compared to using cursors directly.
118+

smmap/buf.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def __len__(self):
4747
def __getitem__(self, i):
4848
c = self._c
4949
assert c.is_valid()
50+
if i < 0:
51+
i = self._size + i
5052
if not c.includes_ofs(i):
5153
c.use_region(i, 1)
5254
# END handle region usage
@@ -57,6 +59,12 @@ def __getslice__(self, i, j):
5759
# fast path, slice fully included - safes a concatenate operation and
5860
# should be the default
5961
assert c.is_valid()
62+
if i < 0:
63+
i = self._size + i
64+
if j == sys.maxint:
65+
j = self._size
66+
if j < 0:
67+
j = self._size + j
6068
if (c.ofs_begin() <= i) and (j < c.ofs_end()):
6169
b = c.ofs_begin()
6270
return c.buffer()[i-b:j-b]
@@ -68,6 +76,7 @@ def __getslice__(self, i, j):
6876
md = str()
6977
while l:
7078
c.use_region(ofs, l)
79+
assert c.is_valid()
7180
d = c.buffer()[:l]
7281
ofs += len(d)
7382
l -= len(d)
@@ -102,6 +111,7 @@ def begin_access(self, cursor = None, offset = 0, size = sys.maxint, flags = 0):
102111
self._size = size
103112
#END set size
104113
return res
114+
# END use our cursor
105115
return False
106116

107117
def end_access(self):

smmap/mman.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@
1111
import sys
1212
from sys import getrefcount
1313

14-
__all__ = ["StaticWindowMapManager", "SlidingWindowMapManager"]
14+
__all__ = ["StaticWindowMapManager", "SlidingWindowMapManager", "WindowCursor"]
1515
#{ Utilities
1616

1717
#}END utilities
1818

1919

20-
2120
class WindowCursor(object):
2221
"""Pointer into the mapped region of the memory manager, keeping the map
2322
alive until it is destroyed and no other client uses it.
24-
23+
2524
Cursors should not be created manually, but are instead returned by the SlidingWindowMapManager
2625
:note: The current implementation is suited for static and sliding window managers, but it also means
2726
that it must be suited for the somewhat quite different sliding manager. It could be improved, but
@@ -85,6 +84,7 @@ def assign(self, rhs):
8584

8685
def use_region(self, offset = 0, size = 0, flags = 0):
8786
"""Assure we point to a window which allows access to the given offset into the file
87+
8888
:param offset: absolute offset in bytes into the file
8989
:param size: amount of bytes to map. If 0, all available bytes will be mapped
9090
:param flags: additional flags to be given to os.open in case a file handle is initially opened

smmap/test/test_buf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def test_basics(self):
5050
assert data[offset] == buf[0]
5151
assert data[offset:offset*2] == buf[0:offset]
5252

53+
# negative indices, partial slices
54+
assert buf[-1] == buf[len(buf)-1]
55+
assert buf[-10:] == buf[len(buf)-10:len(buf)]
56+
5357
# end access makes its cursor invalid
5458
buf.end_access()
5559
assert not buf.cursor().is_valid()

smmap/test/test_tutorial.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from lib import TestBase
2+
3+
class TestTutorial(TestBase):
4+
5+
def test_example(self):
6+
# Memory Managers
7+
##################
8+
import smmap
9+
# This instance should be globally available in your application
10+
# It is configured to be well suitable for 32-bit or 64 bit applications.
11+
mman = smmap.SlidingWindowMapManager()
12+
13+
# the manager provides much useful information about its current state
14+
# like the amount of open file handles or the amount of mapped memory
15+
assert mman.num_file_handles() == 0
16+
assert mman.mapped_memory_size() == 0
17+
# and many more ...
18+
19+
# Cursors
20+
##########
21+
import smmap.test.lib
22+
fc = smmap.test.lib.FileCreator(1024*1024*8, "test_file")
23+
24+
# obtain a cursor to access some file.
25+
c = mman.make_cursor(fc.path)
26+
27+
# the cursor is now associated with the file, but not yet usable
28+
assert c.is_associated()
29+
assert not c.is_valid()
30+
31+
# before you can use the cursor, you have to specify a window you want to
32+
# access. The following just says you want as much data as possible starting
33+
# from offset 0.
34+
# To be sure your region could be mapped, query for validity
35+
assert c.use_region().is_valid() # use_region returns self
36+
37+
# once a region was mapped, you must query its dimension regularly
38+
# to assure you don't try to access its buffer out of its bounds
39+
assert c.size()
40+
c.buffer()[0] # first byte
41+
c.buffer()[1:10] # first 9 bytes
42+
c.buffer()[c.size()-1] # last byte
43+
44+
# its recommended not to create big slices when feeding the buffer
45+
# into consumers (e.g. struct or zlib).
46+
# Instead, either give the buffer directly, or use pythons buffer command.
47+
buffer(c.buffer(), 1, 9) # first 9 bytes without copying them
48+
49+
# you can query absolute offsets, and check whether an offset is included
50+
# in the cursor's data.
51+
assert c.ofs_begin() < c.ofs_end()
52+
assert c.includes_ofs(100)
53+
54+
# If you are over out of bounds with one of your region requests, the
55+
# cursor will be come invalid. It cannot be used in that state
56+
assert not c.use_region(fc.size, 100).is_valid()
57+
# map as much as possible after skipping the first 100 bytes
58+
assert c.use_region(100).is_valid()
59+
60+
# You can explicitly free cursor resources by unusing the cursor's region
61+
c.unuse_region()
62+
assert not c.is_valid()
63+
64+
# Buffers
65+
#########
66+
# Create a default buffer which can operate on the whole file
67+
buf = smmap.SlidingWindowMapBuffer(mman.make_cursor(fc.path))
68+
69+
# you can use it right away
70+
assert buf.cursor().is_valid()
71+
72+
buf[0] # access the first byte
73+
buf[-1] # access the last ten bytes on the file
74+
buf[-10:]# access the last ten bytes
75+
76+
# If you want to keep the instance between different accesses, use the
77+
# dedicated methods
78+
buf.end_access()
79+
assert not buf.cursor().is_valid() # you cannot use the buffer anymore
80+
assert buf.begin_access(offset=10) # start using the buffer at an offset
81+
82+
# it will stop using resources automatically once it goes out of scope
83+

0 commit comments

Comments
 (0)