You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
9.3 KiB

6 years ago
  1. #
  2. # Copyright (c) 2001 - 2017 The SCons Foundation
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included
  13. # in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  16. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  17. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. #
  23. from __future__ import print_function
  24. __revision__ = "src/engine/SCons/Memoize.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
  25. __doc__ = """Memoizer
  26. A decorator-based implementation to count hits and misses of the computed
  27. values that various methods cache in memory.
  28. Use of this modules assumes that wrapped methods be coded to cache their
  29. values in a consistent way. In particular, it requires that the class uses a
  30. dictionary named "_memo" to store the cached values.
  31. Here is an example of wrapping a method that returns a computed value,
  32. with no input parameters::
  33. @SCons.Memoize.CountMethodCall
  34. def foo(self):
  35. try: # Memoization
  36. return self._memo['foo'] # Memoization
  37. except KeyError: # Memoization
  38. pass # Memoization
  39. result = self.compute_foo_value()
  40. self._memo['foo'] = result # Memoization
  41. return result
  42. Here is an example of wrapping a method that will return different values
  43. based on one or more input arguments::
  44. def _bar_key(self, argument): # Memoization
  45. return argument # Memoization
  46. @SCons.Memoize.CountDictCall(_bar_key)
  47. def bar(self, argument):
  48. memo_key = argument # Memoization
  49. try: # Memoization
  50. memo_dict = self._memo['bar'] # Memoization
  51. except KeyError: # Memoization
  52. memo_dict = {} # Memoization
  53. self._memo['dict'] = memo_dict # Memoization
  54. else: # Memoization
  55. try: # Memoization
  56. return memo_dict[memo_key] # Memoization
  57. except KeyError: # Memoization
  58. pass # Memoization
  59. result = self.compute_bar_value(argument)
  60. memo_dict[memo_key] = result # Memoization
  61. return result
  62. Deciding what to cache is tricky, because different configurations
  63. can have radically different performance tradeoffs, and because the
  64. tradeoffs involved are often so non-obvious. Consequently, deciding
  65. whether or not to cache a given method will likely be more of an art than
  66. a science, but should still be based on available data from this module.
  67. Here are some VERY GENERAL guidelines about deciding whether or not to
  68. cache return values from a method that's being called a lot:
  69. -- The first question to ask is, "Can we change the calling code
  70. so this method isn't called so often?" Sometimes this can be
  71. done by changing the algorithm. Sometimes the *caller* should
  72. be memoized, not the method you're looking at.
  73. -- The memoized function should be timed with multiple configurations
  74. to make sure it doesn't inadvertently slow down some other
  75. configuration.
  76. -- When memoizing values based on a dictionary key composed of
  77. input arguments, you don't need to use all of the arguments
  78. if some of them don't affect the return values.
  79. """
  80. # A flag controlling whether or not we actually use memoization.
  81. use_memoizer = None
  82. # Global list of counter objects
  83. CounterList = {}
  84. class Counter(object):
  85. """
  86. Base class for counting memoization hits and misses.
  87. We expect that the initialization in a matching decorator will
  88. fill in the correct class name and method name that represents
  89. the name of the function being counted.
  90. """
  91. def __init__(self, cls_name, method_name):
  92. """
  93. """
  94. self.cls_name = cls_name
  95. self.method_name = method_name
  96. self.hit = 0
  97. self.miss = 0
  98. def key(self):
  99. return self.cls_name+'.'+self.method_name
  100. def display(self):
  101. print(" {:7d} hits {:7d} misses {}()".format(self.hit, self.miss, self.key()))
  102. def __eq__(self, other):
  103. try:
  104. return self.key() == other.key()
  105. except AttributeError:
  106. return True
  107. class CountValue(Counter):
  108. """
  109. A counter class for simple, atomic memoized values.
  110. A CountValue object should be instantiated in a decorator for each of
  111. the class's methods that memoizes its return value by simply storing
  112. the return value in its _memo dictionary.
  113. """
  114. def count(self, *args, **kw):
  115. """ Counts whether the memoized value has already been
  116. set (a hit) or not (a miss).
  117. """
  118. obj = args[0]
  119. if self.method_name in obj._memo:
  120. self.hit = self.hit + 1
  121. else:
  122. self.miss = self.miss + 1
  123. class CountDict(Counter):
  124. """
  125. A counter class for memoized values stored in a dictionary, with
  126. keys based on the method's input arguments.
  127. A CountDict object is instantiated in a decorator for each of the
  128. class's methods that memoizes its return value in a dictionary,
  129. indexed by some key that can be computed from one or more of
  130. its input arguments.
  131. """
  132. def __init__(self, cls_name, method_name, keymaker):
  133. """
  134. """
  135. Counter.__init__(self, cls_name, method_name)
  136. self.keymaker = keymaker
  137. def count(self, *args, **kw):
  138. """ Counts whether the computed key value is already present
  139. in the memoization dictionary (a hit) or not (a miss).
  140. """
  141. obj = args[0]
  142. try:
  143. memo_dict = obj._memo[self.method_name]
  144. except KeyError:
  145. self.miss = self.miss + 1
  146. else:
  147. key = self.keymaker(*args, **kw)
  148. if key in memo_dict:
  149. self.hit = self.hit + 1
  150. else:
  151. self.miss = self.miss + 1
  152. def Dump(title=None):
  153. """ Dump the hit/miss count for all the counters
  154. collected so far.
  155. """
  156. if title:
  157. print(title)
  158. for counter in sorted(CounterList):
  159. CounterList[counter].display()
  160. def EnableMemoization():
  161. global use_memoizer
  162. use_memoizer = 1
  163. def CountMethodCall(fn):
  164. """ Decorator for counting memoizer hits/misses while retrieving
  165. a simple value in a class method. It wraps the given method
  166. fn and uses a CountValue object to keep track of the
  167. caching statistics.
  168. Wrapping gets enabled by calling EnableMemoization().
  169. """
  170. if use_memoizer:
  171. def wrapper(self, *args, **kwargs):
  172. global CounterList
  173. key = self.__class__.__name__+'.'+fn.__name__
  174. if key not in CounterList:
  175. CounterList[key] = CountValue(self.__class__.__name__, fn.__name__)
  176. CounterList[key].count(self, *args, **kwargs)
  177. return fn(self, *args, **kwargs)
  178. wrapper.__name__= fn.__name__
  179. return wrapper
  180. else:
  181. return fn
  182. def CountDictCall(keyfunc):
  183. """ Decorator for counting memoizer hits/misses while accessing
  184. dictionary values with a key-generating function. Like
  185. CountMethodCall above, it wraps the given method
  186. fn and uses a CountDict object to keep track of the
  187. caching statistics. The dict-key function keyfunc has to
  188. get passed in the decorator call and gets stored in the
  189. CountDict instance.
  190. Wrapping gets enabled by calling EnableMemoization().
  191. """
  192. def decorator(fn):
  193. if use_memoizer:
  194. def wrapper(self, *args, **kwargs):
  195. global CounterList
  196. key = self.__class__.__name__+'.'+fn.__name__
  197. if key not in CounterList:
  198. CounterList[key] = CountDict(self.__class__.__name__, fn.__name__, keyfunc)
  199. CounterList[key].count(self, *args, **kwargs)
  200. return fn(self, *args, **kwargs)
  201. wrapper.__name__= fn.__name__
  202. return wrapper
  203. else:
  204. return fn
  205. return decorator
  206. # Local Variables:
  207. # tab-width:4
  208. # indent-tabs-mode:nil
  209. # End:
  210. # vim: set expandtab tabstop=4 shiftwidth=4: