From e7bd81d63073c78103c92e371086b4cd21c1d1a1 Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Wed, 19 Mar 2025 17:05:04 +0100 Subject: [PATCH 1/3] use time.perf_counter in Stopwatch --- Lib/test/support/__init__.py | 21 ++++++++++----------- Lib/test/test_int.py | 12 ++++++------ Lib/test/test_re.py | 4 ++-- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index b35ce1b2285171..c09474455cf5c3 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2578,30 +2578,29 @@ def sleeping_retry(timeout, err_msg=None, /, delay = min(delay * 2, max_delay) -class CPUStopwatch: +class Stopwatch: """Context manager to roughly time a CPU-bound operation. - Disables GC. Uses CPU time if it can (i.e. excludes sleeps & time of - other processes). + Disables GC. Uses perf_counter, i.e. a clock with the highest + available resolution. It is chosen even though it does include + time elapsed during sleep and is system-wide, because the + resolution of process_time is too coarse on Windows and + process_time does not exist everywhere (e.g. WASM). N.B.: - - This *includes* time spent in other threads. - Some systems only have a coarse resolution; check - stopwatch.clock_info.rseolution if. + stopwatch.clock_info.resolution if. Usage: - with ProcessStopwatch() as stopwatch: + with Stopwatch() as stopwatch: ... elapsed = stopwatch.seconds resolution = stopwatch.clock_info.resolution """ def __enter__(self): - get_time = time.process_time - clock_info = time.get_clock_info('process_time') - if get_time() <= 0: # some platforms like WASM lack process_time() - get_time = time.monotonic - clock_info = time.get_clock_info('monotonic') + get_time = time.perf_counter + clock_info = time.get_clock_info('perf_counter') self.context = disable_gc() self.context.__enter__() self.get_time = get_time diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index 8870d7aa5d663d..cbe9164b051a6d 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -588,7 +588,7 @@ def test_denial_of_service_prevented_int_to_str(self): digits = 78_268 with ( support.adjust_int_max_str_digits(digits), - support.CPUStopwatch() as sw_convert): + support.Stopwatch() as sw_convert): huge_decimal = str(huge_int) self.assertEqual(len(huge_decimal), digits) # Ensuring that we chose a slow enough conversion to measure. @@ -603,7 +603,7 @@ def test_denial_of_service_prevented_int_to_str(self): with support.adjust_int_max_str_digits(int(.995 * digits)): with ( self.assertRaises(ValueError) as err, - support.CPUStopwatch() as sw_fail_huge): + support.Stopwatch() as sw_fail_huge): str(huge_int) self.assertIn('conversion', str(err.exception)) self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) @@ -613,7 +613,7 @@ def test_denial_of_service_prevented_int_to_str(self): extra_huge_int = int(f'0x{"c"*500_000}', base=16) # 602060 digits. with ( self.assertRaises(ValueError) as err, - support.CPUStopwatch() as sw_fail_extra_huge): + support.Stopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds said Zen based cloud VM. str(extra_huge_int) self.assertIn('conversion', str(err.exception)) @@ -628,7 +628,7 @@ def test_denial_of_service_prevented_str_to_int(self): huge = '8'*digits with ( support.adjust_int_max_str_digits(digits), - support.CPUStopwatch() as sw_convert): + support.Stopwatch() as sw_convert): int(huge) # Ensuring that we chose a slow enough conversion to measure. # It takes 0.1 seconds on a Zen based cloud VM in an opt build. @@ -640,7 +640,7 @@ def test_denial_of_service_prevented_str_to_int(self): with support.adjust_int_max_str_digits(digits - 1): with ( self.assertRaises(ValueError) as err, - support.CPUStopwatch() as sw_fail_huge): + support.Stopwatch() as sw_fail_huge): int(huge) self.assertIn('conversion', str(err.exception)) self.assertLessEqual(sw_fail_huge.seconds, sw_convert.seconds/2) @@ -650,7 +650,7 @@ def test_denial_of_service_prevented_str_to_int(self): extra_huge = '7'*1_200_000 with ( self.assertRaises(ValueError) as err, - support.CPUStopwatch() as sw_fail_extra_huge): + support.Stopwatch() as sw_fail_extra_huge): # If not limited, 8 seconds in the Zen based cloud VM. int(extra_huge) self.assertIn('conversion', str(err.exception)) diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 5538de60b2a03a..b33b6955f6f5c4 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -1,7 +1,7 @@ from test.support import (gc_collect, bigmemtest, _2G, cpython_only, captured_stdout, check_disallow_instantiation, is_emscripten, is_wasi, - warnings_helper, SHORT_TIMEOUT, CPUStopwatch, requires_resource) + warnings_helper, SHORT_TIMEOUT, Stopwatch, requires_resource) import locale import re import string @@ -2473,7 +2473,7 @@ def test_bug_40736(self): @requires_resource('cpu') def test_search_anchor_at_beginning(self): s = 'x'*10**7 - with CPUStopwatch() as stopwatch: + with Stopwatch() as stopwatch: for p in r'\Ay', r'^y': self.assertIsNone(re.search(p, s)) self.assertEqual(re.split(p, s), [s]) From cdeb8387e16916ac566d76613a9b32c642d878ff Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:49:30 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Petr Viktorin --- Lib/test/support/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index c09474455cf5c3..d0e778a866e67b 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2588,8 +2588,9 @@ class Stopwatch: process_time does not exist everywhere (e.g. WASM). N.B.: + - This *includes* time spent in other threads/processes. - Some systems only have a coarse resolution; check - stopwatch.clock_info.resolution if. + stopwatch.clock_info.resolution when using the results. Usage: From 8bf0dbc99f72c3013098936f5070fa8990cf2fdf Mon Sep 17 00:00:00 2001 From: Chris Eibl <138194463+chris-eibl@users.noreply.github.com> Date: Thu, 24 Apr 2025 16:38:00 +0200 Subject: [PATCH 3/3] Commit suggestion from Hugo Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Lib/test/support/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 244d1c44b6d6f8..432488f35da917 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2581,13 +2581,13 @@ def sleeping_retry(timeout, err_msg=None, /, class Stopwatch: """Context manager to roughly time a CPU-bound operation. - Disables GC. Uses perf_counter, i.e. a clock with the highest + Disables GC. Uses perf_counter, which is a clock with the highest available resolution. It is chosen even though it does include time elapsed during sleep and is system-wide, because the resolution of process_time is too coarse on Windows and - process_time does not exist everywhere (e.g. WASM). + process_time does not exist everywhere (for example, WASM). - N.B.: + Note: - This *includes* time spent in other threads/processes. - Some systems only have a coarse resolution; check stopwatch.clock_info.resolution when using the results.