Skip to content

Commit abf5640

Browse files
committed
Merge branch 'fd'
2 parents 8d64e74 + a8a5e10 commit abf5640

5 files changed

Lines changed: 219 additions & 162 deletions

File tree

smmap/mman.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def _destroy(self):
5454
num_clients = self._rlist.client_count() - 2
5555
if num_clients == 0 and len(self._rlist) == 0:
5656
# Free all resources associated with the mapped file
57-
self._manager._fdict.pop(self._rlist.path())
57+
self._manager._fdict.pop(self._rlist.path_or_fd())
5858
#END remove regions list from manager
5959
#END handle regions
6060

@@ -190,7 +190,7 @@ def use_region(self, offset, size, flags = 0, _is_recursive=False):
190190
if man._handle_count >= man._max_handle_count:
191191
raise Exception
192192
#END assert own imposed max file handles
193-
self._region = self.MappedRegionCls(a.path(), mid.ofs, mid.size, flags)
193+
self._region = self.MappedRegionCls(a.path_or_fd(), mid.ofs, mid.size, flags)
194194
except Exception:
195195
# apparently we are out of system resources or hit a limit
196196
# As many more operations are likely to fail in that condition (
@@ -278,9 +278,26 @@ def file_size(self):
278278
""":return: size of the underlying file"""
279279
return self._rlist.file_size()
280280

281+
def path_or_fd(self):
282+
""":return: path or file decriptor of the underlying mapped file"""
283+
return self._rlist.path_or_fd()
284+
281285
def path(self):
282-
""":return: path of the underlying mapped file"""
283-
return self._rlist.path()
286+
""":return: path of the underlying mapped file
287+
:raise ValueError: if attached path is not a path"""
288+
if isinstance(self._rlist.path_or_fd(), int):
289+
raise ValueError("Path queried although mapping was applied to a file descriptor")
290+
# END handle type
291+
return self._rlist.path_or_fd()
292+
293+
def fd(self):
294+
""":return: file descriptor used to create the underlying mapping.
295+
:note: it is not required to be valid anymore
296+
:raise ValueError: if the mapping was not created by a file descriptor"""
297+
if isinstance(self._rlist.path_or_fd(), basestring):
298+
return ValueError("File descriptor queried although mapping was generated from path")
299+
#END handle type
300+
return self._rlist.path_or_fd()
284301

285302
#} END interface
286303

@@ -383,12 +400,20 @@ def _collect_lru_region(self, size):
383400
return num_found
384401

385402
#{ Interface
386-
def make_cursor(self, path):
387-
""":return: a cursor pointing to the given path. It can be used to map new regions of the file into memory"""
388-
regions = self._fdict.get(path)
403+
def make_cursor(self, path_or_fd):
404+
""":return: a cursor pointing to the given path or file descriptor.
405+
It can be used to map new regions of the file into memory
406+
:note: if a file descriptor is given, it is assumed to be open and valid,
407+
but may be closed afterwards. To refer to the same file, you may reuse
408+
your existing file descriptor, but keep in mind that new windows can only
409+
be mapped as long as it stays valid. This is why the using actual file paths
410+
are preferred unless you plan to keep the file descriptor open.
411+
:note: Using file descriptors directly is faster once new windows are mapped as it
412+
prevents the file to be opened again just for the purpose of mapping it."""
413+
regions = self._fdict.get(path_or_fd)
389414
if regions is None:
390-
regions = self.MappedRegionListCls(path)
391-
self._fdict[path] = regions
415+
regions = self.MappedRegionListCls(path_or_fd)
416+
self._fdict[path_or_fd] = regions
392417
# END obtain region for path
393418
return MemoryCursor(self, regions)
394419

smmap/test/test_buf.py

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from random import randint
77
from time import time
88
import sys
9+
import os
910

1011

1112
man_optimal = MappedMemoryManager()
@@ -63,40 +64,45 @@ def test_basics(self):
6364
# We do it once with an optimal setting, and with a worse manager which
6465
# will produce small mappings only !
6566
max_num_accesses = 400
66-
for manager, man_id in ( (man_optimal, 'optimal'),
67-
(man_worst_case, 'worst case')):
68-
buf = MappedMemoryBuffer(manager.make_cursor(fc.path))
69-
assert manager.num_file_handles() == 1
70-
for access_mode in range(2): # single, multi
71-
num_accesses_left = max_num_accesses
72-
num_bytes = 0
73-
fsize = fc.size
74-
75-
st = time()
76-
buf.begin_access()
77-
while num_accesses_left:
78-
num_accesses_left -= 1
79-
if access_mode: # multi
80-
ofs_start = randint(0, fsize)
81-
ofs_end = randint(ofs_start, fsize)
82-
d = buf[ofs_start:ofs_end]
83-
assert len(d) == ofs_end - ofs_start
84-
assert d == data[ofs_start:ofs_end]
85-
num_bytes += len(d)
86-
else:
87-
pos = randint(0, fsize)
88-
assert buf[pos] == data[pos]
89-
num_bytes += 1
90-
#END handle mode
91-
# END handle num accesses
92-
93-
buf.end_access()
94-
assert manager.num_file_handles()
95-
assert manager.collect()
96-
assert manager.num_file_handles() == 0
97-
elapsed = time() - st
98-
mb = float(1000*1000)
99-
mode_str = (access_mode and "slice") or "single byte"
100-
sys.stderr.write("%s: Made %i random %s accesses to buffer reading a total of %f mb in %f s (%f mb/s)\n" % (man_id, max_num_accesses, mode_str, num_bytes/mb, elapsed, (num_bytes/mb)/elapsed))
101-
# END handle access mode
102-
# END for each manager
67+
fd = os.open(fc.path, os.O_RDONLY)
68+
for item in (fc.path, fd):
69+
for manager, man_id in ( (man_optimal, 'optimal'),
70+
(man_worst_case, 'worst case')):
71+
buf = MappedMemoryBuffer(manager.make_cursor(item))
72+
assert manager.num_file_handles() == 1
73+
for access_mode in range(2): # single, multi
74+
num_accesses_left = max_num_accesses
75+
num_bytes = 0
76+
fsize = fc.size
77+
78+
st = time()
79+
buf.begin_access()
80+
while num_accesses_left:
81+
num_accesses_left -= 1
82+
if access_mode: # multi
83+
ofs_start = randint(0, fsize)
84+
ofs_end = randint(ofs_start, fsize)
85+
d = buf[ofs_start:ofs_end]
86+
assert len(d) == ofs_end - ofs_start
87+
assert d == data[ofs_start:ofs_end]
88+
num_bytes += len(d)
89+
else:
90+
pos = randint(0, fsize)
91+
assert buf[pos] == data[pos]
92+
num_bytes += 1
93+
#END handle mode
94+
# END handle num accesses
95+
96+
buf.end_access()
97+
assert manager.num_file_handles()
98+
assert manager.collect()
99+
assert manager.num_file_handles() == 0
100+
elapsed = time() - st
101+
mb = float(1000*1000)
102+
mode_str = (access_mode and "slice") or "single byte"
103+
sys.stderr.write("%s: Made %i random %s accesses to buffer created from %s reading a total of %f mb in %f s (%f mb/s)\n"
104+
% (man_id, max_num_accesses, mode_str, type(item), num_bytes/mb, elapsed, (num_bytes/mb)/elapsed))
105+
# END handle access mode
106+
# END for each manager
107+
# END for each input
108+
os.close(fd)

smmap/test/test_mman.py

Lines changed: 108 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from random import randint
99
from time import time
10+
import os
1011
import sys
1112
from copy import copy
1213

@@ -62,110 +63,118 @@ def test_memory_manager(self):
6263

6364
# use a region, verify most basic functionality
6465
fc = FileCreator(self.k_window_test_size, "manager_test")
65-
c = man.make_cursor(fc.path)
66-
assert c.use_region(10, 10).is_valid()
67-
assert c.ofs_begin() == 10
68-
assert c.size() == 10
69-
assert c.buffer()[:] == open(fc.path, 'rb').read(20)[10:]
66+
fd = os.open(fc.path, os.O_RDONLY)
67+
for item in (fc.path, fd):
68+
c = man.make_cursor(item)
69+
assert c.use_region(10, 10).is_valid()
70+
assert c.ofs_begin() == 10
71+
assert c.size() == 10
72+
assert c.buffer()[:] == open(fc.path, 'rb').read(20)[10:]
73+
#END for each input
74+
os.close(fd)
7075

7176
def test_memman_operation(self):
7277
# test more access, force it to actually unmap regions
7378
fc = FileCreator(self.k_window_test_size, "manager_operation_test")
7479
data = open(fc.path, 'rb').read()
75-
assert len(data) == fc.size
76-
77-
# small windows, a reasonable max memory. Not too many regions at once
78-
max_num_handles = 15
79-
man = MappedMemoryManager(window_size=fc.size / 100, max_memory_size=fc.size / 3, max_open_handles=max_num_handles)
80-
c = man.make_cursor(fc.path)
81-
82-
# still empty (more about that is tested in test_memory_manager()
83-
assert man.num_open_files() == 0
84-
assert man.mapped_memory_size() == 0
85-
86-
base_offset = 5000
87-
size = man.window_size() / 2
88-
assert c.use_region(base_offset, size).is_valid()
89-
rr = c.region_ref()
90-
assert rr().client_count() == 2 # the manager and the cursor and us
91-
92-
assert man.num_open_files() == 1
93-
assert man.num_file_handles() == 1
94-
assert man.mapped_memory_size() == rr().size()
95-
assert c.size() == size
96-
assert c.ofs_begin() == base_offset
97-
assert rr().ofs_begin() == 0 # it was aligned and expanded
98-
assert rr().size() == align_to_page(man.window_size(), True) # but isn't larger than the max window (aligned)
99-
100-
assert c.buffer()[:] == data[base_offset:base_offset+size]
101-
102-
# obtain second window, which spans the first part of the file - it is a still the same window
103-
assert c.use_region(0, size-10).is_valid()
104-
assert c.region_ref()() == rr()
105-
assert man.num_file_handles() == 1
106-
assert c.size() == size-10
107-
assert c.ofs_begin() == 0
108-
assert c.buffer()[:] == data[:size-10]
109-
110-
# map some part at the end, our requested size cannot be kept
111-
overshoot = 4000
112-
base_offset = fc.size - size + overshoot
113-
assert c.use_region(base_offset, size).is_valid()
114-
assert man.num_file_handles() == 2
115-
assert c.size() < size
116-
assert c.region_ref()() is not rr() # old region is still available, but has not curser ref anymore
117-
assert rr().client_count() == 1 # only held by manager
118-
rr = c.region_ref()
119-
assert rr().client_count() == 2 # manager + cursor
120-
assert rr().ofs_begin() < c.ofs_begin() # it should have extended itself to the left
121-
assert rr().ofs_end() <= fc.size # it cannot be larger than the file
122-
assert c.buffer()[:] == data[base_offset:base_offset+size]
123-
124-
# unising a region makes the cursor invalid
125-
c.unuse_region()
126-
assert not c.is_valid()
127-
# but doesn't change anything regarding the handle count - we cache it and only
128-
# remove mapped regions if we have to
129-
assert man.num_file_handles() == 2
130-
131-
# iterate through the windows, verify data contents
132-
# this will trigger map collection after a while
133-
max_random_accesses = 5000
134-
num_random_accesses = max_random_accesses
135-
memory_read = 0
136-
st = time()
137-
138-
# cache everything to get some more performance
139-
includes_ofs = c.includes_ofs
140-
max_mapped_memory_size = man.max_mapped_memory_size()
141-
max_file_handles = man.max_file_handles()
142-
mapped_memory_size = man.mapped_memory_size
143-
num_file_handles = man.num_file_handles
144-
while num_random_accesses:
145-
num_random_accesses -= 1
146-
base_offset = randint(0, fc.size - 1)
80+
fd = os.open(fc.path, os.O_RDONLY)
81+
for item in (fc.path, fd):
82+
assert len(data) == fc.size
83+
84+
# small windows, a reasonable max memory. Not too many regions at once
85+
max_num_handles = 15
86+
man = MappedMemoryManager(window_size=fc.size / 100, max_memory_size=fc.size / 3, max_open_handles=max_num_handles)
87+
c = man.make_cursor(item)
14788

148-
# precondition
149-
assert max_mapped_memory_size >= mapped_memory_size()
150-
assert max_file_handles >= num_file_handles()
89+
# still empty (more about that is tested in test_memory_manager()
90+
assert man.num_open_files() == 0
91+
assert man.mapped_memory_size() == 0
92+
93+
base_offset = 5000
94+
size = man.window_size() / 2
15195
assert c.use_region(base_offset, size).is_valid()
152-
csize = c.size()
153-
assert c.buffer()[:] == data[base_offset:base_offset+csize]
154-
memory_read += csize
96+
rr = c.region_ref()
97+
assert rr().client_count() == 2 # the manager and the cursor and us
15598

156-
assert includes_ofs(base_offset)
157-
assert includes_ofs(base_offset+csize-1)
158-
assert not includes_ofs(base_offset+csize)
159-
# END while we should do an access
160-
elapsed = time() - st
161-
mb = float(1000 * 1000)
162-
sys.stderr.write("Read %i mb of memory with %i random accesses in %fs (%f mb/s)\n"
163-
% (memory_read/mb, max_random_accesses, elapsed, (memory_read/mb)/elapsed))
164-
165-
# an offset as large as the size doesn't work !
166-
assert not c.use_region(fc.size, size).is_valid()
167-
168-
# collection - it should be able to collect all
169-
assert man.num_file_handles()
170-
assert man.collect()
171-
assert man.num_file_handles() == 0
99+
assert man.num_open_files() == 1
100+
assert man.num_file_handles() == 1
101+
assert man.mapped_memory_size() == rr().size()
102+
assert c.size() == size
103+
assert c.ofs_begin() == base_offset
104+
assert rr().ofs_begin() == 0 # it was aligned and expanded
105+
assert rr().size() == align_to_page(man.window_size(), True) # but isn't larger than the max window (aligned)
106+
107+
assert c.buffer()[:] == data[base_offset:base_offset+size]
108+
109+
# obtain second window, which spans the first part of the file - it is a still the same window
110+
assert c.use_region(0, size-10).is_valid()
111+
assert c.region_ref()() == rr()
112+
assert man.num_file_handles() == 1
113+
assert c.size() == size-10
114+
assert c.ofs_begin() == 0
115+
assert c.buffer()[:] == data[:size-10]
116+
117+
# map some part at the end, our requested size cannot be kept
118+
overshoot = 4000
119+
base_offset = fc.size - size + overshoot
120+
assert c.use_region(base_offset, size).is_valid()
121+
assert man.num_file_handles() == 2
122+
assert c.size() < size
123+
assert c.region_ref()() is not rr() # old region is still available, but has not curser ref anymore
124+
assert rr().client_count() == 1 # only held by manager
125+
rr = c.region_ref()
126+
assert rr().client_count() == 2 # manager + cursor
127+
assert rr().ofs_begin() < c.ofs_begin() # it should have extended itself to the left
128+
assert rr().ofs_end() <= fc.size # it cannot be larger than the file
129+
assert c.buffer()[:] == data[base_offset:base_offset+size]
130+
131+
# unising a region makes the cursor invalid
132+
c.unuse_region()
133+
assert not c.is_valid()
134+
# but doesn't change anything regarding the handle count - we cache it and only
135+
# remove mapped regions if we have to
136+
assert man.num_file_handles() == 2
137+
138+
# iterate through the windows, verify data contents
139+
# this will trigger map collection after a while
140+
max_random_accesses = 5000
141+
num_random_accesses = max_random_accesses
142+
memory_read = 0
143+
st = time()
144+
145+
# cache everything to get some more performance
146+
includes_ofs = c.includes_ofs
147+
max_mapped_memory_size = man.max_mapped_memory_size()
148+
max_file_handles = man.max_file_handles()
149+
mapped_memory_size = man.mapped_memory_size
150+
num_file_handles = man.num_file_handles
151+
while num_random_accesses:
152+
num_random_accesses -= 1
153+
base_offset = randint(0, fc.size - 1)
154+
155+
# precondition
156+
assert max_mapped_memory_size >= mapped_memory_size()
157+
assert max_file_handles >= num_file_handles()
158+
assert c.use_region(base_offset, size).is_valid()
159+
csize = c.size()
160+
assert c.buffer()[:] == data[base_offset:base_offset+csize]
161+
memory_read += csize
162+
163+
assert includes_ofs(base_offset)
164+
assert includes_ofs(base_offset+csize-1)
165+
assert not includes_ofs(base_offset+csize)
166+
# END while we should do an access
167+
elapsed = time() - st
168+
mb = float(1000 * 1000)
169+
sys.stderr.write("Read %i mb of memory with %i random on cursor initialized with %s accesses in %fs (%f mb/s)\n"
170+
% (memory_read/mb, max_random_accesses, type(item), elapsed, (memory_read/mb)/elapsed))
171+
172+
# an offset as large as the size doesn't work !
173+
assert not c.use_region(fc.size, size).is_valid()
174+
175+
# collection - it should be able to collect all
176+
assert man.num_file_handles()
177+
assert man.collect()
178+
assert man.num_file_handles() == 0
179+
#END for each item
180+
os.close(fd)

0 commit comments

Comments
 (0)