Skip to main content

Objects/tupleobject.c (part 6)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/tupleobject.c

This annotation covers allocation and hashing internals. See objects_tupleobject5_detail for tuple.__new__, tuple.__getitem__, and tuple.count/index.

Map

LinesSymbolRole
1-80Free list cacheReuse tuples of size 0-20 without going through malloc
81-180PyTuple_PackAllocate a new tuple from a varargs list
181-280_PyTuple_FromArrayAllocate from a PyObject ** array
281-380tuple.__hash__FNV-like hash combining all element hashes
381-500tuple.__add__ / tuple.__mul__Concatenation and repetition

Reading

Free list cache

// CPython: Objects/tupleobject.c:38 free list
#define PyTuple_MAXSAVESIZE 20
#define PyTuple_MAXFREELIST 2000

static PyTupleObject *free_list[PyTuple_MAXSAVESIZE];
static int numfree[PyTuple_MAXSAVESIZE];

static void
tupledealloc(PyTupleObject *op)
{
Py_ssize_t i = Py_SIZE(op);
if (0 < i && i < PyTuple_MAXSAVESIZE && numfree[i] < PyTuple_MAXFREELIST) {
/* Return to free list: store next pointer in first slot */
op->ob_item[0] = (PyObject *)free_list[i];
free_list[i] = op;
numfree[i]++;
return;
}
PyObject_GC_Del(op);
}

The free list avoids malloc/free for the most common tuple sizes. return (1, 2, 3) from a function allocates a 3-tuple; when it's discarded, the memory goes back to free_list[3]. The next (a, b, c) tuple creation reuses it.

PyTuple_Pack

// CPython: Objects/tupleobject.c:280 PyTuple_Pack
PyObject *
PyTuple_Pack(Py_ssize_t n, ...)
{
va_list vargs;
va_start(vargs, n);
PyTupleObject *result = (PyTupleObject *)PyTuple_New(n);
if (result == NULL) { va_end(vargs); return NULL; }
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *o = va_arg(vargs, PyObject *);
result->ob_item[i] = Py_NewRef(o);
}
va_end(vargs);
return (PyObject *)result;
}

PyTuple_Pack(3, a, b, c) is used heavily in C extension code. It is equivalent to (a, b, c) in Python. All arguments must already be Python objects (no implicit conversion).

tuple.__hash__

// CPython: Objects/tupleobject.c:380 tuplehash
static Py_hash_t
tuplehash(PyTupleObject *v)
{
Py_uhash_t x = 0x345678UL;
Py_ssize_t len = Py_SIZE(v);
PyObject **p = v->ob_item;
Py_uhash_t mult = _PyHASH_MULTIPLIER;
while (--len >= 0) {
Py_uhash_t y = (Py_uhash_t)PyObject_Hash(*p++);
if (y == (Py_uhash_t)-1) return -1;
x = (x ^ y) * mult;
mult += (Py_uhash_t)(82520UL + len + len);
}
x += 97531UL;
if (x == (Py_uhash_t)-1) x = -2;
return (Py_hash_t)x;
}

The tuple hash combines element hashes using a multiplicative FNV-like accumulator. The mult value grows with each iteration, reducing collisions for tuples with the same elements in different orders. (1, 2, 3) and (3, 2, 1) have different hashes.

tuple.__mul__

// CPython: Objects/tupleobject.c:480 tuplerepeat
static PyObject *
tuplerepeat(PyTupleObject *a, Py_ssize_t n)
{
if (Py_SIZE(a) == 0 || n <= 0) return PyTuple_New(0);
if (n > PY_SSIZE_T_MAX / Py_SIZE(a))
return PyErr_NoMemory();
Py_ssize_t size = Py_SIZE(a) * n;
PyTupleObject *np = (PyTupleObject *)PyTuple_New(size);
PyObject **p = np->ob_item;
for (Py_ssize_t i = 0; i < n; i++) {
for (Py_ssize_t j = 0; j < Py_SIZE(a); j++) {
*p++ = Py_NewRef(a->ob_item[j]);
}
}
return (PyObject *)np;
}

(1, 2) * 3 returns (1, 2, 1, 2, 1, 2). The elements are shared (not copied) since tuples are immutable. The overflow check prevents integer overflow in Py_SIZE(a) * n.

gopy notes

The free list is objects.TupleFreeList in objects/tuple.go. PyTuple_Pack is objects.TuplePack. tuple.__hash__ uses the same FNV-like accumulator. tuple.__mul__ is objects.TupleRepeat sharing element pointers via objects.Object interface values.