[Python] Listのcapacityを取得する

とある事情から、Pythonのlistがどれくらいのメモリを確保しているのか知りたくなったので、listのcapacityを取得する関数を作ってみました。

C言語拡張を利用して取得する

Pythonコードから確保済みメモリの大きさを知るすべを知らないので、CPython向けのC言語拡張を利用してリストの構造体(PyListObject)から確保済みのメモリの大きさを取得してみたいと思います。

CPython3.9.0の時点でPyListObjectは以下のように定義されています。

typedef struct {
    PyObject_VAR_HEAD
    Py_ssize_t allocated; // 確保済みのメモリに入る要素数を管理するメンバ
} PyListObject;

https://github.com/python/cpython/blob/master/Include/cpython/listobject.h より抜粋

確保済みの要素数はallocatedメンバで管理されているので、この値を返却する関数を作成します。

こんな感じになりました。

static PyObject *
list_capacity(PyObject *self, PyObject *args) {
    PyObject* o;
    if (!PyArg_ParseTuple(args, "O", &o)){
        return NULL;
    }
    if (!PyList_Check(o)) {
        PyErr_SetString(PyExc_TypeError, "capacity excepted list object.");
        return NULL;
    }
    PyListObject* list = (PyListObject*)o;
    long allocated = list->allocated; // 確保済みの要素数を取得
    PyObject* capacity = PyLong_FromLong(allocated);
    if (capacity == NULL) {
        return NULL;
    }
    return capacity;
}

list-reserve

折角作ったので、お手軽に利用できるようにライブラリにして公開してみました。

https://github.com/ChanTsune/list-reserve

pip install list-reserve

capacity

確保済みのメモリに入る最大の要素数を返却します。

from list_reserve import capacity

l = [1, 2, 3]
print(capacity(l)) # 3

返却する値は、確保済みのメモリに入る最大の要素数なので実際に確保しているメモリのサイズは、64bit環境であればcapacity * 8byte、32bit環境であれば要素数 * 4byteになるはずです。

reserve

リストのメモリを確保します。

from list_reserve import reserve, capacity

l = []
reserve(l, 10)

print(len(l)) # 0

print(capacity(l)) # 10

ただし、リストの要素数の二倍以上確保してしまうと、Pythonのリストのメモリの自動管理の都合で、次の操作で確保済みのメモリが開放されてしまうので要注意です。

shrink_to_fit

メモリの大きさをリストの要素数まで切り詰めます。

from list_reserve import capacity, shrink_to_fit

l = list(range(100))

print(capacity(l)) # 118

shrink_to_fit(l)

print(capacity(l)) # 100

C++のstd::vectorshrink_to_fitメソッドと同様の働きをします。