From b3e479ac2830642ba6d4dcf22259e22a8145a2d9 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Wed, 3 May 2023 10:50:42 -0700 Subject: [PATCH 1/6] gh-NNNN: Skip creating GatheringFuture if all futures finished eagerly --- Lib/asyncio/tasks.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index aa5269ade19a7f..09e9526b2aa9d6 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -815,6 +815,7 @@ def _done_callback(fut): nfinished = 0 loop = None outer = None # bpo-46672 + all_finished = True for arg in coros_or_futures: if arg not in arg_to_fut: fut = ensure_future(arg, loop=loop) @@ -829,7 +830,12 @@ def _done_callback(fut): nfuts += 1 arg_to_fut[arg] = fut - fut.add_done_callback(_done_callback) + if fut.done(): + # call the callback immediately instead of scheduling it + _done_callback(fut) + else: + all_finished = False + fut.add_done_callback(_done_callback) else: # There's a duplicate Future object in coros_or_futures. @@ -837,7 +843,13 @@ def _done_callback(fut): children.append(fut) - outer = _GatheringFuture(children, loop=loop) + if all_finished: + # optimization: skip creating GatheringFuture if all children completed + # (e.g. when all coros are able to complete eagerly) + outer = futures.Future(loop=loop) + outer.set_result([c.result for c in children]) + else: + outer = _GatheringFuture(children, loop=loop) return outer From 8eeebaf125c113f640b9e36ec302bd1164f1ae5e Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Wed, 3 May 2023 16:50:41 -0700 Subject: [PATCH 2/6] Add NEWS entry --- .../Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst diff --git a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst new file mode 100644 index 00000000000000..730ec5ab7ea2b5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst @@ -0,0 +1,3 @@ +Optimize asyncio.gather when using eager tasks factory Skip creating +gathering future and scheduling done callbacks when all futures finish +without blocking - for up to 3x speedup From 9289c9e5ada4dd01f539edf86717a7a8798f2de4 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Wed, 3 May 2023 17:53:00 -0700 Subject: [PATCH 3/6] Apply review suggestion to NEWS entry Co-authored-by: Carl Meyer --- .../Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst index 730ec5ab7ea2b5..9a5a01c26dd123 100644 --- a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst +++ b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst @@ -1,3 +1,3 @@ -Optimize asyncio.gather when using eager tasks factory Skip creating -gathering future and scheduling done callbacks when all futures finish -without blocking - for up to 3x speedup +Optimize :func:`asyncio.gather` when using :func:`asyncio.eager_task_factory`. +Skip creating gathering future and scheduling done callbacks when all futures finish +without blocking. From 9be6e519273e8ce3078e9f9e9e2c61bb292417c7 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Fri, 5 May 2023 10:00:38 -0700 Subject: [PATCH 4/6] Modify implementation to eagerly call done callbacks at the end of the loop, after the GatheringFuture (outer) is created --- Lib/asyncio/tasks.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 09e9526b2aa9d6..cd98359ab6c1cb 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -813,9 +813,9 @@ def _done_callback(fut): children = [] nfuts = 0 nfinished = 0 + done_futs = [] loop = None outer = None # bpo-46672 - all_finished = True for arg in coros_or_futures: if arg not in arg_to_fut: fut = ensure_future(arg, loop=loop) @@ -831,10 +831,8 @@ def _done_callback(fut): nfuts += 1 arg_to_fut[arg] = fut if fut.done(): - # call the callback immediately instead of scheduling it - _done_callback(fut) + done_futs.append(fut) else: - all_finished = False fut.add_done_callback(_done_callback) else: @@ -843,13 +841,14 @@ def _done_callback(fut): children.append(fut) - if all_finished: - # optimization: skip creating GatheringFuture if all children completed - # (e.g. when all coros are able to complete eagerly) - outer = futures.Future(loop=loop) - outer.set_result([c.result for c in children]) - else: - outer = _GatheringFuture(children, loop=loop) + outer = _GatheringFuture(children, loop=loop) + # call the done callbacks for futures that finished eagerly, instead + # of scheduling the callbacks to be called later by the event loop + # optimization: in the special case that *all* futures finished eagerly, + # this will effectively complete the gather eagerly, with the last + # callback setting the result (or exception) on outer before returning it + for fut in done_futs: + _done_callback(fut) return outer From 250b73ca49192d446ab492ff06cbbe51805523e0 Mon Sep 17 00:00:00 2001 From: Itamar Ostricher Date: Fri, 5 May 2023 14:14:51 -0700 Subject: [PATCH 5/6] update inline comment and NEWS entry --- Lib/asyncio/tasks.py | 4 ++-- .../Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index cd98359ab6c1cb..7eb647bd129819 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -842,8 +842,8 @@ def _done_callback(fut): children.append(fut) outer = _GatheringFuture(children, loop=loop) - # call the done callbacks for futures that finished eagerly, instead - # of scheduling the callbacks to be called later by the event loop + # Run done callbacks after GatheringFuture created so any post-processing + # can be performed at this point # optimization: in the special case that *all* futures finished eagerly, # this will effectively complete the gather eagerly, with the last # callback setting the result (or exception) on outer before returning it diff --git a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst index 9a5a01c26dd123..02b597bcd1c30f 100644 --- a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst +++ b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst @@ -1,3 +1,3 @@ -Optimize :func:`asyncio.gather` when using :func:`asyncio.eager_task_factory`. -Skip creating gathering future and scheduling done callbacks when all futures finish -without blocking. +Optimize :func:`asyncio.gather` when using :func:`asyncio.eager_task_factory` +to complete eagerly if all fututres completed eagerly. +Avoid scheduling done callbacks for fututres that complete eagerly. From ce52714dc6d0a3ada7a925cec631ff6cccc2a06d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 6 May 2023 07:50:06 -0700 Subject: [PATCH 6/6] Fix NEWS typo --- .../next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst index 02b597bcd1c30f..b975d48ed3385c 100644 --- a/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst +++ b/Misc/NEWS.d/next/Library/2023-05-03-16-50-24.gh-issue-104144.yNkjL8.rst @@ -1,3 +1,3 @@ Optimize :func:`asyncio.gather` when using :func:`asyncio.eager_task_factory` to complete eagerly if all fututres completed eagerly. -Avoid scheduling done callbacks for fututres that complete eagerly. +Avoid scheduling done callbacks for futures that complete eagerly.