GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-72793: C implementation of parts of copy.deepcopy by eendebakpt · Pull Request #91610 · python/cpython

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Apr 16, 2022 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

The original idea and implementation are from @Villemoes. The original issue has been inactive for a long time.

Benchmark on deepcopy of a dict and dataclass:

deepcopy dict: Mean +- std dev: [b] 7.28 us +- 0.27 us -> [d] 982 ns +- 24 ns: 7.41x faster
deepcopy dataclass: Mean +- std dev: [b] 6.49 us +- 0.07 us -> [d] 3.69 us +- 0.11 us: 1.76x faster

Geometric mean: 3.61x faster

Updated benchmark: (23-12-2022, main at 1ecfd1e)

deepcopy dict: Mean +- std dev: [main] 6.56 us +- 0.07 us -> [pr] 821 ns +- 24 ns: 7.99x faster
deepcopy dataclass: Mean +- std dev: [main] 6.12 us +- 0.07 us -> [pr] 3.10 us +- 0.09 us: 1.97x faster

Geometric mean: 3.97x faster

Updated benchmark: (16-9-2023, main at e57ecf6)

deepcopy dict: Mean +- std dev: [main] 7.91 us +- 0.22 us -> [pr] 958 ns +- 49 ns: 8.25x faster
deepcopy dataclass: Mean +- std dev: [main] 7.74 us +- 0.26 us -> [pr] 3.65 us +- 0.27 us: 2.12x faster

Geometric mean: 4.19x faster

Updated benchmark (10-01-2024)

deepcopy dict: Mean +- std dev: [main] 6.82 us +- 0.07 us -> [c_impl] 922 ns +- 19 ns: 7.40x faster
deepcopy dataclass: Mean +- std dev: [main] 6.59 us +- 0.07 us -> [c_impl] 3.34 us +- 0.06 us: 1.98x faster
deepcopy small dataclass: Mean +- std dev: [main] 4.30 us +- 0.07 us -> [c_impl] 3.01 us +- 0.04 us: 1.43x faster
deepcopy small tuple: Mean +- std dev: [main] 1.07 us +- 0.01 us -> [c_impl] 115 ns +- 1 ns: 9.30x faster
deepcopy repeating: Mean +- std dev: [main] 21.8 us +- 0.2 us -> [c_impl] 8.66 us +- 0.28 us: 2.51x faster
deepcopy repeating_atomic: Mean +- std dev: [main] 24.6 us +- 0.5 us -> [c_impl] 1.28 us +- 0.02 us: 19.16x faster

Geometric mean: 4.59x faster

Benchmark details

Test script

Updated test script

import pyperf
runner = pyperf.Runner()

setup="""
import copy

a={'list': [1,2,3,43], 't': (1,2,3), 'str': 'hello', 'subdict': {'a': True}}

from dataclasses import dataclass

@dataclass
class A:
    a : list
    b : str
    c : bool
    
dc=A([1,2,3], 'hello', True)

@dataclass
class A:
    a : int
    
dc_small = A(123)

small_tuple = (1, )

l = {'hi': 100}
repeating_atomic = [ [1] * 100]
repeating = [dc_small] * 100
"""

runner.timeit(name="deepcopy dict", stmt=f"b=copy.deepcopy(a)", setup=setup)
runner.timeit(name="deepcopy dataclass", stmt=f"b=copy.deepcopy(dc)", setup=setup)
runner.timeit(name="deepcopy small dataclass", stmt=f"b=copy.deepcopy(dc_small)", setup=setup)
runner.timeit(name="deepcopy small tuple", stmt=f"b=copy.deepcopy(small_tuple)", setup=setup)
runner.timeit(name="deepcopy repeating", stmt=f"b=copy.deepcopy(repeating)", setup=setup)
runner.timeit(name="deepcopy repeating_atomic", stmt=f"b=copy.deepcopy(repeating_atomic)", setup=setup)

Old test script:

import pyperf
runner = pyperf.Runner()

setup="""
import copy

a={'list': [1,2,3,43], 't': (1,2,3), 'str': 'hello', 'subdict': {'a': True}}

from dataclasses import dataclass

@dataclass
class A:
    a : list
    b : str
    c : bool
    
dc=A([1,2,3], 'hello', True)
"""

runner.timeit(name=f"deepcopy dict", stmt=f"b=copy.deepcopy(a)", setup=setup)
runner.timeit(name=f"deepcopy dataclass", stmt=f"b=copy.deepcopy(dc)", setup=setup)

Fixes #72793

Pyperformance results

Pyperformance results show small speedup, although this could very well be a random fluctuation. (there are no explicit calls to deepcopy in the pyperformance tests)

2to3: Mean +- std dev: [base] 325 ms +- 5 ms -> [patch] 322 ms +- 4 ms: 1.01x faster
chaos: Mean +- std dev: [base] 87.8 ms +- 0.8 ms -> [patch] 88.3 ms +- 1.1 ms: 1.01x slower
float: Mean +- std dev: [base] 103 ms +- 2 ms -> [patch] 98.5 ms +- 2.5 ms: 1.04x faster
go: Mean +- std dev: [base] 163 ms +- 1 ms -> [patch] 165 ms +- 2 ms: 1.01x slower
json_dumps: Mean +- std dev: [base] 15.1 ms +- 0.5 ms -> [patch] 14.9 ms +- 0.1 ms: 1.01x faster
json_loads: Mean +- std dev: [base] 28.0 us +- 0.3 us -> [patch] 28.1 us +- 0.3 us: 1.01x slower
logging_format: Mean +- std dev: [base] 8.11 us +- 0.14 us -> [patch] 8.18 us +- 0.12 us: 1.01x slower
logging_silent: Mean +- std dev: [base] 133 ns +- 1 ns -> [patch] 130 ns +- 1 ns: 1.02x faster
meteor_contest: Mean +- std dev: [base] 128 ms +- 1 ms -> [patch] 126 ms +- 1 ms: 1.01x faster
nbody: Mean +- std dev: [base] 111 ms +- 2 ms -> [patch] 113 ms +- 2 ms: 1.01x slower
nqueens: Mean +- std dev: [base] 106 ms +- 1 ms -> [patch] 106 ms +- 1 ms: 1.00x slower
pathlib: Mean +- std dev: [base] 23.0 ms +- 0.4 ms -> [patch] 23.2 ms +- 0.7 ms: 1.01x slower
pickle: Mean +- std dev: [base] 10.5 us +- 0.1 us -> [patch] 10.6 us +- 0.4 us: 1.01x slower
pickle_dict: Mean +- std dev: [base] 30.9 us +- 0.2 us -> [patch] 31.3 us +- 0.3 us: 1.02x slower
pickle_list: Mean +- std dev: [base] 4.41 us +- 0.04 us -> [patch] 4.55 us +- 0.06 us: 1.03x slower
pyflate: Mean +- std dev: [base] 532 ms +- 8 ms -> [patch] 535 ms +- 4 ms: 1.01x slower
regex_compile: Mean +- std dev: [base] 163 ms +- 1 ms -> [patch] 165 ms +- 1 ms: 1.01x slower
regex_dna: Mean +- std dev: [base] 238 ms +- 2 ms -> [patch] 213 ms +- 1 ms: 1.12x faster
regex_effbot: Mean +- std dev: [base] 3.50 ms +- 0.03 ms -> [patch] 3.08 ms +- 0.03 ms: 1.14x faster
regex_v8: Mean +- std dev: [base] 29.2 ms +- 0.7 ms -> [patch] 25.8 ms +- 0.9 ms: 1.13x faster
richards: Mean +- std dev: [base] 57.4 ms +- 1.5 ms -> [patch] 58.2 ms +- 1.3 ms: 1.01x slower
scimark_fft: Mean +- std dev: [base] 447 ms +- 10 ms -> [patch] 452 ms +- 2 ms: 1.01x slower
scimark_lu: Mean +- std dev: [base] 142 ms +- 2 ms -> [patch] 145 ms +- 1 ms: 1.02x slower
scimark_sor: Mean +- std dev: [base] 155 ms +- 1 ms -> [patch] 156 ms +- 2 ms: 1.01x slower
scimark_sparse_mat_mult: Mean +- std dev: [base] 6.18 ms +- 0.11 ms -> [patch] 6.03 ms +- 0.06 ms: 1.02x faster
spectral_norm: Mean +- std dev: [base] 150 ms +- 7 ms -> [patch] 146 ms +- 3 ms: 1.02x faster
unpack_sequence: Mean +- std dev: [base] 52.6 ns +- 1.0 ns -> [patch] 52.2 ns +- 0.6 ns: 1.01x faster
unpickle_list: Mean +- std dev: [base] 5.77 us +- 0.06 us -> [patch] 5.88 us +- 0.10 us: 1.02x slower
xml_etree_parse: Mean +- std dev: [base] 176 ms +- 3 ms -> [patch] 173 ms +- 2 ms: 1.02x faster

Benchmark hidden because not significant (17): deltablue, fannkuch, hexiom, logging_simple, pickle_pure_python, pidigits, python_startup, python_startup_no_site, raytrace, scimark_monte_carlo, sqlite_synth, telco, unpickle, unpickle_pure_python, xml_etree_iterparse, xml_etree_generate, xml_etree_process

Geometric mean: 1.01x faster

Notes

  • Issue: C implementation of parts of copy.deepcopy #72793
  • We keep the original python code for the implementation of copy.deepcopy (see https://peps.python.org/pep-0399/). In the CI we test both the pure python and accelerator version of deepcopy.
  • The performance improvement of deepcopy has a measurable impact for certain operations in projects such as lmfit and qiskit.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gst reacted with thumbs up emoji Starbuck5, Fidget-Spinner, network-shark, albertedwardson, and erlend-aasland reacted with rocket emoji
All reactions
  • 👍 1 reaction
  • 🚀 5 reactions
@bedevere-bot bedevere-bot added the awaiting review label Apr 16, 2022
@eendebakpt
eendebakpt marked this pull request as draft April 16, 2022 18:44
@eendebakpt
eendebakpt marked this pull request as ready for review April 16, 2022 21:03
@eendebakpt

eendebakpt commented Apr 28, 2022

Copy link
Copy Markdown
Contributor Author

@pablogsal Would you be able to review this PR?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

slimshady621
slimshady621 approved these changes May 2, 2022
View reviewed changes
@bedevere-bot bedevere-bot added awaiting core review and removed awaiting review labels May 2, 2022
@AlexWaygood AlexWaygood added the performance Performance or resource usage label May 4, 2022
This was referenced May 12, 2022
Benchmark suggestion: deepcopy faster-cpython/ideas#379
Closed
Benchmark suggestion: deepcopy python/pyperformance#199
Closed
@eendebakpt

eendebakpt commented May 27, 2022

Copy link
Copy Markdown
Contributor Author

@serhiy-storchaka As the latest core develop working on this file, would you be able to review this PR?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
eendebakpt force-pushed the deepcopy_c_implementation branch from 83741a6 to 2f8e489 Compare June 7, 2022 19:22
@eendebakpt
eendebakpt requested review from markshannon and rhettinger as code owners June 7, 2022 19:22
@eendebakpt
eendebakpt force-pushed the deepcopy_c_implementation branch 2 times, most recently from af2a201 to 502c747 Compare June 7, 2022 19:29
@AA-Turner AA-Turner added the extension-modules C modules in the Modules dir label Jun 7, 2022
AA-Turner
AA-Turner reviewed Jun 7, 2022
View reviewed changes

@AA-Turner AA-Turner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel comfortable reviewing the C code, but you will need to add tests to ensure the fallback and C implementation have the same inputs/outputs/etc -- the easiest way would be to duplicate the tests and run one set for the C accelerator and one for the Python version.

A

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Misc/NEWS.d/next/Core and Builtins/2022-04-16-20-21-13.gh-issue-72793.qc-BP-.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_capi.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
eendebakpt force-pushed the deepcopy_c_implementation branch from 2c18cb6 to c0fd5c9 Compare June 8, 2022 11:25
@eendebakpt

eendebakpt commented Jun 8, 2022

Copy link
Copy Markdown
Contributor Author

I don't feel comfortable reviewing the C code, but you will need to add tests to ensure the fallback and C implementation have the same inputs/outputs/etc -- the easiest way would be to duplicate the tests and run one set for the C accelerator and one for the Python version.

A

@AA-Turner The _deepcopy_fallback is only used for objects not handled by the C deepcopy method (and vice versa). therefore we cannot test both the fallback and C on the same inputs. The deepcopy is the public method (and _deepcopy_fallback is called via deepcopy), so I think we only need to test on deepcopy. If there are any tests you would like me to add, let me know.

Note: in earlier versions of the PR the fallback there was a funny try-except statement to fix a build problem. This I resolved by making _copy a builtin module.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@AA-Turner

AA-Turner commented Jun 8, 2022

Copy link
Copy Markdown
Member

The _deepcopy_fallback is only used for objects not handled by the C deepcopy method (and vice versa). therefore we cannot test both the fallback and C on the same inputs.

If both functions exist and can be theoretically used, both must be tested. You can import copy._deepcopy_fallback and import _copy.deepcopy to test the functions independently. If I understand correctly, in your current patch _deepcopy_fallback is only ever called from the C layer. If so, you should make this more clear -- oftentimes the C accelerator has a pure Python fallback which implements the same method when the extension module can't be loaded.

A

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tiran
tiran suggested changes Jun 8, 2022
View reviewed changes
Comment thread Modules/Setup.bootstrap.in Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/Setup.stdlib.in Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/copy.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_copy.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_copy.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Modules/_copy.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the awaiting core review label Jun 8, 2022
@bedevere-bot

bedevere-bot commented Jun 8, 2022

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added the awaiting changes label Jun 8, 2022
@serhiy-storchaka

serhiy-storchaka commented Jun 8, 2022

Copy link
Copy Markdown
Member

There are some issues with the copy module. I prefer to resolve them prior to adding the C implementation.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt

eendebakpt commented Jun 8, 2022

Copy link
Copy Markdown
Contributor Author

The _deepcopy_fallback is only used for objects not handled by the C deepcopy method (and vice versa). therefore we cannot test both the fallback and C on the same inputs.

If both functions exist and can be theoretically used, both must be tested. You can import copy._deepcopy_fallback and import _copy.deepcopy to test the functions independently. If I understand correctly, in your current patch _deepcopy_fallback is only ever called from the C layer. If so, you should make this more clear -- oftentimes the C accelerator has a pure Python fallback which implements the same method when the extension module can't be loaded.

A

@tiran noted the fallback will be used by PyPy, so it must indeed be tested. I will add the required tests

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tiran

tiran commented Jun 8, 2022

Copy link
Copy Markdown
Member

We have helper code to block / force imports to test both pure and C accelerated features.

from test.support.import_helper import import_fresh_module

copy_py = import_fresh_module('copy', blocked=['_copy'])
try:
    copy_c = import_fresh_module('copy', fresh=['_copy'])
except ImportError:
    copy_c = None
eendebakpt reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
eendebakpt requested a review from a team as a code owner June 8, 2022 19:00
@eendebakpt

eendebakpt commented Jun 8, 2022

Copy link
Copy Markdown
Contributor Author

I addressed some of the review comments.

@serhiy-storchaka Could you indicate which issues with the copy module there are, and whether there already is a timeline on addressing them?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt

eendebakpt commented Oct 4, 2023

Copy link
Copy Markdown
Contributor Author

@erlend-aasland The failing tests where due to the recent addition of copy.replace. I added the unit tests for new functionality in the same structure as the other unit tests. This means that the pure python method copy.replace is tested twice (which is a bit redundant, but with better consistency overall).

The PR is ready for review, although I am not sure the item Serhiy raised should be resolved first. See #103035 (comment) and #109498.

erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@erlend-aasland

erlend-aasland commented Oct 5, 2023

Copy link
Copy Markdown
Contributor

The PR is ready for review, although I am not sure the item Serhiy raised should be resolved first. See #103035 (comment) and #109498.

I'll leave that to @serhiy-storchaka.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

eendebakpt added 3 commits October 17, 2023 00:48
@eendebakpt
Merge branch 'main' into deepcopy_c_implementation
ce8bed7
@eendebakpt
Merge branch 'main' into deepcopy_c_implementation
93c2d57
@eendebakpt
Merge branch 'main' into deepcopy_c_implementation
e6379d0
@eendebakpt
eendebakpt requested a review from gvanrossum as a code owner December 14, 2023 22:21
eendebakpt added 2 commits December 14, 2023 23:36
@eendebakpt
update argument clinic
0971be5
@eendebakpt
resolve merge conflicts
6d13239
@gvanrossum
gvanrossum removed request for gvanrossum and tiran December 15, 2023 01:43
@eendebakpt eendebakpt mentioned this pull request Jan 22, 2024
gh-114264: Optimize performance of copy.deepcopy by adding a fast path for atomic types #114266
Merged
@erlend-aasland
erlend-aasland dismissed tiran’s stale review April 4, 2024 07:38

Christian is inactive at the moment, so dismissing his review.

@erlend-aasland
Merge branch 'main' into deepcopy_c_implementation
35cdca5
@erlend-aasland
erlend-aasland requested a review from kumaraditya303 April 4, 2024 07:39
@eendebakpt

eendebakpt commented Apr 4, 2024

Copy link
Copy Markdown
Contributor Author

For any reviewers: I also created a performance improvement for the python implementation: #114266.

Depending on the outcome of that PR and some (minor) changes I want to do to make this work in free-threading I will update the benchmarks.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@erlend-aasland

erlend-aasland commented Apr 4, 2024

Copy link
Copy Markdown
Contributor

@eendebakpt, also consider adapting to free-threading in a follow-up PR; this PR is already a huge diff.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@kumaraditya303
kumaraditya303 removed their request for review May 27, 2024 11:38
Pieter Eendebak
Merge branch 'main' into deepcopy_c_implementation
6097604
@ghost

ghost commented Jun 9, 2024 •
edited by ghost
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

The following commit authors need to sign the Contributor License Agreement:

  • [email protected]

Click the button to sign:
CLA not signed

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt

eendebakpt commented Jun 9, 2024

Copy link
Copy Markdown
Contributor Author

Update of the benchmarks with recent changes to main:

deepcopy dict: Mean +- std dev: [main] 3.75 us +- 0.17 us -> [pr] 587 ns +- 25 ns: 6.40x faster
deepcopy dataclass: Mean +- std dev: [main] 4.21 us +- 0.08 us -> [pr] 2.57 us +- 0.11 us: 1.64x faster
deepcopy small dataclass: Mean +- std dev: [main] 3.03 us +- 0.12 us -> [pr] 2.28 us +- 0.10 us: 1.33x faster
deepcopy small tuple: Mean +- std dev: [main] 732 ns +- 21 ns -> [pr] 72.8 ns +- 2.5 ns: 10.05x faster
deepcopy repeating: Mean +- std dev: [main] 20.7 us +- 0.7 us -> [pr] 6.49 us +- 0.32 us: 3.18x faster
deepcopy repeating_atomic: Mean +- std dev: [main] 10.2 us +- 0.3 us -> [pr] 801 ns +- 33 ns: 12.79x faster

Geometric mean: 4.23x faster
erlend-aasland, Eclips4, and a-reich reacted with rocket emoji
All reactions
  • 🚀 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt
Merge branch 'main' into deepcopy_c_implementation
fde2b83
@eendebakpt

eendebakpt commented Jun 28, 2024

Copy link
Copy Markdown
Contributor Author

To make a fair comparison between current main and the C implementation in this PR I looked at the python implementation again to see whether there are optimizations we do in the C implementation that could also be applied in the Python main. There are quite some options:

  • Remove the redundant _nil argument to deepcopy
  • Replace try-except constructions with dictionaries with a d.get(key, sentinel) to avoid expensive creation of exceptions
  • Use comprehensions in the deepcopy_list and deepcopy_tuple
  • In deepcopy_tuple combine the creation of y and the check for k, j in zip(x, y): into a single for loop
  • Inline the if cls in _atomic_types: return x part at various locations
  • Inline the call to the (only once used) _keep_alive

I will not make PRs for these, because each of them has a very high likelihood of being rejected because either the performance gain is small or the change would reduce readability or maintainability of the code (for the interested reader: I would give the last option the highest probability of success).

encukou and auvipy reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lgeiger lgeiger mentioned this pull request Jun 30, 2024
Add fast path to deepcopy() for empty list/tuple/dict/set #121192
Closed
@eendebakpt eendebakpt mentioned this pull request Jul 2, 2024
gh-121192: Add fast path to deepcopy() for empty list/tuple/dict/set #121193
Closed
@eendebakpt
Merge branch 'main' into deepcopy_c_implementation
9bef684
@eendebakpt eendebakpt mentioned this pull request Apr 25, 2025
gh-101178: Add Ascii85, Base85, and Z85 support to binascii #102753
Merged
@Bobronium

Bobronium commented Feb 15, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

If you're looking for a faster deepcopy today, you may find copium useful.

It's a complete rewrite of deepcopy in C, compatible with Python 3.10-3.14(t), available on PyPI.

It was initially based on this PR, but quickly grew into a much larger project. It's still WIP, but already has extensive test coverage and provides real speed boost.

Benchmarks

Benchmark copy_3.14 copium_3.14
deepcopy dict 3.77 us 396 ns: 9.52x faster
deepcopy dataclass 4.01 us 857 ns: 4.68x faster
deepcopy small dataclass 3.01 us 681 ns: 4.43x faster
deepcopy small tuple 826 ns 75.7 ns: 10.91x faster
deepcopy repeating 20.8 us 1.67 us: 12.42x faster
deepcopy repeating_atomic 8.69 us 686 ns: 12.68x faster
Geometric mean (ref) 8.35x faster
On 3.13 and lower, there's even larger speedup.
Benchmark copy_3.13 copium_3.13
deepcopy dict 5.77 us 394 ns: 14.63x faster
deepcopy dataclass 5.33 us 780 ns: 6.83x faster
deepcopy small dataclass 3.29 us 620 ns: 5.31x faster
deepcopy small tuple 882 ns 66.1 ns: 13.34x faster
deepcopy repeating 18.1 us 1.80 us: 10.03x faster
deepcopy repeating_atomic 21.0 us 675 ns: 31.19x faster
Geometric mean (ref) 11.42x faster
Reproduction
cat > /tmp/benchmark.py << 'PY'
import pyperf
runner = pyperf.Runner()

setup="""
import copy

a={'list': [1,2,3,43], 't': (1,2,3), 'str': 'hello', 'subdict': {'a': True}}

from dataclasses import dataclass

@dataclass
class A:
    a : list
    b : str
    c : bool

dc=A([1,2,3], 'hello', True)

@dataclass
class A:
    a : int

dc_small = A(123)

small_tuple = (1, )

l = {'hi': 100}
repeating_atomic = [ [1] * 100]
repeating = [dc_small] * 100
"""

runner.timeit(name="deepcopy dict", stmt=f"b=copy.deepcopy(a)", setup=setup)
runner.timeit(name="deepcopy dataclass", stmt=f"b=copy.deepcopy(dc)", setup=setup)
runner.timeit(name="deepcopy small dataclass", stmt=f"b=copy.deepcopy(dc_small)", setup=setup)
runner.timeit(name="deepcopy small tuple", stmt=f"b=copy.deepcopy(small_tuple)", setup=setup)
runner.timeit(name="deepcopy repeating", stmt=f"b=copy.deepcopy(repeating)", setup=setup)
runner.timeit(name="deepcopy repeating_atomic", stmt=f"b=copy.deepcopy(repeating_atomic)", setup=setup)
PY

py=3.14
rm -f /tmp/copium_$py.json \
&& uv run --python $py --with 'copium[autopatch]' --with pyperf python /tmp/benchmark.py -o /tmp/copium_$py.json \
&& rm -f /tmp/copy_$py.json \
&& uv run --python $py --with pyperf python /tmp/benchmark.py -o /tmp/copy_$py.json \
&& uvx pyperf compare_to /tmp/copy_$py.json /tmp/copium_$py.json --table --table-format md
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt

eendebakpt commented Feb 16, 2026

Copy link
Copy Markdown
Contributor Author

@Bobronium Thanks for the link! I tried out copium, and it works as advertised!

Historically there have been two reasons for me to pursue this PR:

  1. If included in CPython it is available for everyone (batteries included!)
  2. When creating a pypi package as an alternative for deepcopy, it would not automatically be used by other pip packages making use of copy.deepcopy.

The second point is handled by copium by patching the vectorcall function of the deepcopy object (see remark Zero overhead patch). Not all projects will be comfortable with such patching, but it works!

Unless there is interest from a core dev to review the PR (in which case I will rebase) I am going to close the PR.

Bobronium reacted with thumbs up emoji Bobronium, owocado, and erlend-aasland reacted with heart emoji
All reactions
  • 👍 1 reaction
  • ❤️ 3 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@eendebakpt eendebakpt closed this Mar 26, 2026
@eendebakpt eendebakpt mentioned this pull request Jun 3, 2026
gh-150815: Speed up copy.deepcopy() of containers with atomic elements #150822
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@AA-Turner AA-Turner AA-Turner left review comments
@kumaraditya303 kumaraditya303 kumaraditya303 requested changes
@corona10 corona10 Awaiting requested review from corona10 corona10 is a code owner
@erlend-aasland erlend-aasland Awaiting requested review from erlend-aasland erlend-aasland is a code owner
+2 more reviewers
@tiran tiran tiran left review comments
@slimshady621 slimshady621 slimshady621 approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting change review extension-modules C modules in the Modules dir performance Performance or resource usage

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

C implementation of parts of copy.deepcopy

Uh oh!

There was an error while loading. Please reload this page.

10 participants

@eendebakpt @AA-Turner @bedevere-bot @serhiy-storchaka @tiran @erlend-aasland @Bobronium @kumaraditya303 @slimshady621 @AlexWaygood
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.