test_mman.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. from .lib import TestBase, FileCreator
  2. from smmap.mman import (
  3. WindowCursor,
  4. SlidingWindowMapManager,
  5. StaticWindowMapManager
  6. )
  7. from smmap.util import align_to_mmap
  8. from random import randint
  9. from time import time
  10. import os
  11. import sys
  12. from copy import copy
  13. class TestMMan(TestBase):
  14. def test_cursor(self):
  15. with FileCreator(self.k_window_test_size, "cursor_test") as fc:
  16. man = SlidingWindowMapManager()
  17. ci = WindowCursor(man) # invalid cursor
  18. assert not ci.is_valid()
  19. assert not ci.is_associated()
  20. assert ci.size() == 0 # this is cached, so we can query it in invalid state
  21. cv = man.make_cursor(fc.path)
  22. assert not cv.is_valid() # no region mapped yet
  23. assert cv.is_associated() # but it know where to map it from
  24. assert cv.file_size() == fc.size
  25. assert cv.path() == fc.path
  26. # copy module
  27. cio = copy(cv)
  28. assert not cio.is_valid() and cio.is_associated()
  29. # assign method
  30. assert not ci.is_associated()
  31. ci.assign(cv)
  32. assert not ci.is_valid() and ci.is_associated()
  33. # unuse non-existing region is fine
  34. cv.unuse_region()
  35. cv.unuse_region()
  36. # destruction is fine (even multiple times)
  37. cv._destroy()
  38. WindowCursor(man)._destroy()
  39. def test_memory_manager(self):
  40. slide_man = SlidingWindowMapManager()
  41. static_man = StaticWindowMapManager()
  42. for man in (static_man, slide_man):
  43. assert man.num_file_handles() == 0
  44. assert man.num_open_files() == 0
  45. winsize_cmp_val = 0
  46. if isinstance(man, StaticWindowMapManager):
  47. winsize_cmp_val = -1
  48. # END handle window size
  49. assert man.window_size() > winsize_cmp_val
  50. assert man.mapped_memory_size() == 0
  51. assert man.max_mapped_memory_size() > 0
  52. # collection doesn't raise in 'any' mode
  53. man._collect_lru_region(0)
  54. # doesn't raise if we are within the limit
  55. man._collect_lru_region(10)
  56. # doesn't fail if we over-allocate
  57. assert man._collect_lru_region(sys.maxsize) == 0
  58. # use a region, verify most basic functionality
  59. with FileCreator(self.k_window_test_size, "manager_test") as fc:
  60. fd = os.open(fc.path, os.O_RDONLY)
  61. try:
  62. for item in (fc.path, fd):
  63. c = man.make_cursor(item)
  64. assert c.path_or_fd() is item
  65. assert c.use_region(10, 10).is_valid()
  66. assert c.ofs_begin() == 10
  67. assert c.size() == 10
  68. with open(fc.path, 'rb') as fp:
  69. assert c.buffer()[:] == fp.read(20)[10:]
  70. if isinstance(item, int):
  71. self.assertRaises(ValueError, c.path)
  72. else:
  73. self.assertRaises(ValueError, c.fd)
  74. # END handle value error
  75. # END for each input
  76. finally:
  77. os.close(fd)
  78. # END for each manasger type
  79. def test_memman_operation(self):
  80. # test more access, force it to actually unmap regions
  81. with FileCreator(self.k_window_test_size, "manager_operation_test") as fc:
  82. with open(fc.path, 'rb') as fp:
  83. data = fp.read()
  84. fd = os.open(fc.path, os.O_RDONLY)
  85. try:
  86. max_num_handles = 15
  87. # small_size =
  88. for mtype, args in ((StaticWindowMapManager, (0, fc.size // 3, max_num_handles)),
  89. (SlidingWindowMapManager, (fc.size // 100, fc.size // 3, max_num_handles)),):
  90. for item in (fc.path, fd):
  91. assert len(data) == fc.size
  92. # small windows, a reasonable max memory. Not too many regions at once
  93. man = mtype(window_size=args[0], max_memory_size=args[1], max_open_handles=args[2])
  94. c = man.make_cursor(item)
  95. # still empty (more about that is tested in test_memory_manager()
  96. assert man.num_open_files() == 0
  97. assert man.mapped_memory_size() == 0
  98. base_offset = 5000
  99. # window size is 0 for static managers, hence size will be 0. We take that into consideration
  100. size = man.window_size() // 2
  101. assert c.use_region(base_offset, size).is_valid()
  102. rr = c.region()
  103. assert rr.client_count() == 2 # the manager and the cursor and us
  104. assert man.num_open_files() == 1
  105. assert man.num_file_handles() == 1
  106. assert man.mapped_memory_size() == rr.size()
  107. # assert c.size() == size # the cursor may overallocate in its static version
  108. assert c.ofs_begin() == base_offset
  109. assert rr.ofs_begin() == 0 # it was aligned and expanded
  110. if man.window_size():
  111. # but isn't larger than the max window (aligned)
  112. assert rr.size() == align_to_mmap(man.window_size(), True)
  113. else:
  114. assert rr.size() == fc.size
  115. # END ignore static managers which dont use windows and are aligned to file boundaries
  116. assert c.buffer()[:] == data[base_offset:base_offset + (size or c.size())]
  117. # obtain second window, which spans the first part of the file - it is a still the same window
  118. nsize = (size or fc.size) - 10
  119. assert c.use_region(0, nsize).is_valid()
  120. assert c.region() == rr
  121. assert man.num_file_handles() == 1
  122. assert c.size() == nsize
  123. assert c.ofs_begin() == 0
  124. assert c.buffer()[:] == data[:nsize]
  125. # map some part at the end, our requested size cannot be kept
  126. overshoot = 4000
  127. base_offset = fc.size - (size or c.size()) + overshoot
  128. assert c.use_region(base_offset, size).is_valid()
  129. if man.window_size():
  130. assert man.num_file_handles() == 2
  131. assert c.size() < size
  132. assert c.region() is not rr # old region is still available, but has not curser ref anymore
  133. assert rr.client_count() == 1 # only held by manager
  134. else:
  135. assert c.size() < fc.size
  136. # END ignore static managers which only have one handle per file
  137. rr = c.region()
  138. assert rr.client_count() == 2 # manager + cursor
  139. assert rr.ofs_begin() < c.ofs_begin() # it should have extended itself to the left
  140. assert rr.ofs_end() <= fc.size # it cannot be larger than the file
  141. assert c.buffer()[:] == data[base_offset:base_offset + (size or c.size())]
  142. # unising a region makes the cursor invalid
  143. c.unuse_region()
  144. assert not c.is_valid()
  145. if man.window_size():
  146. # but doesn't change anything regarding the handle count - we cache it and only
  147. # remove mapped regions if we have to
  148. assert man.num_file_handles() == 2
  149. # END ignore this for static managers
  150. # iterate through the windows, verify data contents
  151. # this will trigger map collection after a while
  152. max_random_accesses = 5000
  153. num_random_accesses = max_random_accesses
  154. memory_read = 0
  155. st = time()
  156. # cache everything to get some more performance
  157. includes_ofs = c.includes_ofs
  158. max_mapped_memory_size = man.max_mapped_memory_size()
  159. max_file_handles = man.max_file_handles()
  160. mapped_memory_size = man.mapped_memory_size
  161. num_file_handles = man.num_file_handles
  162. while num_random_accesses:
  163. num_random_accesses -= 1
  164. base_offset = randint(0, fc.size - 1)
  165. # precondition
  166. if man.window_size():
  167. assert max_mapped_memory_size >= mapped_memory_size()
  168. # END statics will overshoot, which is fine
  169. assert max_file_handles >= num_file_handles()
  170. assert c.use_region(base_offset, (size or c.size())).is_valid()
  171. csize = c.size()
  172. assert c.buffer()[:] == data[base_offset:base_offset + csize]
  173. memory_read += csize
  174. assert includes_ofs(base_offset)
  175. assert includes_ofs(base_offset + csize - 1)
  176. assert not includes_ofs(base_offset + csize)
  177. # END while we should do an access
  178. elapsed = max(time() - st, 0.001) # prevent zero divison errors on windows
  179. mb = float(1000 * 1000)
  180. print("%s: Read %i mb of memory with %i random on cursor initialized with %s accesses in %fs (%f mb/s)\n"
  181. % (mtype, memory_read / mb, max_random_accesses, type(item), elapsed, (memory_read / mb) / elapsed),
  182. file=sys.stderr)
  183. # an offset as large as the size doesn't work !
  184. assert not c.use_region(fc.size, size).is_valid()
  185. # collection - it should be able to collect all
  186. assert man.num_file_handles()
  187. assert man.collect()
  188. assert man.num_file_handles() == 0
  189. # END for each item
  190. # END for each manager type
  191. finally:
  192. os.close(fd)