diff --git a/doc/api.rst b/doc/api.rst index 363af711..54c1fad6 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -539,7 +539,7 @@ Runner class See the :ref:`bench_func() example `. - .. method:: bench_async_func(name, func, \*args, inner_loops=None, metadata=None) + .. method:: bench_async_func(name, func, \*args, inner_loops=None, metadata=None, loop_factory=None) Benchmark the function ``await func(*args)`` in asyncio event loop. @@ -548,6 +548,9 @@ Runner class The *inner_loops* parameter is used to normalize timing per loop iteration. + The *loop_factory* parameter, if specified, will be used to create the + event loop used by the benchmark. + To call ``func()`` with keyword arguments, use ``functools.partial``. Return a :class:`Benchmark` instance. diff --git a/doc/changelog.rst b/doc/changelog.rst index 42b3f6d7..fe931d4a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,12 +1,15 @@ Changelog ========= -Version 2.6.0 (2022-11-21) +Version 2.6.0 (2023-03-22) -------------------------- * Inherit ``PYTHONPATH`` environment variable by default. Patch by Theodore Ni. +* ``Runner.bench_async_func()`` takes an optional ``loop_factory`` to support custom loop construction. + Patch by Itamar O. + Version 2.5.0 (2022-11-04) -------------------------- diff --git a/doc/examples/bench_async_func_with_loop_factory.py b/doc/examples/bench_async_func_with_loop_factory.py new file mode 100755 index 00000000..6fbb4e72 --- /dev/null +++ b/doc/examples/bench_async_func_with_loop_factory.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +import asyncio +import pyperf + + +def loop_factory(): + return asyncio.new_event_loop() + + +async def func(): + await asyncio.sleep(0.001) + + +runner = pyperf.Runner() +runner.bench_async_func('async_sleep', func, loop_factory=loop_factory) diff --git a/pyperf/_runner.py b/pyperf/_runner.py index 6c344cb1..a1613b7c 100644 --- a/pyperf/_runner.py +++ b/pyperf/_runner.py @@ -546,6 +546,7 @@ def bench_async_func(self, name, func, *args, **kwargs): inner_loops = kwargs.pop('inner_loops', None) metadata = kwargs.pop('metadata', None) + loop_factory = kwargs.pop('loop_factory', None) self._no_keyword_argument(kwargs) if not self._check_worker_task(): @@ -582,16 +583,20 @@ async def main(): return dt import asyncio - if hasattr(asyncio, 'run'): # Python 3.7+ - dt = asyncio.run(main()) - else: # Python 3.6 + # using the lower level loop API instead of asyncio.run because + # asyncio.run gained the `loop_factory` arg only in Python 3.12. + # we can go back to asyncio.run when Python 3.12 is the oldest + # supported version for pyperf. + if loop_factory is None: loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - dt = loop.run_until_complete(main()) - finally: - asyncio.set_event_loop(None) - loop.close() + else: + loop = loop_factory() + asyncio.set_event_loop(loop) + try: + dt = loop.run_until_complete(main()) + finally: + asyncio.set_event_loop(None) + loop.close() return dt diff --git a/pyperf/tests/test_examples.py b/pyperf/tests/test_examples.py index 1ef79a89..6a9cccbb 100644 --- a/pyperf/tests/test_examples.py +++ b/pyperf/tests/test_examples.py @@ -52,6 +52,12 @@ def test_bench_async_func(self): args = ['-p2', '-w1', '--min-time=0.001'] self.check_command(script, args) + def test_bench_async_func_with_loop_factory(self): + script = 'bench_async_func_with_loop_factory.py' + # Use -w1 --min-time=0.001 to reduce the duration of the test + args = ['-p2', '-w1', '--min-time=0.001'] + self.check_command(script, args) + def test_bench_time_func(self): script = 'bench_time_func.py' args = ['-p2', '-w1', '--min-time=0.001']