]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-80642: timeit - make target time of autorange configurable (#140283)
authorMiikka Koskinen <miikka.koskinen@iki.fi>
Fri, 3 Apr 2026 06:47:21 +0000 (09:47 +0300)
committerGitHub <noreply@github.com>
Fri, 3 Apr 2026 06:47:21 +0000 (06:47 +0000)
Co-authored-by: Alessandro Cucci <alessandro.cucci@gmail.com>
Co-authored-by: blurb-it[bot] <blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Co-authored-by: Stan Ulbrych <stan@python.org>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Doc/library/timeit.rst
Doc/whatsnew/3.15.rst
Lib/test/test_timeit.py
Lib/timeit.py
Misc/NEWS.d/next/Library/2019-04-25-21-11-37.bpo-36461.TO5YyP.rst [new file with mode: 0644]

index bc12061a2aeb2d59eb1de54bc73a4d0df312b18f..aed7e7556f6666e4ecabd138f046ead9eadee737 100644 (file)
@@ -143,21 +143,24 @@ The module defines three convenience functions and a public class:
             timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
 
 
-   .. method:: Timer.autorange(callback=None)
+   .. method:: Timer.autorange(callback=None, target_time=None)
 
       Automatically determine how many times to call :meth:`.timeit`.
 
       This is a convenience function that calls :meth:`.timeit` repeatedly
-      so that the total time >= 0.2 second, returning the eventual
+      so that the total time >= *Timer.target_time* seconds, returning the eventual
       (number of loops, time taken for that number of loops). It calls
       :meth:`.timeit` with increasing numbers from the sequence 1, 2, 5,
-      10, 20, 50, ... until the time taken is at least 0.2 seconds.
+      10, 20, 50, ... until the time taken is at least *target_time* seconds.
 
       If *callback* is given and is not ``None``, it will be called after
       each trial with two arguments: ``callback(number, time_taken)``.
 
       .. versionadded:: 3.6
 
+      .. versionchanged:: next
+         The optional *target_time* parameter was added.
+
 
    .. method:: Timer.repeat(repeat=5, number=1000000)
 
@@ -239,6 +242,13 @@ Where the following options are understood:
 
    .. versionadded:: 3.5
 
+.. option:: -t, --target-time=T
+
+   if :option:`--number` is 0, the code will run until it takes at
+   least this many seconds (default: 0.2)
+
+   .. versionadded:: next
+
 .. option:: -v, --verbose
 
    print raw timing results; repeat for more digits precision
@@ -254,7 +264,7 @@ similarly.
 
 If :option:`-n` is not given, a suitable number of loops is calculated by trying
 increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the total
-time is at least 0.2 seconds.
+time is at least :option:`--target-time` seconds (default: 0.2).
 
 :func:`default_timer` measurements can be affected by other programs running on
 the same machine, so the best thing to do when accurate timing is necessary is
index 2a5f9c939e72806ae7d4c0caded00879c22c0983..287109035f1ee63f1a18f2a238de2fa021b42865 100644 (file)
@@ -1090,6 +1090,11 @@ timeit
   :ref:`environment variables <using-on-controlling-color>`.
   (Contributed by Yi Hong in :gh:`139374`.)
 
+* Make the target time of :meth:`timeit.Timer.autorange` configurable
+  and add ``--target-time`` option to the command-line interface.
+  (Contributed by Alessandro Cucci and Miikka Koskinen in :gh:`140283`.)
+
+
 tkinter
 -------
 
index f8bc306b455a5d275a5945ce71fb15e2a1ea9d91..8837e88ba638cfdbcdbf75e2ff935da22aec7476 100644 (file)
@@ -364,10 +364,10 @@ class TestTimeit(unittest.TestCase):
             s = self.run_main(switches=['-n1', '1/0'])
         self.assert_exc_string(error_stringio.getvalue(), 'ZeroDivisionError')
 
-    def autorange(self, seconds_per_increment=1/1024, callback=None):
+    def autorange(self, seconds_per_increment=1/1024, callback=None, target_time=0.2):
         timer = FakeTimer(seconds_per_increment=seconds_per_increment)
         t = timeit.Timer(stmt=self.fake_stmt, setup=self.fake_setup, timer=timer)
-        return t.autorange(callback)
+        return t.autorange(callback, target_time=target_time)
 
     def test_autorange(self):
         num_loops, time_taken = self.autorange()
@@ -379,6 +379,11 @@ class TestTimeit(unittest.TestCase):
         self.assertEqual(num_loops, 1)
         self.assertEqual(time_taken, 1.0)
 
+    def test_autorange_with_target_time(self):
+        num_loops, time_taken = self.autorange(target_time=1.0)
+        self.assertEqual(num_loops, 2000)
+        self.assertEqual(time_taken, 2000/1024)
+
     def test_autorange_with_callback(self):
         def callback(a, b):
             print("{} {:.3f}".format(a, b))
index 80791acdeca23fab3069754c4ce29ddce28e6641..f900da6ffe7d67a93d2919821864b05adfd1de6e 100644 (file)
@@ -7,7 +7,7 @@ the Python Cookbook, published by O'Reilly.
 Library usage: see the Timer class.
 
 Command line usage:
-    python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [--] [statement]
+    python timeit.py [-n N] [-r N] [-s S] [-p] [-h] [-t T] [--] [statement]
 
 Options:
   -n/--number N: how many times to execute 'statement' (default: see below)
@@ -17,6 +17,9 @@ Options:
   -p/--process: use time.process_time() (default is time.perf_counter())
   -v/--verbose: print raw timing results; repeat for more digits precision
   -u/--unit: set the output time unit (nsec, usec, msec, or sec)
+  -t/--target-time T: if --number is 0 the code will run until it
+                      takes *at least* this many seconds
+                      (default: 0.2)
   -h/--help: print this usage message and exit
   --: separate options from statement, use when statement starts with -
   statement: statement to be timed (default 'pass')
@@ -28,7 +31,7 @@ treated similarly.
 
 If -n is not given, a suitable number of loops is calculated by trying
 increasing numbers from the sequence 1, 2, 5, 10, 20, 50, ... until the
-total time is at least 0.2 seconds.
+total time is at least --target-time seconds.
 
 Note: there is a certain baseline overhead associated with executing a
 pass statement.  It differs between versions.  The code here doesn't try
@@ -57,6 +60,7 @@ dummy_src_name = "<timeit-src>"
 default_number = 1000000
 default_repeat = 5
 default_timer = time.perf_counter
+default_target_time = 0.2
 
 _globals = globals
 
@@ -212,12 +216,13 @@ class Timer:
             r.append(t)
         return r
 
-    def autorange(self, callback=None):
-        """Return the number of loops and time taken so that total time >= 0.2.
+    def autorange(self, callback=None, target_time=default_target_time):
+        """Return the number of loops and time taken so that
+        total time >= target_time (default is 0.2 seconds).
 
         Calls the timeit method with increasing numbers from the sequence
-        1, 2, 5, 10, 20, 50, ... until the time taken is at least 0.2
-        second.  Returns (number, time_taken).
+        1, 2, 5, 10, 20, 50, ... until the target time is reached.
+        Returns (number, time_taken).
 
         If *callback* is given and is not None, it will be called after
         each trial with two arguments: ``callback(number, time_taken)``.
@@ -229,7 +234,7 @@ class Timer:
                 time_taken = self.timeit(number)
                 if callback:
                     callback(number, time_taken)
-                if time_taken >= 0.2:
+                if time_taken >= target_time:
                     return (number, time_taken)
             i *= 10
 
@@ -270,9 +275,10 @@ def main(args=None, *, _wrap_timer=None):
     colorize = _colorize.can_colorize()
 
     try:
-        opts, args = getopt.getopt(args, "n:u:s:r:pvh",
+        opts, args = getopt.getopt(args, "n:u:s:r:pt:vh",
                                    ["number=", "setup=", "repeat=",
-                                    "process", "verbose", "unit=", "help"])
+                                    "process", "target-time=",
+                                    "verbose", "unit=", "help"])
     except getopt.error as err:
         print(err)
         print("use -h/--help for command line help")
@@ -281,6 +287,7 @@ def main(args=None, *, _wrap_timer=None):
     timer = default_timer
     stmt = "\n".join(args) or "pass"
     number = 0  # auto-determine
+    target_time = default_target_time
     setup = []
     repeat = default_repeat
     verbose = 0
@@ -305,6 +312,8 @@ def main(args=None, *, _wrap_timer=None):
                 repeat = 1
         if o in ("-p", "--process"):
             timer = time.process_time
+        if o in ("-t", "--target-time"):
+            target_time = float(a)
         if o in ("-v", "--verbose"):
             if verbose:
                 precision += 1
@@ -324,7 +333,7 @@ def main(args=None, *, _wrap_timer=None):
 
     t = Timer(stmt, setup, timer)
     if number == 0:
-        # determine number so that 0.2 <= total time < 2.0
+        # determine number so that total time >= target_time
         callback = None
         if verbose:
             def callback(number, time_taken):
@@ -333,7 +342,7 @@ def main(args=None, *, _wrap_timer=None):
                 print(msg.format(num=number, s='s' if plural else '',
                                  secs=time_taken, prec=precision))
         try:
-            number, _ = t.autorange(callback)
+            number, _ = t.autorange(callback, target_time)
         except:
             t.print_exc(colorize=colorize)
             return 1
diff --git a/Misc/NEWS.d/next/Library/2019-04-25-21-11-37.bpo-36461.TO5YyP.rst b/Misc/NEWS.d/next/Library/2019-04-25-21-11-37.bpo-36461.TO5YyP.rst
new file mode 100644 (file)
index 0000000..e78f660
--- /dev/null
@@ -0,0 +1,3 @@
+Make the target time of :meth:`timeit.Timer.autorange` configurable\r
+and add ``--target-time`` option to the command-line interface of\r
+:mod:`timeit`.\r