From 9990245b3d441820d6576f679d9ce6e291e71b49 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 18 Jan 2024 22:06:11 +0100 Subject: [PATCH 01/10] Optimize performance of copy.deepcopy by adding a fast path for atomic types --- Lib/copy.py | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index a69bc4e78c20b3..293044373ccec8 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -121,6 +121,11 @@ def deepcopy(x, memo=None, _nil=[]): See the module's __doc__ string for more info. """ + cls = type(x) + + if cls in _atomic_types: + return x + d = id(x) if memo is None: memo = {} @@ -129,14 +134,12 @@ def deepcopy(x, memo=None, _nil=[]): if y is not _nil: return y - cls = type(x) - copier = _deepcopy_dispatch.get(cls) if copier is not None: y = copier(x, memo) else: if issubclass(cls, type): - y = _deepcopy_atomic(x, memo) + y = x # atomic copy else: copier = getattr(x, "__deepcopy__", None) if copier is not None: @@ -167,26 +170,12 @@ def deepcopy(x, memo=None, _nil=[]): _keep_alive(x, memo) # Make sure x lives at least as long as d return y -_deepcopy_dispatch = d = {} +_atomic_types = {type(None), int, float, bool, complex, str, bytes, type, range, property, + types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented), + types.FunctionType, weakref.ref, types.CodeType} + +_deepcopy_dispatch = {} -def _deepcopy_atomic(x, memo): - return x -d[types.NoneType] = _deepcopy_atomic -d[types.EllipsisType] = _deepcopy_atomic -d[types.NotImplementedType] = _deepcopy_atomic -d[int] = _deepcopy_atomic -d[float] = _deepcopy_atomic -d[bool] = _deepcopy_atomic -d[complex] = _deepcopy_atomic -d[bytes] = _deepcopy_atomic -d[str] = _deepcopy_atomic -d[types.CodeType] = _deepcopy_atomic -d[type] = _deepcopy_atomic -d[range] = _deepcopy_atomic -d[types.BuiltinFunctionType] = _deepcopy_atomic -d[types.FunctionType] = _deepcopy_atomic -d[weakref.ref] = _deepcopy_atomic -d[property] = _deepcopy_atomic def _deepcopy_list(x, memo, deepcopy=deepcopy): y = [] @@ -195,7 +184,7 @@ def _deepcopy_list(x, memo, deepcopy=deepcopy): for a in x: append(deepcopy(a, memo)) return y -d[list] = _deepcopy_list +_deepcopy_dispatch[list] = _deepcopy_list def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] @@ -212,7 +201,7 @@ def _deepcopy_tuple(x, memo, deepcopy=deepcopy): else: y = x return y -d[tuple] = _deepcopy_tuple +_deepcopy_dispatch[tuple] = _deepcopy_tuple def _deepcopy_dict(x, memo, deepcopy=deepcopy): y = {} @@ -220,13 +209,12 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy): for key, value in x.items(): y[deepcopy(key, memo)] = deepcopy(value, memo) return y -d[dict] = _deepcopy_dict +_deepcopy_dispatch[dict] = _deepcopy_dict def _deepcopy_method(x, memo): # Copy instance methods return type(x)(x.__func__, deepcopy(x.__self__, memo)) -d[types.MethodType] = _deepcopy_method +_deepcopy_dispatch[types.MethodType] = _deepcopy_method -del d def _keep_alive(x, memo): """Keeps a reference to the object x in the memo. From 06f1980fa6236f3403ffd34597ee453ba4fd2c8f Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 18 Jan 2024 22:41:58 +0100 Subject: [PATCH 02/10] match order with main --- Lib/copy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 293044373ccec8..ea5e7ec3677de8 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -170,9 +170,9 @@ def deepcopy(x, memo=None, _nil=[]): _keep_alive(x, memo) # Make sure x lives at least as long as d return y -_atomic_types = {type(None), int, float, bool, complex, str, bytes, type, range, property, - types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented), - types.FunctionType, weakref.ref, types.CodeType} +_atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType, + int, float, bool, complex, bytes, str, types.CodeType, type, range, + types.BuiltinFunctionType, types.FunctionType, weakref.ref, property} _deepcopy_dispatch = {} From dd9fabfaf4f089865ff68445d53555f265e43c41 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:44:26 +0000 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst diff --git a/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst b/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst new file mode 100644 index 00000000000000..81e56dbe30b15c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst @@ -0,0 +1 @@ +Improve performance of `copy.deepcopy` by adding a fast path for atomic types. From becbb591cc5a83d477edbd1c57435bcfe79b6745 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 18 Jan 2024 22:50:01 +0100 Subject: [PATCH 04/10] format news entry --- .../next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst b/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst index 81e56dbe30b15c..069ac68b4f3a95 100644 --- a/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst +++ b/Misc/NEWS.d/next/Library/2024-01-18-21-44-23.gh-issue-114264.DBKn29.rst @@ -1 +1 @@ -Improve performance of `copy.deepcopy` by adding a fast path for atomic types. +Improve performance of :func:`copy.deepcopy` by adding a fast path for atomic types. From 339c2b9551482dc580bc2cb6ed49a480f11a6e75 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 19 Jan 2024 11:20:11 +0100 Subject: [PATCH 05/10] put d variable back --- Lib/copy.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index ea5e7ec3677de8..7a1907d75494d7 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -174,7 +174,7 @@ def deepcopy(x, memo=None, _nil=[]): int, float, bool, complex, bytes, str, types.CodeType, type, range, types.BuiltinFunctionType, types.FunctionType, weakref.ref, property} -_deepcopy_dispatch = {} +_deepcopy_dispatch = d = {} def _deepcopy_list(x, memo, deepcopy=deepcopy): @@ -184,7 +184,7 @@ def _deepcopy_list(x, memo, deepcopy=deepcopy): for a in x: append(deepcopy(a, memo)) return y -_deepcopy_dispatch[list] = _deepcopy_list +d[list] = _deepcopy_list def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] @@ -201,7 +201,7 @@ def _deepcopy_tuple(x, memo, deepcopy=deepcopy): else: y = x return y -_deepcopy_dispatch[tuple] = _deepcopy_tuple +d[tuple] = _deepcopy_tuple def _deepcopy_dict(x, memo, deepcopy=deepcopy): y = {} @@ -209,12 +209,13 @@ def _deepcopy_dict(x, memo, deepcopy=deepcopy): for key, value in x.items(): y[deepcopy(key, memo)] = deepcopy(value, memo) return y -_deepcopy_dispatch[dict] = _deepcopy_dict +d[dict] = _deepcopy_dict def _deepcopy_method(x, memo): # Copy instance methods return type(x)(x.__func__, deepcopy(x.__self__, memo)) -_deepcopy_dispatch[types.MethodType] = _deepcopy_method +d[types.MethodType] = _deepcopy_method +del d def _keep_alive(x, memo): """Keeps a reference to the object x in the memo. From eb8e6ba35cf7a1826fe756a5281b70af5531acfa Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 13 Apr 2024 23:23:02 +0200 Subject: [PATCH 06/10] improve performance of _deepcopy_list with a list comprehension --- Lib/copy.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 7a1907d75494d7..0734480c2b2db5 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -180,12 +180,13 @@ def deepcopy(x, memo=None, _nil=[]): def _deepcopy_list(x, memo, deepcopy=deepcopy): y = [] memo[id(x)] = y - append = y.append - for a in x: - append(deepcopy(a, memo)) + y += [deepcopy(a, memo) for a in x] return y + d[list] = _deepcopy_list +_nil = object() + def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] # We're not going to put the tuple in the memo, but it's still important we @@ -194,6 +195,7 @@ def _deepcopy_tuple(x, memo, deepcopy=deepcopy): return memo[id(x)] except KeyError: pass + for k, j in zip(x, y): if k is not j: y = tuple(y) From e468b6c5169c124e567885d29d388fb757f2bf5d Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 13 Apr 2024 23:23:26 +0200 Subject: [PATCH 07/10] avoid generation of exception in _deepcopy_tuple --- Lib/copy.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 0734480c2b2db5..d394fe9d862d21 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -191,10 +191,9 @@ def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] # We're not going to put the tuple in the memo, but it's still important we # check for it, in case the tuple contains recursive mutable structures. - try: - return memo[id(x)] - except KeyError: - pass + d = memo.get(id(x), _nil) + if d is not _nil: + return d for k, j in zip(x, y): if k is not j: From e3310f5731db181c40e7e758b59cbab454a0455c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sun, 14 Apr 2024 21:33:46 +0200 Subject: [PATCH 08/10] revert comprehension changes --- Lib/copy.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index d394fe9d862d21..e79222cddf8e6c 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -180,7 +180,9 @@ def deepcopy(x, memo=None, _nil=[]): def _deepcopy_list(x, memo, deepcopy=deepcopy): y = [] memo[id(x)] = y - y += [deepcopy(a, memo) for a in x] + append = y.append + for a in x: + append(deepcopy(a, memo)) return y d[list] = _deepcopy_list @@ -191,10 +193,10 @@ def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] # We're not going to put the tuple in the memo, but it's still important we # check for it, in case the tuple contains recursive mutable structures. - d = memo.get(id(x), _nil) - if d is not _nil: - return d - + try: + return memo[id(x)] + except KeyError: + pass for k, j in zip(x, y): if k is not j: y = tuple(y) From 4bc74d3fda5c39bcecdd5bebcf909477dcc4a6da Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 2 May 2024 00:00:11 +0200 Subject: [PATCH 09/10] Update Lib/copy.py --- Lib/copy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/copy.py b/Lib/copy.py index e79222cddf8e6c..c144033f6a25d3 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -187,7 +187,6 @@ def _deepcopy_list(x, memo, deepcopy=deepcopy): d[list] = _deepcopy_list -_nil = object() def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] From c66b24d5ad017414ebf9feb6bc92d50f56a6733d Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 2 May 2024 00:08:13 +0200 Subject: [PATCH 10/10] Apply suggestions from code review --- Lib/copy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index c144033f6a25d3..7a1907d75494d7 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -184,10 +184,8 @@ def _deepcopy_list(x, memo, deepcopy=deepcopy): for a in x: append(deepcopy(a, memo)) return y - d[list] = _deepcopy_list - def _deepcopy_tuple(x, memo, deepcopy=deepcopy): y = [deepcopy(a, memo) for a in x] # We're not going to put the tuple in the memo, but it's still important we