Skip to content

Commit 38a4c0c

Browse files
committed
gh-107137: Add _PyTupleBuilder API to the internal C API
Add _PyTupleBuilder structure and functions: * _PyTupleBuilder_Init() * _PyTupleBuilder_Alloc() * _PyTupleBuilder_Append() * _PyTupleBuilder_AppendUnsafe() * _PyTupleBuilder_Finish() * _PyTupleBuilder_Dealloc() The builder tracks the size of the tuple and resize it in _PyTupleBuilder_Finish() if needed. Don't allocate empty tuple. Allocate an array of 16 objects on the stack to avoid allocating small tuple. _PyTupleBuilder_Append() overallocates the tuple by 25% to reduce the number of _PyTuple_Resize() calls. Do no track the temporary internal tuple by the GC before _PyTupleBuilder_Finish() creates the final complete and consistent tuple object. Use _PyTupleBuilder API in itertools batched_traverse(), PySequence_Tuple() and initialize_structseq_dict(). Add also helper functions: * _PyTuple_ResizeNoTrack() * _PyTuple_NewNoTrack()
1 parent 0ae4870 commit 38a4c0c

File tree

5 files changed

+241
-99
lines changed

5 files changed

+241
-99
lines changed

Include/internal/pycore_tuple.h

+138-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
#include "tupleobject.h" /* _PyTuple_CAST() */
12-
11+
extern PyObject* _PyTuple_NewNoTrack(Py_ssize_t size);
12+
extern int _PyTuple_ResizeNoTrack(PyObject **pv, Py_ssize_t newsize);
1313

1414
/* runtime lifecycle */
1515

@@ -73,6 +73,142 @@ typedef struct {
7373
PyTupleObject *it_seq; /* Set to NULL when iterator is exhausted */
7474
} _PyTupleIterObject;
7575

76+
77+
// --- _PyTupleBuilder API ---------------------------------------------------
78+
79+
typedef struct _PyTupleBuilder {
80+
PyObject* small_tuple[16];
81+
PyObject *tuple;
82+
PyObject **items;
83+
size_t size;
84+
size_t allocated;
85+
} _PyTupleBuilder;
86+
87+
static inline int
88+
_PyTupleBuilder_Alloc(_PyTupleBuilder *builder, size_t size)
89+
{
90+
if (size > (size_t)PY_SSIZE_T_MAX) {
91+
/* Check for overflow */
92+
PyErr_NoMemory();
93+
return -1;
94+
}
95+
if (size <= builder->allocated) {
96+
return 0;
97+
}
98+
99+
if (size <= Py_ARRAY_LENGTH(builder->small_tuple)) {
100+
assert(builder->tuple == NULL);
101+
builder->items = builder->small_tuple;
102+
builder->allocated = Py_ARRAY_LENGTH(builder->small_tuple);
103+
return 0;
104+
}
105+
106+
assert(size >= 1);
107+
if (builder->tuple != NULL) {
108+
if (_PyTuple_ResizeNoTrack(&builder->tuple, (Py_ssize_t)size) < 0) {
109+
return -1;
110+
}
111+
}
112+
else {
113+
builder->tuple = _PyTuple_NewNoTrack((Py_ssize_t)size);
114+
if (builder->tuple == NULL) {
115+
return -1;
116+
}
117+
118+
if (builder->size > 0) {
119+
memcpy(_PyTuple_ITEMS(builder->tuple),
120+
builder->small_tuple,
121+
builder->size * sizeof(builder->small_tuple[0]));
122+
}
123+
}
124+
builder->items = _PyTuple_ITEMS(builder->tuple);
125+
builder->allocated = size;
126+
return 0;
127+
}
128+
129+
static inline int
130+
_PyTupleBuilder_Init(_PyTupleBuilder *builder, Py_ssize_t size)
131+
{
132+
memset(builder, 0, sizeof(*builder));
133+
134+
int res;
135+
if (size > 0) {
136+
res = _PyTupleBuilder_Alloc(builder, (size_t)size);
137+
}
138+
else {
139+
res = 0;
140+
}
141+
return res;
142+
}
143+
144+
// The tuple builder must have already enough allocated items to store item.
145+
static inline void
146+
_PyTupleBuilder_AppendUnsafe(_PyTupleBuilder *builder, PyObject *item)
147+
{
148+
assert(builder->items != NULL);
149+
assert(builder->size < builder->allocated);
150+
builder->items[builder->size] = item;
151+
builder->size++;
152+
}
153+
154+
static inline int
155+
_PyTupleBuilder_Append(_PyTupleBuilder *builder, PyObject *item)
156+
{
157+
if (builder->size >= (size_t)PY_SSIZE_T_MAX) {
158+
// prevent integer overflow
159+
PyErr_NoMemory();
160+
return -1;
161+
}
162+
if (builder->size >= builder->allocated) {
163+
size_t allocated = builder->size;
164+
allocated += (allocated >> 2); // Over-allocate by 25%
165+
if (_PyTupleBuilder_Alloc(builder, allocated) < 0) {
166+
return -1;
167+
}
168+
}
169+
_PyTupleBuilder_AppendUnsafe(builder, item);
170+
return 0;
171+
}
172+
173+
static inline void
174+
_PyTupleBuilder_Dealloc(_PyTupleBuilder *builder)
175+
{
176+
Py_CLEAR(builder->tuple);
177+
builder->items = NULL;
178+
builder->size = 0;
179+
builder->allocated = 0;
180+
}
181+
182+
static inline PyObject*
183+
_PyTupleBuilder_Finish(_PyTupleBuilder *builder)
184+
{
185+
if (builder->size == 0) {
186+
_PyTupleBuilder_Dealloc(builder);
187+
// return the empty tuple singleton
188+
return PyTuple_New(0);
189+
}
190+
191+
if (builder->tuple != NULL) {
192+
if (_PyTuple_ResizeNoTrack(&builder->tuple, (Py_ssize_t)builder->size) < 0) {
193+
_PyTupleBuilder_Dealloc(builder);
194+
return NULL;
195+
}
196+
197+
PyObject *result = builder->tuple;
198+
builder->tuple = NULL;
199+
// Avoid _PyObject_GC_TRACK() to avoid including pycore_object.h
200+
PyObject_GC_Track(result);
201+
return result;
202+
}
203+
else {
204+
PyObject *tuple = _PyTuple_FromArraySteal(builder->items,
205+
(Py_ssize_t)builder->size);
206+
builder->size = 0;
207+
return tuple;
208+
}
209+
}
210+
211+
76212
#ifdef __cplusplus
77213
}
78214
#endif

Modules/itertoolsmodule.c

+26-31
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "pycore_moduleobject.h" // _PyModule_GetState()
55
#include "pycore_typeobject.h" // _PyType_GetModuleState()
66
#include "pycore_object.h" // _PyObject_GC_TRACK()
7-
#include "pycore_tuple.h" // _PyTuple_ITEMS()
7+
#include "pycore_tuple.h" // _PyTupleBuilder
88
#include "structmember.h" // PyMemberDef
99
#include <stddef.h> // offsetof()
1010

@@ -193,47 +193,42 @@ batched_traverse(batchedobject *bo, visitproc visit, void *arg)
193193
static PyObject *
194194
batched_next(batchedobject *bo)
195195
{
196-
Py_ssize_t i;
197-
Py_ssize_t n = bo->batch_size;
198196
PyObject *it = bo->it;
199-
PyObject *item;
200-
PyObject *result;
201-
202197
if (it == NULL) {
203198
return NULL;
204199
}
205-
result = PyTuple_New(n);
206-
if (result == NULL) {
200+
201+
_PyTupleBuilder builder;
202+
Py_ssize_t n = bo->batch_size;
203+
if (_PyTupleBuilder_Init(&builder, n) < 0) {
207204
return NULL;
208205
}
206+
209207
iternextfunc iternext = *Py_TYPE(it)->tp_iternext;
210-
PyObject **items = _PyTuple_ITEMS(result);
211-
for (i=0 ; i < n ; i++) {
212-
item = iternext(it);
208+
for (Py_ssize_t i=0 ; i < n; i++) {
209+
PyObject *item = iternext(it);
213210
if (item == NULL) {
214-
goto null_item;
211+
if (PyErr_Occurred()) {
212+
if (!PyErr_ExceptionMatches(PyExc_StopIteration)) {
213+
/* Input raised an exception other than StopIteration */
214+
goto error;
215+
}
216+
PyErr_Clear();
217+
// StopIteration was raised
218+
}
219+
if (i == 0) {
220+
goto error;
221+
}
222+
break;
215223
}
216-
items[i] = item;
224+
_PyTupleBuilder_AppendUnsafe(&builder, item);
217225
}
218-
return result;
226+
return _PyTupleBuilder_Finish(&builder);
219227

220-
null_item:
221-
if (PyErr_Occurred()) {
222-
if (!PyErr_ExceptionMatches(PyExc_StopIteration)) {
223-
/* Input raised an exception other than StopIteration */
224-
Py_CLEAR(bo->it);
225-
Py_DECREF(result);
226-
return NULL;
227-
}
228-
PyErr_Clear();
229-
}
230-
if (i == 0) {
231-
Py_CLEAR(bo->it);
232-
Py_DECREF(result);
233-
return NULL;
234-
}
235-
_PyTuple_Resize(&result, i);
236-
return result;
228+
error:
229+
_PyTupleBuilder_Dealloc(&builder);
230+
Py_CLEAR(bo->it);
231+
return NULL;
237232
}
238233

239234
static PyType_Slot batched_slots[] = {

Objects/abstract.c

+26-44
Original file line numberDiff line numberDiff line change
@@ -2074,11 +2074,6 @@ PySequence_DelSlice(PyObject *s, Py_ssize_t i1, Py_ssize_t i2)
20742074
PyObject *
20752075
PySequence_Tuple(PyObject *v)
20762076
{
2077-
PyObject *it; /* iter(v) */
2078-
Py_ssize_t n; /* guess for result tuple size */
2079-
PyObject *result = NULL;
2080-
Py_ssize_t j;
2081-
20822077
if (v == NULL) {
20832078
return null_error();
20842079
}
@@ -2091,66 +2086,53 @@ PySequence_Tuple(PyObject *v)
20912086
a copy, so there's no need for exactness below. */
20922087
return Py_NewRef(v);
20932088
}
2094-
if (PyList_CheckExact(v))
2089+
if (PyList_CheckExact(v)) {
20952090
return PyList_AsTuple(v);
2091+
}
20962092

2097-
/* Get iterator. */
2098-
it = PyObject_GetIter(v);
2099-
if (it == NULL)
2093+
_PyTupleBuilder builder;
2094+
if (_PyTupleBuilder_Init(&builder, 0) < 0) {
21002095
return NULL;
2096+
}
2097+
2098+
/* Get iterator. */
2099+
PyObject *it = PyObject_GetIter(v); // iter(v)
2100+
if (it == NULL) {
2101+
goto Fail;
2102+
}
21012103

21022104
/* Guess result size and allocate space. */
2103-
n = PyObject_LengthHint(v, 10);
2104-
if (n == -1)
2105+
Py_ssize_t n = PyObject_LengthHint(v, 10); // Guess for result tuple size
2106+
if (n == -1) {
21052107
goto Fail;
2106-
result = PyTuple_New(n);
2107-
if (result == NULL)
2108+
}
2109+
if (_PyTupleBuilder_Alloc(&builder, n) < 0) {
21082110
goto Fail;
2111+
}
21092112

21102113
/* Fill the tuple. */
2114+
Py_ssize_t j;
21112115
for (j = 0; ; ++j) {
21122116
PyObject *item = PyIter_Next(it);
21132117
if (item == NULL) {
2114-
if (PyErr_Occurred())
2118+
if (PyErr_Occurred()) {
21152119
goto Fail;
2120+
}
21162121
break;
21172122
}
2118-
if (j >= n) {
2119-
size_t newn = (size_t)n;
2120-
/* The over-allocation strategy can grow a bit faster
2121-
than for lists because unlike lists the
2122-
over-allocation isn't permanent -- we reclaim
2123-
the excess before the end of this routine.
2124-
So, grow by ten and then add 25%.
2125-
*/
2126-
newn += 10u;
2127-
newn += newn >> 2;
2128-
if (newn > PY_SSIZE_T_MAX) {
2129-
/* Check for overflow */
2130-
PyErr_NoMemory();
2131-
Py_DECREF(item);
2132-
goto Fail;
2133-
}
2134-
n = (Py_ssize_t)newn;
2135-
if (_PyTuple_Resize(&result, n) != 0) {
2136-
Py_DECREF(item);
2137-
goto Fail;
2138-
}
2123+
2124+
if (_PyTupleBuilder_Append(&builder, item) < 0) {
2125+
Py_DECREF(item);
2126+
goto Fail;
21392127
}
2140-
PyTuple_SET_ITEM(result, j, item);
21412128
}
21422129

2143-
/* Cut tuple back if guess was too large. */
2144-
if (j < n &&
2145-
_PyTuple_Resize(&result, j) != 0)
2146-
goto Fail;
2147-
21482130
Py_DECREF(it);
2149-
return result;
2131+
return _PyTupleBuilder_Finish(&builder);
21502132

21512133
Fail:
2152-
Py_XDECREF(result);
2153-
Py_DECREF(it);
2134+
_PyTupleBuilder_Dealloc(&builder);
2135+
Py_XDECREF(it);
21542136
return NULL;
21552137
}
21562138

0 commit comments

Comments
 (0)