Skip to content

Commit 4f3d1ad

Browse files
committed
Applied some optimizations for performance. Python is very easily overwhelmed with plenty of calls, causing too much overhead
1 parent c90cfef commit 4f3d1ad

3 files changed

Lines changed: 68 additions & 45 deletions

File tree

smmap/mman.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,30 @@ def use_region(self, offset, size, _is_recursive=False):
9999
# END check existing region
100100

101101
if need_region:
102+
window_size = man._window_size
103+
102104
# abort on offsets beyond our mapped file's size - currently we are invalid
103105
if offset >= self.file_size():
104106
return self
105107
# END handle offset too large
106108

107109
existing_region = None
108-
for region in self._rlist:
109-
if region.includes_ofs(offset):
110-
existing_region = region
111-
break
112-
#END handle existing region
113-
#END for each existing region
110+
a = self._rlist
111+
lo = 0
112+
hi = len(a)
113+
while lo < hi:
114+
mid = (lo+hi)//2
115+
ofs = a[mid]._b
116+
if ofs <= offset:
117+
if a[mid].includes_ofs(offset):
118+
existing_region = a[mid]
119+
break
120+
#END have region
121+
lo = mid+1
122+
else:
123+
hi = mid
124+
#END handle position
125+
#END while bisecting
114126

115127
if existing_region is None:
116128
left = MemoryWindow(0, 0)
@@ -119,20 +131,23 @@ def use_region(self, offset, size, _is_recursive=False):
119131

120132
# we want to honor the max memory size, and assure we have anough
121133
# memory available
122-
man._collect_lru_region(man.window_size())
134+
# Save calls !
135+
if self._manager._memory_size + window_size > self._manager._max_memory_size:
136+
man._collect_lru_region(window_size)
137+
#END handle collection
123138

124139
# we assume the list remains sorted by offset
125140
insert_pos = 0
126-
len_regions = len(self._rlist)
141+
len_regions = len(a)
127142
if len_regions == 1:
128-
if self._rlist[0].ofs_begin() <= offset:
143+
if a[0]._b <= offset:
129144
insert_pos = 1
130145
#END maintain sort
131146
else:
132147
# find insert position
133148
insert_pos = len_regions
134-
for i, region in enumerate(self._rlist):
135-
if region.ofs_begin() > offset:
149+
for i, region in enumerate(a):
150+
if region._b > offset:
136151
insert_pos = i
137152
break
138153
#END if insert position is correct
@@ -143,17 +158,17 @@ def use_region(self, offset, size, _is_recursive=False):
143158
# possible mapping
144159
if insert_pos == 0:
145160
if len_regions:
146-
right = MemoryWindow.from_region(self._rlist[insert_pos])
161+
right = MemoryWindow.from_region(a[insert_pos])
147162
#END adjust right side
148163
else:
149164
if insert_pos != len_regions:
150-
right = MemoryWindow.from_region(self._rlist[insert_pos])
165+
right = MemoryWindow.from_region(a[insert_pos])
151166
# END adjust right window
152-
left = MemoryWindow.from_region(self._rlist[insert_pos - 1])
167+
left = MemoryWindow.from_region(a[insert_pos - 1])
153168
#END adjust surrounding windows
154169

155-
mid.extend_left_to(left, man._window_size)
156-
mid.extend_right_to(right, man._window_size)
170+
mid.extend_left_to(left, window_size)
171+
mid.extend_right_to(right, window_size)
157172
mid.align()
158173

159174
# it can happen that we align beyond the end of the file
@@ -166,7 +181,7 @@ def use_region(self, offset, size, _is_recursive=False):
166181
if man._handle_count >= man._max_handle_count:
167182
raise Exception
168183
#END assert own imposed max file handles
169-
self._region = MappedRegion(self._rlist.path(), mid.ofs, mid.size)
184+
self._region = MappedRegion(a.path(), mid.ofs, mid.size)
170185
except Exception:
171186
# apparently we are out of system resources or hit a limit
172187
# As many more operations are likely to fail in that condition (
@@ -185,14 +200,14 @@ def use_region(self, offset, size, _is_recursive=False):
185200

186201
man._handle_count += 1
187202
man._memory_size += self._region.size()
188-
self._rlist.insert(insert_pos, self._region)
203+
a.insert(insert_pos, self._region)
189204
else:
190205
self._region = existing_region
191206
#END need region handling
192207
#END handle acquire region
193208

194209
self._region.increment_usage_count()
195-
self._ofs = offset - self._region.ofs_begin()
210+
self._ofs = offset - self._region._b
196211
self._size = min(size, self._region.ofs_end() - offset)
197212

198213
return self
@@ -220,12 +235,12 @@ def is_associated(self):
220235

221236
def ofs_begin(self):
222237
""":return: offset to the first byte pointed to by our cursor"""
223-
return self._region.ofs_begin() + self._ofs
238+
return self._region._b + self._ofs
224239

225240
def ofs_end(self):
226241
""":return: offset to one past the last available byte"""
227242
# unroll method calls for performance !
228-
return self._region.ofs_begin() + self._ofs + self._size
243+
return self._region._b + self._ofs + self._size
229244

230245
def size(self):
231246
""":return: amount of bytes we point to"""
@@ -241,10 +256,9 @@ def region_ref(self):
241256
def includes_ofs(self, ofs):
242257
""":return: True if the given absolute offset is contained in the cursors
243258
current region
244-
:note: always False if the cursor does not point to a valid region"""
245-
if self._region is None:
246-
return False
247-
return self.ofs_begin() <= ofs < self.ofs_end()
259+
:note: cursor must be valid for this to work"""
260+
# unroll methods
261+
return (self._region._b + self._ofs) <= ofs < (self._region._b + self._ofs + self._size)
248262

249263
def file_size(self):
250264
""":return: size of the underlying file"""
@@ -327,7 +341,7 @@ def _collect_lru_region(self, size):
327341
for region in regions:
328342
# check client count - consider that we keep one reference ourselves !
329343
if (region.client_count()-2 == 0 and
330-
(lru_region is None or region.usage_count() < lru_region.usage_count())):
344+
(lru_region is None or region._uc < lru_region._uc)):
331345
lru_region = region
332346
lru_list = regions
333347
# END update lru_region

smmap/test/test_mman.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,33 +127,39 @@ def test_memman_operation(self):
127127
# remove mapped regions if we have to
128128
assert man.num_file_handles() == 2
129129

130-
# an offset as large as the size doesn't work !
131-
assert not c.use_region(fc.size, size).is_valid()
132-
133130
# iterate through the windows, verify data contents
134131
# this will trigger map collection after a while
135132
max_random_accesses = 15000
136133
num_random_accesses = max_random_accesses
137134
memory_read = 0
138135
st = time()
139136

137+
# cache everything to get some more performance
138+
includes_ofs = c.includes_ofs
139+
max_mapped_memory_size = man.max_mapped_memory_size()
140+
max_file_handles = man.max_file_handles()
141+
mapped_memory_size = man.mapped_memory_size
142+
num_file_handles = man.num_file_handles
140143
while num_random_accesses:
141144
num_random_accesses -= 1
142145
base_offset = randint(0, fc.size - 1)
143146

144147
# precondition
145-
assert man.max_mapped_memory_size() >= man.mapped_memory_size()
146-
assert man.max_file_handles() >= man.num_file_handles()
148+
assert max_mapped_memory_size >= mapped_memory_size()
149+
assert max_file_handles >= num_file_handles()
147150
assert c.use_region(base_offset, size).is_valid()
148-
assert c.buffer()[:] == data[base_offset:base_offset+c.size()]
149-
memory_read += c.size()
151+
csize = c.size()
152+
assert c.buffer()[:] == data[base_offset:base_offset+csize]
153+
memory_read += csize
150154

151-
assert c.includes_ofs(base_offset)
152-
assert c.includes_ofs(base_offset+c.size()-1)
153-
assert not c.includes_ofs(base_offset+c.size())
155+
assert includes_ofs(base_offset)
156+
assert includes_ofs(base_offset+csize-1)
157+
assert not includes_ofs(base_offset+csize)
154158
# END while we should do an access
155159
elapsed = time() - st
156160
mb = 1000 * 1000
157-
sys.stderr.write("Read %i mb of memory with %i random accesses in %f s(%f mb/s)\n"
161+
sys.stderr.write("Read %i mb of memory with %i random accesses in %fs (%f mb/s)\n"
158162
% (memory_read/mb, max_random_accesses, elapsed, (memory_read/mb)/elapsed))
159163

164+
# an offset as large as the size doesn't work !
165+
assert not c.use_region(fc.size, size).is_valid()

smmap/util.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import mmap
55

6-
from mmap import PAGESIZE
6+
from mmap import PAGESIZE, mmap, ACCESS_READ
77
from sys import getrefcount
88

99
__all__ = [ "align_to_page", "is_64_bit",
@@ -48,7 +48,7 @@ def __repr__(self):
4848
@classmethod
4949
def from_region(cls, region):
5050
""":return: new window from a region"""
51-
return cls(region.ofs_begin(), region.size())
51+
return cls(region._b, region._size)
5252

5353
def ofs_end(self):
5454
return self.ofs + self.size
@@ -83,6 +83,7 @@ class MappedRegion(object):
8383
'_b' , # beginning of mapping
8484
'_mf', # mapped memory chunk (as returned by mmap)
8585
'_uc', # total amount of usages
86+
'_size', # cached size of our memory map
8687
'__weakref__'
8788
]
8889
_need_compat_layer = sys.version_info[1] < 6
@@ -100,11 +101,12 @@ def __init__(self, path, ofs, size):
100101
allocated the the size automatically adjusted
101102
:raise Exception: if no memory can be allocated"""
102103
self._b = ofs
104+
self._size = 0
103105
self._uc = 0
104106

105107
fd = os.open(path, os.O_RDONLY|getattr(os, 'O_BINARY', 0))
106108
try:
107-
kwargs = dict(access=mmap.ACCESS_READ, offset=ofs)
109+
kwargs = dict(access=ACCESS_READ, offset=ofs)
108110
corrected_size = size
109111
sizeofs = ofs
110112
if self._need_compat_layer:
@@ -116,7 +118,8 @@ def __init__(self, path, ofs, size):
116118
# have to correct size, otherwise (instead of the c version) it will
117119
# bark that the size is too large ... many extra file accesses because
118120
# if this ... argh !
119-
self._mf = mmap.mmap(fd, min(os.fstat(fd).st_size - sizeofs, corrected_size), **kwargs)
121+
self._mf = mmap(fd, min(os.fstat(fd).st_size - sizeofs, corrected_size), **kwargs)
122+
self._size = len(self._mf)
120123

121124
if self._need_compat_layer:
122125
self._mfb = buffer(self._mf, ofs, size)
@@ -126,7 +129,7 @@ def __init__(self, path, ofs, size):
126129
#END close file handle
127130

128131
def __repr__(self):
129-
return "MappedRegion<%i, %i>" % (self._b, self.size())
132+
return "MappedRegion<%i, %i>" % (self._b, self._size)
130133

131134
#{ Interface
132135

@@ -140,15 +143,15 @@ def ofs_begin(self):
140143

141144
def size(self):
142145
""":return: total size of the mapped region in bytes"""
143-
return len(self._mf)
146+
return self._size
144147

145148
def ofs_end(self):
146149
""":return: Absolute offset to one byte beyond the mapping into the file"""
147-
return self._b + self.size()
150+
return self._b + self._size
148151

149152
def includes_ofs(self, ofs):
150153
""":return: True if the given offset can be read in our mapped region"""
151-
return self.ofs_begin() <= ofs < self.ofs_end()
154+
return self._b <= ofs < self._b + self._size
152155

153156
def client_count(self):
154157
""":return: number of clients currently using this region"""

0 commit comments

Comments
 (0)