Skip to content

Commit a8a5e10

Browse files
committed
Added tests for the fd case. It shows that this is measurably faster than the string path version because there is less system overhead. Good to know actally
1 parent 4ad2231 commit a8a5e10

4 files changed

Lines changed: 157 additions & 138 deletions

File tree

smmap/mman.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,9 @@ def make_cursor(self, path_or_fd):
407407
but may be closed afterwards. To refer to the same file, you may reuse
408408
your existing file descriptor, but keep in mind that new windows can only
409409
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."""
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."""
411413
regions = self._fdict.get(path_or_fd)
412414
if regions is None:
413415
regions = self.MappedRegionListCls(path_or_fd)

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)

smmap/util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ def __init__(self, path_or_fd, ofs, size, flags = 0):
131131
self._mfb = buffer(self._mf, ofs, size)
132132
#END handle buffer wrapping
133133
finally:
134-
os.close(fd)
134+
if isinstance(path_or_fd, basestring):
135+
os.close(fd)
136+
#END only close it if we opened it
135137
#END close file handle
136138

137139
def __repr__(self):

0 commit comments

Comments
 (0)