No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

241 líneas
7.8KB

  1. """Module containing a memory memory manager which provides a sliding window on a number of memory mapped files"""
  2. import os
  3. import sys
  4. from mmap import mmap, ACCESS_READ
  5. from mmap import ALLOCATIONGRANULARITY
  6. __all__ = ["align_to_mmap", "is_64_bit", "buffer",
  7. "MapWindow", "MapRegion", "MapRegionList", "ALLOCATIONGRANULARITY"]
  8. #{ Utilities
  9. try:
  10. # Python 2
  11. buffer = buffer
  12. except NameError:
  13. # Python 3 has no `buffer`; only `memoryview`
  14. def buffer(obj, offset, size):
  15. # Actually, for gitpython this is fastest ... .
  16. return memoryview(obj)[offset:offset+size]
  17. # doing it directly is much faster !
  18. # return obj[offset:offset + size]
  19. def string_types():
  20. if sys.version_info[0] >= 3:
  21. return str
  22. else:
  23. return basestring
  24. def align_to_mmap(num, round_up):
  25. """
  26. Align the given integer number to the closest page offset, which usually is 4096 bytes.
  27. :param round_up: if True, the next higher multiple of page size is used, otherwise
  28. the lower page_size will be used (i.e. if True, 1 becomes 4096, otherwise it becomes 0)
  29. :return: num rounded to closest page"""
  30. res = (num // ALLOCATIONGRANULARITY) * ALLOCATIONGRANULARITY
  31. if round_up and (res != num):
  32. res += ALLOCATIONGRANULARITY
  33. # END handle size
  34. return res
  35. def is_64_bit():
  36. """:return: True if the system is 64 bit. Otherwise it can be assumed to be 32 bit"""
  37. return sys.maxsize > (1 << 32) - 1
  38. #}END utilities
  39. #{ Utility Classes
  40. class MapWindow(object):
  41. """Utility type which is used to snap windows towards each other, and to adjust their size"""
  42. __slots__ = (
  43. 'ofs', # offset into the file in bytes
  44. 'size' # size of the window in bytes
  45. )
  46. def __init__(self, offset, size):
  47. self.ofs = offset
  48. self.size = size
  49. def __repr__(self):
  50. return "MapWindow(%i, %i)" % (self.ofs, self.size)
  51. @classmethod
  52. def from_region(cls, region):
  53. """:return: new window from a region"""
  54. return cls(region._b, region.size())
  55. def ofs_end(self):
  56. return self.ofs + self.size
  57. def align(self):
  58. """Assures the previous window area is contained in the new one"""
  59. nofs = align_to_mmap(self.ofs, 0)
  60. self.size += self.ofs - nofs # keep size constant
  61. self.ofs = nofs
  62. self.size = align_to_mmap(self.size, 1)
  63. def extend_left_to(self, window, max_size):
  64. """Adjust the offset to start where the given window on our left ends if possible,
  65. but don't make yourself larger than max_size.
  66. The resize will assure that the new window still contains the old window area"""
  67. rofs = self.ofs - window.ofs_end()
  68. nsize = rofs + self.size
  69. rofs -= nsize - min(nsize, max_size)
  70. self.ofs = self.ofs - rofs
  71. self.size += rofs
  72. def extend_right_to(self, window, max_size):
  73. """Adjust the size to make our window end where the right window begins, but don't
  74. get larger than max_size"""
  75. self.size = min(self.size + (window.ofs - self.ofs_end()), max_size)
  76. class MapRegion(object):
  77. """Defines a mapped region of memory, aligned to pagesizes
  78. **Note:** deallocates used region automatically on destruction"""
  79. __slots__ = [
  80. '_b', # beginning of mapping
  81. '_mf', # mapped memory chunk (as returned by mmap)
  82. '_uc', # total amount of usages
  83. '_size', # cached size of our memory map
  84. '__weakref__'
  85. ]
  86. #{ Configuration
  87. #} END configuration
  88. def __init__(self, path_or_fd, ofs, size, flags=0):
  89. """Initialize a region, allocate the memory map
  90. :param path_or_fd: path to the file to map, or the opened file descriptor
  91. :param ofs: **aligned** offset into the file to be mapped
  92. :param size: if size is larger then the file on disk, the whole file will be
  93. allocated the the size automatically adjusted
  94. :param flags: additional flags to be given when opening the file.
  95. :raise Exception: if no memory can be allocated"""
  96. self._b = ofs
  97. self._size = 0
  98. self._uc = 0
  99. if isinstance(path_or_fd, int):
  100. fd = path_or_fd
  101. else:
  102. fd = os.open(path_or_fd, os.O_RDONLY | getattr(os, 'O_BINARY', 0) | flags)
  103. # END handle fd
  104. try:
  105. kwargs = dict(access=ACCESS_READ, offset=ofs)
  106. corrected_size = size
  107. sizeofs = ofs
  108. # have to correct size, otherwise (instead of the c version) it will
  109. # bark that the size is too large ... many extra file accesses because
  110. # if this ... argh !
  111. actual_size = min(os.fstat(fd).st_size - sizeofs, corrected_size)
  112. self._mf = mmap(fd, actual_size, **kwargs)
  113. # END handle memory mode
  114. self._size = len(self._mf)
  115. finally:
  116. if isinstance(path_or_fd, string_types()):
  117. os.close(fd)
  118. # END only close it if we opened it
  119. # END close file handle
  120. # We assume the first one to use us keeps us around
  121. self.increment_client_count()
  122. def __repr__(self):
  123. return "MapRegion<%i, %i>" % (self._b, self.size())
  124. #{ Interface
  125. def buffer(self):
  126. """:return: a buffer containing the memory"""
  127. return self._mf
  128. def map(self):
  129. """:return: a memory map containing the memory"""
  130. return self._mf
  131. def ofs_begin(self):
  132. """:return: absolute byte offset to the first byte of the mapping"""
  133. return self._b
  134. def size(self):
  135. """:return: total size of the mapped region in bytes"""
  136. return self._size
  137. def ofs_end(self):
  138. """:return: Absolute offset to one byte beyond the mapping into the file"""
  139. return self._b + self._size
  140. def includes_ofs(self, ofs):
  141. """:return: True if the given offset can be read in our mapped region"""
  142. return self._b <= ofs < self._b + self._size
  143. def client_count(self):
  144. """:return: number of clients currently using this region"""
  145. return self._uc
  146. def increment_client_count(self, ofs = 1):
  147. """Adjust the usage count by the given positive or negative offset.
  148. If usage count equals 0, we will auto-release our resources
  149. :return: True if we released resources, False otherwise. In the latter case, we can still be used"""
  150. self._uc += ofs
  151. assert self._uc > -1, "Increments must match decrements, usage counter negative: %i" % self._uc
  152. if self.client_count() == 0:
  153. self.release()
  154. return True
  155. else:
  156. return False
  157. # end handle release
  158. def release(self):
  159. """Release all resources this instance might hold. Must only be called if there usage_count() is zero"""
  160. self._mf.close()
  161. #} END interface
  162. class MapRegionList(list):
  163. """List of MapRegion instances associating a path with a list of regions."""
  164. __slots__ = (
  165. '_path_or_fd', # path or file descriptor which is mapped by all our regions
  166. '_file_size' # total size of the file we map
  167. )
  168. def __new__(cls, path):
  169. return super(MapRegionList, cls).__new__(cls)
  170. def __init__(self, path_or_fd):
  171. self._path_or_fd = path_or_fd
  172. self._file_size = None
  173. def path_or_fd(self):
  174. """:return: path or file descriptor we are attached to"""
  175. return self._path_or_fd
  176. def file_size(self):
  177. """:return: size of file we manager"""
  178. if self._file_size is None:
  179. if isinstance(self._path_or_fd, string_types()):
  180. self._file_size = os.stat(self._path_or_fd).st_size
  181. else:
  182. self._file_size = os.fstat(self._path_or_fd).st_size
  183. # END handle path type
  184. # END update file size
  185. return self._file_size
  186. #} END utility classes