Skip to content

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

Open
cmaloney wants to merge 1 commit into
python:mainfrom
cmaloney:list_new_freelist
Open

cmaloney wants to merge 1 commit 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 <noreply@anthropic.com>
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

Comment on lines +1 to +2
Speed up :class:`list` construction by reusing list objects from the free
list, as ``[]`` already does.

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.

Does not need to be known to end user.

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting merge skip news type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants