ยปCore Development>Code coverage>Lib/timeit.py

Python code coverage for Lib/timeit.py

#countcontent
1n/a#! /usr/bin/env python3
2n/a
3n/a"""Tool for measuring execution time of small code snippets.
4n/a
5n/aThis module avoids a number of common traps for measuring execution
6n/atimes. See also Tim Peters' introduction to the Algorithms chapter in
7n/athe Python Cookbook, published by O'Reilly.
8n/a
9n/aLibrary usage: see the Timer class.
10n/a
11n/aCommand line usage:
12n/a python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [--] [statement]
13n/a
14n/aOptions:
15n/a -n/--number N: how many times to execute 'statement' (default: see below)
16n/a -r/--repeat N: how many times to repeat the timer (default 3)
17n/a -s/--setup S: statement to be executed once initially (default 'pass').
18n/a Execution time of this setup statement is NOT timed.
19n/a -p/--process: use time.process_time() (default is time.perf_counter())
20n/a -v/--verbose: print raw timing results; repeat for more digits precision
21n/a -u/--unit: set the output time unit (nsec, usec, msec, or sec)
22n/a -h/--help: print this usage message and exit
23n/a --: separate options from statement, use when statement starts with -
24n/a statement: statement to be timed (default 'pass')
25n/a
26n/aA multi-line statement may be given by specifying each line as a
27n/aseparate argument; indented lines are possible by enclosing an
28n/aargument in quotes and using leading spaces. Multiple -s options are
29n/atreated similarly.
30n/a
31n/aIf -n is not given, a suitable number of loops is calculated by trying
32n/asuccessive powers of 10 until the total time is at least 0.2 seconds.
33n/a
34n/aNote: there is a certain baseline overhead associated with executing a
35n/apass statement. It differs between versions. The code here doesn't try
36n/ato hide it, but you should be aware of it. The baseline overhead can be
37n/ameasured by invoking the program without arguments.
38n/a
39n/aClasses:
40n/a
41n/a Timer
42n/a
43n/aFunctions:
44n/a
45n/a timeit(string, string) -> float
46n/a repeat(string, string) -> list
47n/a default_timer() -> float
48n/a
49n/a"""
50n/a
51n/aimport gc
52n/aimport sys
53n/aimport time
54n/aimport itertools
55n/a
56n/a__all__ = ["Timer", "timeit", "repeat", "default_timer"]
57n/a
58n/adummy_src_name = "<timeit-src>"
59n/adefault_number = 1000000
60n/adefault_repeat = 5
61n/adefault_timer = time.perf_counter
62n/a
63n/a_globals = globals
64n/a
65n/a# Don't change the indentation of the template; the reindent() calls
66n/a# in Timer.__init__() depend on setup being indented 4 spaces and stmt
67n/a# being indented 8 spaces.
68n/atemplate = """
69n/adef inner(_it, _timer{init}):
70n/a {setup}
71n/a _t0 = _timer()
72n/a for _i in _it:
73n/a {stmt}
74n/a _t1 = _timer()
75n/a return _t1 - _t0
76n/a"""
77n/a
78n/adef reindent(src, indent):
79n/a """Helper to reindent a multi-line statement."""
80n/a return src.replace("\n", "\n" + " "*indent)
81n/a
82n/aclass Timer:
83n/a """Class for timing execution speed of small code snippets.
84n/a
85n/a The constructor takes a statement to be timed, an additional
86n/a statement used for setup, and a timer function. Both statements
87n/a default to 'pass'; the timer function is platform-dependent (see
88n/a module doc string). If 'globals' is specified, the code will be
89n/a executed within that namespace (as opposed to inside timeit's
90n/a namespace).
91n/a
92n/a To measure the execution time of the first statement, use the
93n/a timeit() method. The repeat() method is a convenience to call
94n/a timeit() multiple times and return a list of results.
95n/a
96n/a The statements may contain newlines, as long as they don't contain
97n/a multi-line string literals.
98n/a """
99n/a
100n/a def __init__(self, stmt="pass", setup="pass", timer=default_timer,
101n/a globals=None):
102n/a """Constructor. See class doc string."""
103n/a self.timer = timer
104n/a local_ns = {}
105n/a global_ns = _globals() if globals is None else globals
106n/a init = ''
107n/a if isinstance(setup, str):
108n/a # Check that the code can be compiled outside a function
109n/a compile(setup, dummy_src_name, "exec")
110n/a stmtprefix = setup + '\n'
111n/a setup = reindent(setup, 4)
112n/a elif callable(setup):
113n/a local_ns['_setup'] = setup
114n/a init += ', _setup=_setup'
115n/a stmtprefix = ''
116n/a setup = '_setup()'
117n/a else:
118n/a raise ValueError("setup is neither a string nor callable")
119n/a if isinstance(stmt, str):
120n/a # Check that the code can be compiled outside a function
121n/a compile(stmtprefix + stmt, dummy_src_name, "exec")
122n/a stmt = reindent(stmt, 8)
123n/a elif callable(stmt):
124n/a local_ns['_stmt'] = stmt
125n/a init += ', _stmt=_stmt'
126n/a stmt = '_stmt()'
127n/a else:
128n/a raise ValueError("stmt is neither a string nor callable")
129n/a src = template.format(stmt=stmt, setup=setup, init=init)
130n/a self.src = src # Save for traceback display
131n/a code = compile(src, dummy_src_name, "exec")
132n/a exec(code, global_ns, local_ns)
133n/a self.inner = local_ns["inner"]
134n/a
135n/a def print_exc(self, file=None):
136n/a """Helper to print a traceback from the timed code.
137n/a
138n/a Typical use:
139n/a
140n/a t = Timer(...) # outside the try/except
141n/a try:
142n/a t.timeit(...) # or t.repeat(...)
143n/a except:
144n/a t.print_exc()
145n/a
146n/a The advantage over the standard traceback is that source lines
147n/a in the compiled template will be displayed.
148n/a
149n/a The optional file argument directs where the traceback is
150n/a sent; it defaults to sys.stderr.
151n/a """
152n/a import linecache, traceback
153n/a if self.src is not None:
154n/a linecache.cache[dummy_src_name] = (len(self.src),
155n/a None,
156n/a self.src.split("\n"),
157n/a dummy_src_name)
158n/a # else the source is already stored somewhere else
159n/a
160n/a traceback.print_exc(file=file)
161n/a
162n/a def timeit(self, number=default_number):
163n/a """Time 'number' executions of the main statement.
164n/a
165n/a To be precise, this executes the setup statement once, and
166n/a then returns the time it takes to execute the main statement
167n/a a number of times, as a float measured in seconds. The
168n/a argument is the number of times through the loop, defaulting
169n/a to one million. The main statement, the setup statement and
170n/a the timer function to be used are passed to the constructor.
171n/a """
172n/a it = itertools.repeat(None, number)
173n/a gcold = gc.isenabled()
174n/a gc.disable()
175n/a try:
176n/a timing = self.inner(it, self.timer)
177n/a finally:
178n/a if gcold:
179n/a gc.enable()
180n/a return timing
181n/a
182n/a def repeat(self, repeat=default_repeat, number=default_number):
183n/a """Call timeit() a few times.
184n/a
185n/a This is a convenience function that calls the timeit()
186n/a repeatedly, returning a list of results. The first argument
187n/a specifies how many times to call timeit(), defaulting to 3;
188n/a the second argument specifies the timer argument, defaulting
189n/a to one million.
190n/a
191n/a Note: it's tempting to calculate mean and standard deviation
192n/a from the result vector and report these. However, this is not
193n/a very useful. In a typical case, the lowest value gives a
194n/a lower bound for how fast your machine can run the given code
195n/a snippet; higher values in the result vector are typically not
196n/a caused by variability in Python's speed, but by other
197n/a processes interfering with your timing accuracy. So the min()
198n/a of the result is probably the only number you should be
199n/a interested in. After that, you should look at the entire
200n/a vector and apply common sense rather than statistics.
201n/a """
202n/a r = []
203n/a for i in range(repeat):
204n/a t = self.timeit(number)
205n/a r.append(t)
206n/a return r
207n/a
208n/a def autorange(self, callback=None):
209n/a """Return the number of loops so that total time >= 0.2.
210n/a
211n/a Calls the timeit method with increasing numbers from the sequence
212n/a 1, 2, 5, 10, 20, 50, ... until the time taken is at least 0.2
213n/a second. Returns (number, time_taken).
214n/a
215n/a If *callback* is given and is not None, it will be called after
216n/a each trial with two arguments: ``callback(number, time_taken)``.
217n/a """
218n/a i = 1
219n/a while True:
220n/a for j in 1, 2, 5:
221n/a number = i * j
222n/a time_taken = self.timeit(number)
223n/a if callback:
224n/a callback(number, time_taken)
225n/a if time_taken >= 0.2:
226n/a return (number, time_taken)
227n/a i *= 10
228n/a
229n/adef timeit(stmt="pass", setup="pass", timer=default_timer,
230n/a number=default_number, globals=None):
231n/a """Convenience function to create Timer object and call timeit method."""
232n/a return Timer(stmt, setup, timer, globals).timeit(number)
233n/a
234n/adef repeat(stmt="pass", setup="pass", timer=default_timer,
235n/a repeat=default_repeat, number=default_number, globals=None):
236n/a """Convenience function to create Timer object and call repeat method."""
237n/a return Timer(stmt, setup, timer, globals).repeat(repeat, number)
238n/a
239n/adef main(args=None, *, _wrap_timer=None):
240n/a """Main program, used when run as a script.
241n/a
242n/a The optional 'args' argument specifies the command line to be parsed,
243n/a defaulting to sys.argv[1:].
244n/a
245n/a The return value is an exit code to be passed to sys.exit(); it
246n/a may be None to indicate success.
247n/a
248n/a When an exception happens during timing, a traceback is printed to
249n/a stderr and the return value is 1. Exceptions at other times
250n/a (including the template compilation) are not caught.
251n/a
252n/a '_wrap_timer' is an internal interface used for unit testing. If it
253n/a is not None, it must be a callable that accepts a timer function
254n/a and returns another timer function (used for unit testing).
255n/a """
256n/a if args is None:
257n/a args = sys.argv[1:]
258n/a import getopt
259n/a try:
260n/a opts, args = getopt.getopt(args, "n:u:s:r:tcpvh",
261n/a ["number=", "setup=", "repeat=",
262n/a "time", "clock", "process",
263n/a "verbose", "unit=", "help"])
264n/a except getopt.error as err:
265n/a print(err)
266n/a print("use -h/--help for command line help")
267n/a return 2
268n/a
269n/a timer = default_timer
270n/a stmt = "\n".join(args) or "pass"
271n/a number = 0 # auto-determine
272n/a setup = []
273n/a repeat = default_repeat
274n/a verbose = 0
275n/a time_unit = None
276n/a units = {"nsec": 1e-9, "usec": 1e-6, "msec": 1e-3, "sec": 1.0}
277n/a precision = 3
278n/a for o, a in opts:
279n/a if o in ("-n", "--number"):
280n/a number = int(a)
281n/a if o in ("-s", "--setup"):
282n/a setup.append(a)
283n/a if o in ("-u", "--unit"):
284n/a if a in units:
285n/a time_unit = a
286n/a else:
287n/a print("Unrecognized unit. Please select nsec, usec, msec, or sec.",
288n/a file=sys.stderr)
289n/a return 2
290n/a if o in ("-r", "--repeat"):
291n/a repeat = int(a)
292n/a if repeat <= 0:
293n/a repeat = 1
294n/a if o in ("-p", "--process"):
295n/a timer = time.process_time
296n/a if o in ("-v", "--verbose"):
297n/a if verbose:
298n/a precision += 1
299n/a verbose += 1
300n/a if o in ("-h", "--help"):
301n/a print(__doc__, end=' ')
302n/a return 0
303n/a setup = "\n".join(setup) or "pass"
304n/a
305n/a # Include the current directory, so that local imports work (sys.path
306n/a # contains the directory of this script, rather than the current
307n/a # directory)
308n/a import os
309n/a sys.path.insert(0, os.curdir)
310n/a if _wrap_timer is not None:
311n/a timer = _wrap_timer(timer)
312n/a
313n/a t = Timer(stmt, setup, timer)
314n/a if number == 0:
315n/a # determine number so that 0.2 <= total time < 2.0
316n/a callback = None
317n/a if verbose:
318n/a def callback(number, time_taken):
319n/a msg = "{num} loop{s} -> {secs:.{prec}g} secs"
320n/a plural = (number != 1)
321n/a print(msg.format(num=number, s='s' if plural else '',
322n/a secs=time_taken, prec=precision))
323n/a try:
324n/a number, _ = t.autorange(callback)
325n/a except:
326n/a t.print_exc()
327n/a return 1
328n/a
329n/a if verbose:
330n/a print()
331n/a
332n/a try:
333n/a raw_timings = t.repeat(repeat, number)
334n/a except:
335n/a t.print_exc()
336n/a return 1
337n/a
338n/a def format_time(dt):
339n/a unit = time_unit
340n/a
341n/a if unit is not None:
342n/a scale = units[unit]
343n/a else:
344n/a scales = [(scale, unit) for unit, scale in units.items()]
345n/a scales.sort(reverse=True)
346n/a for scale, unit in scales:
347n/a if dt >= scale:
348n/a break
349n/a
350n/a return "%.*g %s" % (precision, dt / scale, unit)
351n/a
352n/a if verbose:
353n/a print("raw times: %s" % ", ".join(map(format_time, raw_timings)))
354n/a print()
355n/a timings = [dt / number for dt in raw_timings]
356n/a
357n/a best = min(timings)
358n/a print("%d loop%s, best of %d: %s per loop"
359n/a % (number, 's' if number != 1 else '',
360n/a repeat, format_time(best)))
361n/a
362n/a best = min(timings)
363n/a worst = max(timings)
364n/a if worst >= best * 4:
365n/a import warnings
366n/a warnings.warn_explicit("The test results are likely unreliable. "
367n/a "The worst time (%s) was more than four times "
368n/a "slower than the best time (%s)."
369n/a % (format_time(worst), format_time(best)),
370n/a UserWarning, '', 0)
371n/a return None
372n/a
373n/aif __name__ == "__main__":
374n/a sys.exit(main())