Skip to content

gh-160858: Use the list freelist in list() - #160859

Merged
corona10 merged 2 commits into
python:mainfrom
cmaloney:list_new_freelist
Oct 29, 2026
Merged

corona10 merged 2 commits into
python:mainfrom
cmaloney:list_new_freelist

Conversation

@cmaloney

@cmaloney cmaloney commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

list() allocated its result with PyType_GenericAlloc(), bypassing the freelist that PyList_New() and the [] display use. For an exact list, allocate with PyList_New(0) instead. The object handed to list.init() is in the same state either way: empty, with no item array, and tracked by the GC. Subclasses still use PyType_GenericAlloc().

pyperf, PGO+LTO, base 19270d3 :

Benchmark main branch
list() 21.2 ns 15.3 ns: 1.38x faster
list(d) 36.1 ns 27.4 ns: 1.32x faster
list(t) 30.3 ns 23.9 ns: 1.27x faster
list(l) 32.0 ns 25.7 ns: 1.24x faster
list(r) 43.6 ns 36.0 ns: 1.21x faster
x + 1 10.4 ns 9.70 ns: 1.07x faster
len(l) 9.87 ns 9.53 ns: 1.03x faster
l[0] 8.24 ns 8.00 ns: 1.03x faster
L(t) 48.2 ns 47.4 ns: 1.02x faster
tuple(l) 15.4 ns 16.0 ns: 1.04x slower
Geometric mean (ref) 1.13x faster

Not significant: []. x + 1, len(l), l[0], L(t) and tuple(l) don't run the changed code; their movement is layout noise, within the up-to-1.07x spread between two runs of the same main build.

list() allocated its result with PyType_GenericAlloc(), bypassing the
freelist that PyList_New() and the [] display use.  For an exact list,
allocate with PyList_New(0) instead.  The object handed to
list.__init__() is in the same state either way: empty, with no item
array, and tracked by the GC.  Subclasses still use
PyType_GenericAlloc().

pyperf, PGO+LTO, base 19270d3, two interleaved rounds per side
(a third round, disturbed by machine load, was dropped):

| Benchmark      | main    | branch                |
|----------------|:-------:|:---------------------:|
| list()         | 21.2 ns | 15.3 ns: 1.38x faster |
| list(d)        | 36.1 ns | 27.4 ns: 1.32x faster |
| list(t)        | 30.3 ns | 23.9 ns: 1.27x faster |
| list(l)        | 32.0 ns | 25.7 ns: 1.24x faster |
| list(r)        | 43.6 ns | 36.0 ns: 1.21x faster |
| x + 1          | 10.4 ns | 9.70 ns: 1.07x faster |
| len(l)         | 9.87 ns | 9.53 ns: 1.03x faster |
| l[0]           | 8.24 ns | 8.00 ns: 1.03x faster |
| L(t)           | 48.2 ns | 47.4 ns: 1.02x faster |
| tuple(l)       | 15.4 ns | 16.0 ns: 1.04x slower |
| Geometric mean | (ref)   | 1.13x faster          |

Not significant: [].  x + 1, len(l), l[0], L(t) and tuple(l) don't
run the changed code; their movement is layout noise, within the
up-to-1.07x spread between two runs of the same main build.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@bedevere-app bedevere-app Bot added awaiting review type-feature A feature request or enhancement labels Oct 29, 2026
Comment thread Objects/listobject.c

PyObject *list = PyType_GenericAlloc(_PyType_CAST(type), 0);
/* use PyList_New() to make exact lists from the freelist. */
PyObject *list = (type == (PyObject *)&PyList_Type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This should always be true because tp_vectorcall is not inherited but with the handwritten vector call, it isn't asserted in debug builds

@corona10 corona10 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm!

@corona10
corona10 merged commit 386d9d0 into python:main Oct 29, 2026
53 checks passed
@cmaloney
cmaloney deleted the list_new_freelist branch October 29, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip news type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants