You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello amazing Python people :-) I want to preface this by acknowledging that I don't have experience implementing programming language internals, so I may be missing crucial information. More, it's my first interaction with the CPython community.
I've been recently watching an interesting analysis on array growth factors. At one point there was a comparison of CPython's growth factor ~1.125 and other languages — based on this Wikipedia section. The video highlights that such choice focuses more on memory savings rather than performance. I believe this ratio might be too conservative for today's speed/space trade-off considerations — perhaps it's a good time to revisit it and hopefully make faster-cpython!
While searching for sources for this issue, I came across a comment from @rhettinger — an amazing presenter!:
We should definitely revisit the over-allocation strategy. I last worked on the existing strategy back in 2004. Since then, the weighting of the speed/space trade-off considerations have changed.
Reacted by Asif Saif Uddin {"Auvi":"অভি"}, KhDenys and Vali
I'm not a CPython developer either, but I decided to do some casual benchmarking this week to hopefully contribute to the discussion.
I skimmed the source in Objects/ and concluded that dicts and sets already use a larger growth factor. I'm guessing this is because resizing them is more expensive - rather than just being a memory copy, it involves reinserting all the elements. The code for Unicode objects usually precalculates sizes exactly instead of using a growth factor. That leaves list, bytes, and bytearray.
I wrote four quick patches so that I could benchmark growth factors of 1.5 and 2, both for list only and for all three types. I tried to match the style of the existing code as closely as possible. For example, I'd personally avoid the floating point operations here in favor of moving the calculation out of the if, but I kept the logic as-is to avoid biasing the benchmarks.
The following 22 benchmarks were failing, but that still left the majority working so I decided to continue.
async_tree
async_tree_cpu_io_mixed
async_tree_cpu_io_mixed_tg
async_tree_eager
async_tree_eager_cpu_io_mixed
async_tree_eager_cpu_io_mixed_tg
async_tree_eager_io
async_tree_eager_io_tg
async_tree_eager_memoization
async_tree_eager_memoization_tg
async_tree_eager_tg
async_tree_io
async_tree_io_tg
async_tree_memoization
async_tree_memoization_tg
async_tree_tg
chameleon
coverage
sqlalchemy_declarative
sqlalchemy_imperative
sqlite_synth
tornado_http
My setup is Debian 12, an Intel i7-11700KF CPU with a big air cooler, and GCC 12.2.0. The benchmarks were run in a bare-bones window manager session; the X server was running, but potentially CPU-intensive services like Syncthing were stopped. I used commit 2b47f46d7dc30d27b2486991fea4acd83553294b of CPython and 9e29e3f69595fed2b74a8dab081ce9caf1640cd9 of pyperformance.
Here are the geometric means of the runtime ratios of all benchmarks:
Growth factor
Speed compared to main
1.5 for list
1.011x faster
2 for list
1.009x faster
1.5 for list, bytes, and bytearray
1.006x faster
2 for list, bytes, and bytearray
1.005x faster
It looks like raising the growth factor for list to 1.5 speeds up CPython by 1%, but it's not a good idea to raise the factor further or to do that for bytes and bytearray.
I'm not sure how much of that is real and how much is statistical noise, so I'd appreciate reproductions. I've included the steps and files below, in the "Reproduction steps" section.
Per-benchmark results
I've only included the statistically significant benchmarks because this post is long enough already.
Stop unnecessary programs, then tune the system for benchmarks:
sudo ~/.local/pipx/venvs/pyperformance/bin/pyperf system tune
Now you can benchmark main and the patched versions:
cd~/pybench
pyperformance compile benchmark.conf 2b47f46d7dc30d27b2486991fea4acd83553294b main
forpatchin patches/*.patch;do
pyperformance compile --patch "$patch" benchmark.conf 2b47f46d7dc30d27b2486991fea4acd83553294b main
done
If you want, you can also use my script to screen-scrape pyperformance's output and calculate the geometric means:
mkdir -p results-txt
pyperformance compare -O table json/2025-04-21_21-15-main-2b47f46d7dc3.json.gz json/patch/2025-04-21_21-15-main-2b47f46d7dc3-patch-0001-Change-list-growth-factor-to-1.5.json.gz >results-txt/list-1.5.txt
# And so on for all the other comparisons you want to do, then:
./geomean-from-pyperformance-output.py results-txt/*
Activity
[-]CPython's List Growth Factor[/-][+]List Growth Factor[/+]pkkm commentedon Apr 27, 2025
I'm not a CPython developer either, but I decided to do some casual benchmarking this week to hopefully contribute to the discussion.
I skimmed the source in
Objects/and concluded that dicts and sets already use a larger growth factor. I'm guessing this is because resizing them is more expensive - rather than just being a memory copy, it involves reinserting all the elements. The code for Unicode objects usually precalculates sizes exactly instead of using a growth factor. That leaveslist,bytes, andbytearray.I wrote four quick patches so that I could benchmark growth factors of 1.5 and 2, both for
listonly and for all three types. I tried to match the style of the existing code as closely as possible. For example, I'd personally avoid the floating point operations here in favor of moving the calculation out of theif, but I kept the logic as-is to avoid biasing the benchmarks.The following 22 benchmarks were failing, but that still left the majority working so I decided to continue.
My setup is Debian 12, an Intel i7-11700KF CPU with a big air cooler, and GCC 12.2.0. The benchmarks were run in a bare-bones window manager session; the X server was running, but potentially CPU-intensive services like Syncthing were stopped. I used commit
2b47f46d7dc30d27b2486991fea4acd83553294bof CPython and9e29e3f69595fed2b74a8dab081ce9caf1640cd9of pyperformance.Here are the geometric means of the runtime ratios of all benchmarks:
mainlistlistlist,bytes, andbytearraylist,bytes, andbytearrayIt looks like raising the growth factor for
listto 1.5 speeds up CPython by 1%, but it's not a good idea to raise the factor further or to do that forbytesandbytearray.I'm not sure how much of that is real and how much is statistical noise, so I'd appreciate reproductions. I've included the steps and files below, in the "Reproduction steps" section.
Per-benchmark results
I've only included the statistically significant benchmarks because this post is long enough already.
Growth factor 1.5 for
list:Growth factor 2 for
list:Growth factor 1.5 for
list,bytes, andbytearray:Growth factor 2 for
list,bytes, andbytearray:Original result files here: json-results.tar.gz.
Reproduction steps
Download the source files here: patches-config-and-script.tar.gz.
Put the patches and
benchmark.confin~/pybench.Install pyperformance:
pipx uninstall pyperformance pipx install 'git+https://github.com/python/pyperformance.git@9e29e3f69595fed2b74a8dab081ce9caf1640cd9'Clone CPython:
Stop unnecessary programs, then tune the system for benchmarks:
sudo ~/.local/pipx/venvs/pyperformance/bin/pyperf system tuneNow you can benchmark
mainand the patched versions:If you want, you can also use my script to screen-scrape pyperformance's output and calculate the geometric means: