Fundamentals 9 min read

Why Python Caches Small Integers and How the Internal Object Pools Work

This article explains Python's integer object implementation, detailing how small integers are cached in a fixed‑size pool, how larger integers are managed through a dynamic PyIntBlock structure, and why using xrange (or range in Python 3) avoids memory bloat.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Why Python Caches Small Integers and How the Internal Object Pools Work

Python Integer Object Implementation Overview

The following explains the internal mechanisms CPython uses to create and manage integer objects, including the small‑integer cache, the general integer pool, and the memory‑allocation strategy.

>> a = 1
>>> b = 1
>>> id(a) == id(b)
True
>>> c = 257
>>> d = 257
>>> id(c) == id(d)
False

PyIntObject Structure

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

Constructor Functions

PyAPI_FUNC(PyObject *) PyInt_FromString(char *, char **);
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE *, Py_ssize_t, int);
#endif
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);

All constructors ultimately call PyInt_FromLong, which contains the core allocation logic.

PyInt_FromLong Implementation

PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
    /* If the value is within the small‑integer range, return the cached object */
    if (-(NSMALLNEGINTS) <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
        return (PyObject *)v;
    }
    /* Allocate from the free list or create a new block */
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *)v;
}

Small‑Integer Object Pool

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif

The pool is a pointer array covering the range [-5, 257); each entry points to a unique PyIntObject. Objects in this range are created once during interpreter startup and reused, which explains the True result for a and b above.

Small integer pool layout
Small integer pool layout

General Integer Object Pool (PyIntBlock)

#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE      8       /* Enough for a 64‑bit pointer */
#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};

typedef struct _intblock PyIntBlock;

Two static pointers keep track of the allocation state:

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

Block Allocation (fill_free_list)

static PyIntObject *fill_free_list(void)
{
    PyIntObject *p, *q;
    PyIntBlock *new_block = (PyIntObject *)PyMem_MALLOC(sizeof(PyIntBlock));
    if (new_block == NULL)
        return (PyIntObject *)PyErr_NoMemory();
    /* Link the new block into the block list */
    new_block->next = block_list;
    block_list = new_block;
    /* Build a singly‑linked free list from the objects array */
    p = &new_block->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p) {
        Py_TYPE(q) = (struct _typeobject *)q - 1; /* reuse ob_type as next pointer */
    }
    Py_TYPE(p) = NULL;
    return p + N_INTOBJECTS - 1; /* return head of the free list */
}

When the free list is exhausted, a new PyIntBlock is allocated, linked into block_list, and its objects are chained together to form a new free list.

PyIntBlock layout
PyIntBlock layout

Deallocation (int_dealloc)

static void int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) {
        Py_TYPE(v) = (struct _typeobject *)free_list;
        free_list = v;
    } else {
        ((struct _typeobject *)Py_TYPE(v))->tp_free((PyObject *)v);
    }
}

Exact integer objects are returned to the free list; other types use their type‑specific deallocator.

Practical Implications

Because PyIntBlock memory is never released before interpreter shutdown, generating large ranges with range(100000) in Python 2 can retain a substantial amount of memory. Using xrange (or range in Python 3, which behaves like xrange) avoids this issue.

Memory usage illustration
Memory usage illustration

In summary, CPython optimizes integer handling by caching a small range of frequently used values and by allocating larger integers in blocks that are reclaimed via a free‑list mechanism, ensuring fast object creation while managing memory efficiently.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory Managementobject poolCPythoninteger objectsmall integers
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.