From 1f2f8abadc718e3df7688eec0061ef3ca8eb0deb Mon Sep 17 00:00:00 2001 From: jihtsan <15151999611@163.com> Date: Wed, 14 Oct 2026 09:10:59 +0800 Subject: [PATCH] Add Runner.is_worker for guarding benchmark result processing --- doc/api.rst | 17 +++++++++++++++++ pyperf/_runner.py | 4 ++++ pyperf/tests/test_runner.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/doc/api.rst b/doc/api.rst index cb08ebed..d6d06ba6 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -692,6 +692,23 @@ Runner class Return the :attr:`args` attribute. + .. method:: is_worker() + + Return ``True`` in a benchmark worker process, or ``False`` in the + main process. Parse command line arguments if they have not been parsed + yet. + + Worker processes re-execute the benchmark script and may skip benchmarks, + returning ``None``. Run all benchmarks in both the main process and + workers, and use this method to guard code that processes their results:: + + runner = pyperf.Runner() + benchmarks = [runner.bench_func("first", first_func), + runner.bench_func("second", second_func)] + if not runner.is_worker(): + suite = pyperf.BenchmarkSuite(benchmarks) + suite.dump("benchmarks.json") + Attributes: .. attribute:: args diff --git a/pyperf/_runner.py b/pyperf/_runner.py index 3a7ab11d..56388793 100644 --- a/pyperf/_runner.py +++ b/pyperf/_runner.py @@ -392,6 +392,10 @@ def parse_args(self, args=None): self._set_args(args) return args + def is_worker(self): + """Return whether this process is a benchmark worker.""" + return self.parse_args().worker + def _cpu_affinity(self): cpus = self.args.affinity if not cpus: diff --git a/pyperf/tests/test_runner.py b/pyperf/tests/test_runner.py index c77e0251..066781d7 100644 --- a/pyperf/tests/test_runner.py +++ b/pyperf/tests/test_runner.py @@ -484,6 +484,41 @@ def test_parse_args_twice_error(self): with self.assertRaises(RuntimeError): runner.parse_args(args) + def test_is_worker(self): + for args, expected in (([], False), (['--worker', '-l1', '-w0'], True)): + for parsed in (False, True): + with self.subTest(args=args, parsed=parsed): + pyperf.Runner._created.clear() + runner = pyperf.Runner() + if parsed: + runner.parse_args(args) + with mock.patch.object(sys, 'argv', ['benchmark.py'] + args): + self.assertIs(runner.is_worker(), expected) + namespace = runner.args + self.assertIs(runner.is_worker(), expected) + self.assertIs(runner.args, namespace) + + def test_is_worker_guards_benchmark_suite(self): + source = textwrap.dedent(''' + import pyperf + + def noop(): + pass + + runner = pyperf.Runner(values=1, processes=1, loops=1, warmups=0) + benchmarks = [runner.bench_func('first', noop), + runner.bench_func('second', noop)] + if not runner.is_worker(): + suite = pyperf.BenchmarkSuite(benchmarks) + print('suite: ' + ', '.join(suite.get_benchmark_names())) + ''') + with tests.temporary_file(suffix='.py') as filename: + with open(filename, 'w', encoding='utf-8') as script: + script.write(source) + result = tests.get_output([sys.executable, filename, '--quiet']) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.count('suite: first, second'), 1) + def test_duplicated_named(self): def time_func(loops): return 1.0