blob: 16863146b5a2b3b5c5c04702a4de2d7ffc1c76e9 [file] [log] [blame]
Brett Cannon3b0a19e2010-07-16 19:04:29 +00001"""Benchmark some basic import use-cases.
2
3The assumption is made that this benchmark is run in a fresh interpreter and
4thus has no external changes made to import-related attributes in sys.
5
6"""
Brett Cannon6ae7a7d2009-03-30 15:53:01 +00007from . import util
8from .source import util as source_util
Brett Cannon5db0c942010-07-22 07:40:56 +00009import decimal
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000010import imp
11import importlib
Brett Cannon810c64d2012-05-11 11:12:00 -040012import importlib._bootstrap
13import importlib.machinery
Brett Cannon190f33c2012-01-30 19:12:29 -050014import json
Brett Cannon3b0a19e2010-07-16 19:04:29 +000015import os
16import py_compile
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000017import sys
Brett Cannon466e6a92012-02-07 09:19:12 -050018import tabnanny
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000019import timeit
20
21
Brett Cannon23cf5742009-09-03 20:45:21 +000022def bench(name, cleanup=lambda: None, *, seconds=1, repeat=3):
23 """Bench the given statement as many times as necessary until total
24 executions take one second."""
25 stmt = "__import__({!r})".format(name)
26 timer = timeit.Timer(stmt)
27 for x in range(repeat):
28 total_time = 0
29 count = 0
30 while total_time < seconds:
31 try:
32 total_time += timer.timeit(1)
33 finally:
34 cleanup()
35 count += 1
36 else:
37 # One execution too far
38 if total_time > seconds:
39 count -= 1
Brett Cannon7b9bcb82010-07-15 06:24:04 +000040 yield count // seconds
Brett Cannon23cf5742009-09-03 20:45:21 +000041
Brett Cannon3b0a19e2010-07-16 19:04:29 +000042def from_cache(seconds, repeat):
Brett Cannon23cf5742009-09-03 20:45:21 +000043 """sys.modules"""
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000044 name = '<benchmark import>'
Brett Cannon23cf5742009-09-03 20:45:21 +000045 module = imp.new_module(name)
46 module.__file__ = '<test>'
47 module.__package__ = ''
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000048 with util.uncache(name):
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000049 sys.modules[name] = module
Brett Cannon3b0a19e2010-07-16 19:04:29 +000050 for result in bench(name, repeat=repeat, seconds=seconds):
Brett Cannon23cf5742009-09-03 20:45:21 +000051 yield result
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000052
53
Brett Cannon3b0a19e2010-07-16 19:04:29 +000054def builtin_mod(seconds, repeat):
Brett Cannon23cf5742009-09-03 20:45:21 +000055 """Built-in module"""
56 name = 'errno'
57 if name in sys.modules:
58 del sys.modules[name]
Brett Cannon7b9bcb82010-07-15 06:24:04 +000059 # Relying on built-in importer being implicit.
Brett Cannon3b0a19e2010-07-16 19:04:29 +000060 for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
61 seconds=seconds):
Brett Cannon23cf5742009-09-03 20:45:21 +000062 yield result
Brett Cannon6ae7a7d2009-03-30 15:53:01 +000063
64
Brett Cannon3b0a19e2010-07-16 19:04:29 +000065def source_wo_bytecode(seconds, repeat):
Brett Cannon466e6a92012-02-07 09:19:12 -050066 """Source w/o bytecode: small"""
Brett Cannon3b0a19e2010-07-16 19:04:29 +000067 sys.dont_write_bytecode = True
68 try:
69 name = '__importlib_test_benchmark__'
70 # Clears out sys.modules and puts an entry at the front of sys.path.
71 with source_util.create_modules(name) as mapping:
72 assert not os.path.exists(imp.cache_from_source(mapping[name]))
Brett Cannon810c64d2012-05-11 11:12:00 -040073 sys.meta_path.append(importlib.machinery.PathFinder)
74 loader = (importlib.machinery.SourceFileLoader,
75 importlib._bootstrap._SOURCE_SUFFIXES, True)
76 sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
Brett Cannon3b0a19e2010-07-16 19:04:29 +000077 for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
78 seconds=seconds):
79 yield result
80 finally:
81 sys.dont_write_bytecode = False
82
83
Brett Cannon466e6a92012-02-07 09:19:12 -050084def _wo_bytecode(module):
85 name = module.__name__
86 def benchmark_wo_bytecode(seconds, repeat):
87 """Source w/o bytecode: {}"""
88 bytecode_path = imp.cache_from_source(module.__file__)
89 if os.path.exists(bytecode_path):
90 os.unlink(bytecode_path)
91 sys.dont_write_bytecode = True
92 try:
93 for result in bench(name, lambda: sys.modules.pop(name),
94 repeat=repeat, seconds=seconds):
95 yield result
96 finally:
97 sys.dont_write_bytecode = False
98
99 benchmark_wo_bytecode.__doc__ = benchmark_wo_bytecode.__doc__.format(name)
100 return benchmark_wo_bytecode
101
102tabnanny_wo_bytecode = _wo_bytecode(tabnanny)
103decimal_wo_bytecode = _wo_bytecode(decimal)
Brett Cannon5db0c942010-07-22 07:40:56 +0000104
105
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000106def source_writing_bytecode(seconds, repeat):
Brett Cannon466e6a92012-02-07 09:19:12 -0500107 """Source writing bytecode: small"""
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000108 assert not sys.dont_write_bytecode
109 name = '__importlib_test_benchmark__'
110 with source_util.create_modules(name) as mapping:
Brett Cannon810c64d2012-05-11 11:12:00 -0400111 sys.meta_path.append(importlib.machinery.PathFinder)
112 loader = (importlib.machinery.SourceFileLoader,
113 importlib._bootstrap._SOURCE_SUFFIXES, True)
114 sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000115 def cleanup():
116 sys.modules.pop(name)
117 os.unlink(imp.cache_from_source(mapping[name]))
118 for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
119 assert not os.path.exists(imp.cache_from_source(mapping[name]))
120 yield result
121
122
Brett Cannon466e6a92012-02-07 09:19:12 -0500123def _writing_bytecode(module):
124 name = module.__name__
125 def writing_bytecode_benchmark(seconds, repeat):
126 """Source writing bytecode: {}"""
127 assert not sys.dont_write_bytecode
128 def cleanup():
129 sys.modules.pop(name)
130 os.unlink(imp.cache_from_source(module.__file__))
131 for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
132 yield result
133
134 writing_bytecode_benchmark.__doc__ = (
135 writing_bytecode_benchmark.__doc__.format(name))
136 return writing_bytecode_benchmark
137
138tabnanny_writing_bytecode = _writing_bytecode(tabnanny)
139decimal_writing_bytecode = _writing_bytecode(decimal)
Brett Cannon5db0c942010-07-22 07:40:56 +0000140
141
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000142def source_using_bytecode(seconds, repeat):
Brett Cannon466e6a92012-02-07 09:19:12 -0500143 """Source w/ bytecode: small"""
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000144 name = '__importlib_test_benchmark__'
145 with source_util.create_modules(name) as mapping:
Brett Cannon810c64d2012-05-11 11:12:00 -0400146 sys.meta_path.append(importlib.machinery.PathFinder)
147 loader = (importlib.machinery.SourceFileLoader,
148 importlib._bootstrap._SOURCE_SUFFIXES, True)
149 sys.path_hooks.append(importlib.machinery.FileFinder.path_hook(loader))
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000150 py_compile.compile(mapping[name])
151 assert os.path.exists(imp.cache_from_source(mapping[name]))
152 for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
153 seconds=seconds):
154 yield result
155
156
Brett Cannon466e6a92012-02-07 09:19:12 -0500157def _using_bytecode(module):
158 name = module.__name__
159 def using_bytecode_benchmark(seconds, repeat):
160 """Source w/ bytecode: {}"""
161 py_compile.compile(module.__file__)
162 for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
163 seconds=seconds):
164 yield result
165
166 using_bytecode_benchmark.__doc__ = (
167 using_bytecode_benchmark.__doc__.format(name))
168 return using_bytecode_benchmark
169
170tabnanny_using_bytecode = _using_bytecode(tabnanny)
171decimal_using_bytecode = _using_bytecode(decimal)
Brett Cannon5db0c942010-07-22 07:40:56 +0000172
173
Brett Cannondfc32702012-02-23 19:34:35 -0500174def main(import_, options):
175 if options.source_file:
176 with options.source_file:
177 prev_results = json.load(options.source_file)
Brett Cannon190f33c2012-01-30 19:12:29 -0500178 else:
179 prev_results = {}
Brett Cannon23cf5742009-09-03 20:45:21 +0000180 __builtins__.__import__ = import_
Brett Cannon5db0c942010-07-22 07:40:56 +0000181 benchmarks = (from_cache, builtin_mod,
Brett Cannon5db0c942010-07-22 07:40:56 +0000182 source_writing_bytecode,
Brett Cannoncae10682012-02-07 09:40:33 -0500183 source_wo_bytecode, source_using_bytecode,
Brett Cannon466e6a92012-02-07 09:19:12 -0500184 tabnanny_writing_bytecode,
Brett Cannoncae10682012-02-07 09:40:33 -0500185 tabnanny_wo_bytecode, tabnanny_using_bytecode,
186 decimal_writing_bytecode,
187 decimal_wo_bytecode, decimal_using_bytecode,
188 )
Brett Cannondfc32702012-02-23 19:34:35 -0500189 if options.benchmark:
Brett Cannone3a9ae52012-01-30 19:27:51 -0500190 for b in benchmarks:
Brett Cannondfc32702012-02-23 19:34:35 -0500191 if b.__doc__ == options.benchmark:
Brett Cannone3a9ae52012-01-30 19:27:51 -0500192 benchmarks = [b]
193 break
194 else:
Brett Cannondfc32702012-02-23 19:34:35 -0500195 print('Unknown benchmark: {!r}'.format(options.benchmark,
196 file=sys.stderr))
Brett Cannone3a9ae52012-01-30 19:27:51 -0500197 sys.exit(1)
Brett Cannon5db0c942010-07-22 07:40:56 +0000198 seconds = 1
199 seconds_plural = 's' if seconds > 1 else ''
200 repeat = 3
Brett Cannon190f33c2012-01-30 19:12:29 -0500201 header = ('Measuring imports/second over {} second{}, best out of {}\n'
Brett Cannoncae10682012-02-07 09:40:33 -0500202 'Entire benchmark run should take about {} seconds\n'
203 'Using {!r} as __import__\n')
Brett Cannon190f33c2012-01-30 19:12:29 -0500204 print(header.format(seconds, seconds_plural, repeat,
Brett Cannoncae10682012-02-07 09:40:33 -0500205 len(benchmarks) * seconds * repeat, __import__))
Brett Cannon190f33c2012-01-30 19:12:29 -0500206 new_results = {}
Brett Cannon23cf5742009-09-03 20:45:21 +0000207 for benchmark in benchmarks:
208 print(benchmark.__doc__, "[", end=' ')
209 sys.stdout.flush()
210 results = []
Brett Cannon5db0c942010-07-22 07:40:56 +0000211 for result in benchmark(seconds=seconds, repeat=repeat):
Brett Cannon23cf5742009-09-03 20:45:21 +0000212 results.append(result)
213 print(result, end=' ')
214 sys.stdout.flush()
Brett Cannon3b0a19e2010-07-16 19:04:29 +0000215 assert not sys.dont_write_bytecode
Brett Cannoncbe1a4e2010-07-16 19:26:23 +0000216 print("]", "best is", format(max(results), ',d'))
Brett Cannon190f33c2012-01-30 19:12:29 -0500217 new_results[benchmark.__doc__] = results
Brett Cannondfc32702012-02-23 19:34:35 -0500218 if prev_results:
219 print('\n\nComparing new vs. old\n')
Brett Cannon190f33c2012-01-30 19:12:29 -0500220 for benchmark in benchmarks:
221 benchmark_name = benchmark.__doc__
Brett Cannondfc32702012-02-23 19:34:35 -0500222 old_result = max(prev_results[benchmark_name])
223 new_result = max(new_results[benchmark_name])
224 result = '{:,d} vs. {:,d} ({:%})'.format(new_result,
225 old_result,
226 new_result/old_result)
Brett Cannon190f33c2012-01-30 19:12:29 -0500227 print(benchmark_name, ':', result)
Brett Cannondfc32702012-02-23 19:34:35 -0500228 if options.dest_file:
229 with options.dest_file:
230 json.dump(new_results, options.dest_file, indent=2)
Brett Cannon6ae7a7d2009-03-30 15:53:01 +0000231
232
233if __name__ == '__main__':
Brett Cannon190f33c2012-01-30 19:12:29 -0500234 import argparse
Brett Cannon6ae7a7d2009-03-30 15:53:01 +0000235
Brett Cannon190f33c2012-01-30 19:12:29 -0500236 parser = argparse.ArgumentParser()
237 parser.add_argument('-b', '--builtin', dest='builtin', action='store_true',
Brett Cannon6ae7a7d2009-03-30 15:53:01 +0000238 default=False, help="use the built-in __import__")
Brett Cannondfc32702012-02-23 19:34:35 -0500239 parser.add_argument('-r', '--read', dest='source_file',
240 type=argparse.FileType('r'),
241 help='file to read benchmark data from to compare '
242 'against')
243 parser.add_argument('-w', '--write', dest='dest_file',
244 type=argparse.FileType('w'),
245 help='file to write benchmark data to')
Brett Cannone3a9ae52012-01-30 19:27:51 -0500246 parser.add_argument('--benchmark', dest='benchmark',
Brett Cannondfc32702012-02-23 19:34:35 -0500247 help='specific benchmark to run')
Brett Cannon190f33c2012-01-30 19:12:29 -0500248 options = parser.parse_args()
Brett Cannon6ae7a7d2009-03-30 15:53:01 +0000249 import_ = __import__
250 if not options.builtin:
251 import_ = importlib.__import__
252
Brett Cannondfc32702012-02-23 19:34:35 -0500253 main(import_, options)