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.

244 lines
8.9 KiB

  1. """Benchmark some basic import use-cases.
  2. The assumption is made that this benchmark is run in a fresh interpreter and
  3. thus has no external changes made to import-related attributes in sys.
  4. """
  5. from test.test_importlib import util
  6. import decimal
  7. import imp
  8. import importlib
  9. import importlib.machinery
  10. import json
  11. import os
  12. import py_compile
  13. import sys
  14. import tabnanny
  15. import timeit
  16. def bench(name, cleanup=lambda: None, *, seconds=1, repeat=3):
  17. """Bench the given statement as many times as necessary until total
  18. executions take one second."""
  19. stmt = "__import__({!r})".format(name)
  20. timer = timeit.Timer(stmt)
  21. for x in range(repeat):
  22. total_time = 0
  23. count = 0
  24. while total_time < seconds:
  25. try:
  26. total_time += timer.timeit(1)
  27. finally:
  28. cleanup()
  29. count += 1
  30. else:
  31. # One execution too far
  32. if total_time > seconds:
  33. count -= 1
  34. yield count // seconds
  35. def from_cache(seconds, repeat):
  36. """sys.modules"""
  37. name = '<benchmark import>'
  38. module = imp.new_module(name)
  39. module.__file__ = '<test>'
  40. module.__package__ = ''
  41. with util.uncache(name):
  42. sys.modules[name] = module
  43. yield from bench(name, repeat=repeat, seconds=seconds)
  44. def builtin_mod(seconds, repeat):
  45. """Built-in module"""
  46. name = 'errno'
  47. if name in sys.modules:
  48. del sys.modules[name]
  49. # Relying on built-in importer being implicit.
  50. yield from bench(name, lambda: sys.modules.pop(name), repeat=repeat,
  51. seconds=seconds)
  52. def source_wo_bytecode(seconds, repeat):
  53. """Source w/o bytecode: small"""
  54. sys.dont_write_bytecode = True
  55. try:
  56. name = '__importlib_test_benchmark__'
  57. # Clears out sys.modules and puts an entry at the front of sys.path.
  58. with util.create_modules(name) as mapping:
  59. assert not os.path.exists(imp.cache_from_source(mapping[name]))
  60. sys.meta_path.append(importlib.machinery.PathFinder)
  61. loader = (importlib.machinery.SourceFileLoader,
  62. importlib.machinery.SOURCE_SUFFIXES)
  63. sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
  64. yield from bench(name, lambda: sys.modules.pop(name), repeat=repeat,
  65. seconds=seconds)
  66. finally:
  67. sys.dont_write_bytecode = False
  68. def _wo_bytecode(module):
  69. name = module.__name__
  70. def benchmark_wo_bytecode(seconds, repeat):
  71. """Source w/o bytecode: {}"""
  72. bytecode_path = imp.cache_from_source(module.__file__)
  73. if os.path.exists(bytecode_path):
  74. os.unlink(bytecode_path)
  75. sys.dont_write_bytecode = True
  76. try:
  77. yield from bench(name, lambda: sys.modules.pop(name),
  78. repeat=repeat, seconds=seconds)
  79. finally:
  80. sys.dont_write_bytecode = False
  81. benchmark_wo_bytecode.__doc__ = benchmark_wo_bytecode.__doc__.format(name)
  82. return benchmark_wo_bytecode
  83. tabnanny_wo_bytecode = _wo_bytecode(tabnanny)
  84. decimal_wo_bytecode = _wo_bytecode(decimal)
  85. def source_writing_bytecode(seconds, repeat):
  86. """Source writing bytecode: small"""
  87. assert not sys.dont_write_bytecode
  88. name = '__importlib_test_benchmark__'
  89. with util.create_modules(name) as mapping:
  90. sys.meta_path.append(importlib.machinery.PathFinder)
  91. loader = (importlib.machinery.SourceFileLoader,
  92. importlib.machinery.SOURCE_SUFFIXES)
  93. sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
  94. def cleanup():
  95. sys.modules.pop(name)
  96. os.unlink(imp.cache_from_source(mapping[name]))
  97. for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
  98. assert not os.path.exists(imp.cache_from_source(mapping[name]))
  99. yield result
  100. def _writing_bytecode(module):
  101. name = module.__name__
  102. def writing_bytecode_benchmark(seconds, repeat):
  103. """Source writing bytecode: {}"""
  104. assert not sys.dont_write_bytecode
  105. def cleanup():
  106. sys.modules.pop(name)
  107. os.unlink(imp.cache_from_source(module.__file__))
  108. yield from bench(name, cleanup, repeat=repeat, seconds=seconds)
  109. writing_bytecode_benchmark.__doc__ = (
  110. writing_bytecode_benchmark.__doc__.format(name))
  111. return writing_bytecode_benchmark
  112. tabnanny_writing_bytecode = _writing_bytecode(tabnanny)
  113. decimal_writing_bytecode = _writing_bytecode(decimal)
  114. def source_using_bytecode(seconds, repeat):
  115. """Source w/ bytecode: small"""
  116. name = '__importlib_test_benchmark__'
  117. with util.create_modules(name) as mapping:
  118. sys.meta_path.append(importlib.machinery.PathFinder)
  119. loader = (importlib.machinery.SourceFileLoader,
  120. importlib.machinery.SOURCE_SUFFIXES)
  121. sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
  122. py_compile.compile(mapping[name])
  123. assert os.path.exists(imp.cache_from_source(mapping[name]))
  124. yield from bench(name, lambda: sys.modules.pop(name), repeat=repeat,
  125. seconds=seconds)
  126. def _using_bytecode(module):
  127. name = module.__name__
  128. def using_bytecode_benchmark(seconds, repeat):
  129. """Source w/ bytecode: {}"""
  130. py_compile.compile(module.__file__)
  131. yield from bench(name, lambda: sys.modules.pop(name), repeat=repeat,
  132. seconds=seconds)
  133. using_bytecode_benchmark.__doc__ = (
  134. using_bytecode_benchmark.__doc__.format(name))
  135. return using_bytecode_benchmark
  136. tabnanny_using_bytecode = _using_bytecode(tabnanny)
  137. decimal_using_bytecode = _using_bytecode(decimal)
  138. def main(import_, options):
  139. if options.source_file:
  140. with options.source_file:
  141. prev_results = json.load(options.source_file)
  142. else:
  143. prev_results = {}
  144. __builtins__.__import__ = import_
  145. benchmarks = (from_cache, builtin_mod,
  146. source_writing_bytecode,
  147. source_wo_bytecode, source_using_bytecode,
  148. tabnanny_writing_bytecode,
  149. tabnanny_wo_bytecode, tabnanny_using_bytecode,
  150. decimal_writing_bytecode,
  151. decimal_wo_bytecode, decimal_using_bytecode,
  152. )
  153. if options.benchmark:
  154. for b in benchmarks:
  155. if b.__doc__ == options.benchmark:
  156. benchmarks = [b]
  157. break
  158. else:
  159. print('Unknown benchmark: {!r}'.format(options.benchmark),
  160. file=sys.stderr)
  161. sys.exit(1)
  162. seconds = 1
  163. seconds_plural = 's' if seconds > 1 else ''
  164. repeat = 3
  165. header = ('Measuring imports/second over {} second{}, best out of {}\n'
  166. 'Entire benchmark run should take about {} seconds\n'
  167. 'Using {!r} as __import__\n')
  168. print(header.format(seconds, seconds_plural, repeat,
  169. len(benchmarks) * seconds * repeat, __import__))
  170. new_results = {}
  171. for benchmark in benchmarks:
  172. print(benchmark.__doc__, "[", end=' ')
  173. sys.stdout.flush()
  174. results = []
  175. for result in benchmark(seconds=seconds, repeat=repeat):
  176. results.append(result)
  177. print(result, end=' ')
  178. sys.stdout.flush()
  179. assert not sys.dont_write_bytecode
  180. print("]", "best is", format(max(results), ',d'))
  181. new_results[benchmark.__doc__] = results
  182. if prev_results:
  183. print('\n\nComparing new vs. old\n')
  184. for benchmark in benchmarks:
  185. benchmark_name = benchmark.__doc__
  186. old_result = max(prev_results[benchmark_name])
  187. new_result = max(new_results[benchmark_name])
  188. result = '{:,d} vs. {:,d} ({:%})'.format(new_result,
  189. old_result,
  190. new_result/old_result)
  191. print(benchmark_name, ':', result)
  192. if options.dest_file:
  193. with options.dest_file:
  194. json.dump(new_results, options.dest_file, indent=2)
  195. if __name__ == '__main__':
  196. import argparse
  197. parser = argparse.ArgumentParser()
  198. parser.add_argument('-b', '--builtin', dest='builtin', action='store_true',
  199. default=False, help="use the built-in __import__")
  200. parser.add_argument('-r', '--read', dest='source_file',
  201. type=argparse.FileType('r'),
  202. help='file to read benchmark data from to compare '
  203. 'against')
  204. parser.add_argument('-w', '--write', dest='dest_file',
  205. type=argparse.FileType('w'),
  206. help='file to write benchmark data to')
  207. parser.add_argument('--benchmark', dest='benchmark',
  208. help='specific benchmark to run')
  209. options = parser.parse_args()
  210. import_ = __import__
  211. if not options.builtin:
  212. import_ = importlib.__import__
  213. main(import_, options)