diff --git a/Objects/listobject.c b/Objects/listobject.c --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -22,8 +22,11 @@ * Note that self->ob_item may change, and even if newsize is less * than ob_size on entry. */ + +#define list_resize(self, newsize) list_resize2(self, newsize, 1) + static int -list_resize(PyListObject *self, Py_ssize_t newsize) +list_resize2(PyListObject *self, Py_ssize_t newsize, int keep_overallocated) { PyObject **items; size_t new_allocated; @@ -33,7 +36,7 @@ to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. */ - if (allocated >= newsize && newsize >= (allocated >> 1)) { + if (allocated >= newsize && (keep_overallocated || newsize >= (allocated >> 1))) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; @@ -2318,6 +2321,42 @@ return PyLong_FromSsize_t(res); } +static PyObject * +list_preallocate(PyListObject *self, PyObject *v) +{ + Py_ssize_t i, new_allocated; + PyObject **items; + + i = PyNumber_AsSsize_t(v, PyExc_ValueError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if (i <= 0) { + PyErr_SetString(PyExc_ValueError, + "size <= 0"); + return NULL; + } + + new_allocated = self->allocated + i; + if (new_allocated > (PY_SIZE_MAX / sizeof(PyObject *))) { + PyErr_NoMemory(); + return NULL; + } + + items = self->ob_item; + PyMem_RESIZE(items, PyObject *, new_allocated); + self->ob_item = items; + self->allocated = new_allocated; + Py_RETURN_NONE; +} + +static PyObject * +list_shrink(PyListObject *self) +{ + list_resize2(self, Py_SIZE(self), 0); /* can't fail */ + Py_RETURN_NONE; +} + + static PyObject *list_iter(PyObject *seq); static PyObject *list_reversed(PyListObject* seq, PyObject* unused); @@ -2327,6 +2366,10 @@ "L.__reversed__() -- return a reverse iterator over the list"); PyDoc_STRVAR(sizeof_doc, "L.__sizeof__() -- size of L in memory, in bytes"); +PyDoc_STRVAR(preallocate_doc, +"L.__preallocate__(size) -- preallocate slots"); +PyDoc_STRVAR(shrink_doc, +"L.__shrink__() -- shrink slots"); PyDoc_STRVAR(clear_doc, "L.clear() -> None -- remove all items from L"); PyDoc_STRVAR(copy_doc, @@ -2359,6 +2402,8 @@ {"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, getitem_doc}, {"__reversed__",(PyCFunction)list_reversed, METH_NOARGS, reversed_doc}, {"__sizeof__", (PyCFunction)list_sizeof, METH_NOARGS, sizeof_doc}, + {"__preallocate__", (PyCFunction)list_preallocate, METH_O, preallocate_doc}, + {"__shrink__", (PyCFunction)list_shrink, METH_NOARGS, shrink_doc}, {"clear", (PyCFunction)listclear, METH_NOARGS, clear_doc}, {"copy", (PyCFunction)listcopy, METH_NOARGS, copy_doc}, {"append", (PyCFunction)listappend, METH_O, append_doc}, diff --git a/Objects/listobject.c b/Objects/listobject.c --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -22,8 +22,9 @@ * Note that self->ob_item may change, and even if newsize is less * than ob_size on entry. */ + static int -list_resize(PyListObject *self, Py_ssize_t newsize) +list_resize(PyListObject *self, Py_ssize_t newsize, int keep_overallocated) { PyObject **items; size_t new_allocated; @@ -33,7 +34,7 @@ to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. */ - if (allocated >= newsize && newsize >= (allocated >> 1)) { + if (allocated >= newsize && (keep_overallocated || newsize >= (allocated >> 1))) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; @@ -251,7 +252,7 @@ return -1; } - if (list_resize(self, n+1) == -1) + if (list_resize(self, n+1, 1) == -1) return -1; if (where < 0) { @@ -291,7 +292,7 @@ return -1; } - if (list_resize(self, n+1) == -1) + if (list_resize(self, n+1, 1) == -1) return -1; Py_INCREF(v); @@ -646,12 +647,12 @@ if (d < 0) { /* Delete -d items */ memmove(&item[ihigh+d], &item[ihigh], (Py_SIZE(a) - ihigh)*sizeof(PyObject *)); - list_resize(a, Py_SIZE(a) + d); + list_resize(a, Py_SIZE(a) + d, 0); item = a->ob_item; } else if (d > 0) { /* Insert d items */ k = Py_SIZE(a); - if (list_resize(a, k+d) < 0) + if (list_resize(a, k+d, 0) < 0) goto Error; item = a->ob_item; memmove(&item[ihigh+d], &item[ihigh], @@ -706,7 +707,7 @@ return PyErr_NoMemory(); } - if (list_resize(self, size*n) == -1) + if (list_resize(self, size*n, 0) == -1) return NULL; p = size; @@ -799,7 +800,7 @@ Py_RETURN_NONE; } m = Py_SIZE(self); - if (list_resize(self, m + n) == -1) { + if (list_resize(self, m + n, 0) == -1) { Py_DECREF(b); return NULL; } @@ -835,7 +836,7 @@ mn = m + n; if (mn >= m) { /* Make room. */ - if (list_resize(self, mn) == -1) + if (list_resize(self, mn, 0) == -1) goto error; /* Make the list sane again. */ Py_SIZE(self) = m; @@ -872,7 +873,7 @@ /* Cut back result list if initial guess was too large. */ if (Py_SIZE(self) < self->allocated) - list_resize(self, Py_SIZE(self)); /* shrinking can't fail */ + list_resize(self, Py_SIZE(self), 0); /* shrinking can't fail */ Py_DECREF(it); Py_RETURN_NONE; @@ -924,7 +925,7 @@ } v = self->ob_item[i]; if (i == Py_SIZE(self) - 1) { - status = list_resize(self, Py_SIZE(self) - 1); + status = list_resize(self, Py_SIZE(self) - 1, 0); assert(status >= 0); return v; /* and v now owns the reference the list had */ } @@ -2318,6 +2319,42 @@ return PyLong_FromSsize_t(res); } +static PyObject * +list_preallocate(PyListObject *self, PyObject *v) +{ + Py_ssize_t i, new_allocated; + PyObject **items; + + i = PyNumber_AsSsize_t(v, PyExc_ValueError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if (i <= 0) { + PyErr_SetString(PyExc_ValueError, + "size <= 0"); + return NULL; + } + + new_allocated = self->allocated + i; + if (new_allocated > (PY_SIZE_MAX / sizeof(PyObject *))) { + PyErr_NoMemory(); + return NULL; + } + + items = self->ob_item; + PyMem_RESIZE(items, PyObject *, new_allocated); + self->ob_item = items; + self->allocated = new_allocated; + Py_RETURN_NONE; +} + +static PyObject * +list_shrink(PyListObject *self) +{ + list_resize(self, Py_SIZE(self), 0); /* shrinking can't fail */ + Py_RETURN_NONE; +} + + static PyObject *list_iter(PyObject *seq); static PyObject *list_reversed(PyListObject* seq, PyObject* unused); @@ -2327,6 +2364,10 @@ "L.__reversed__() -- return a reverse iterator over the list"); PyDoc_STRVAR(sizeof_doc, "L.__sizeof__() -- size of L in memory, in bytes"); +PyDoc_STRVAR(preallocate_doc, +"L.__preallocate__(size) -- preallocate slots"); +PyDoc_STRVAR(shrink_doc, +"L.__shrink__() -- shrink slots"); PyDoc_STRVAR(clear_doc, "L.clear() -> None -- remove all items from L"); PyDoc_STRVAR(copy_doc, @@ -2359,6 +2400,8 @@ {"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, getitem_doc}, {"__reversed__",(PyCFunction)list_reversed, METH_NOARGS, reversed_doc}, {"__sizeof__", (PyCFunction)list_sizeof, METH_NOARGS, sizeof_doc}, + {"__preallocate__", (PyCFunction)list_preallocate, METH_O, preallocate_doc}, + {"__shrink__", (PyCFunction)list_shrink, METH_NOARGS, shrink_doc}, {"clear", (PyCFunction)listclear, METH_NOARGS, clear_doc}, {"copy", (PyCFunction)listcopy, METH_NOARGS, copy_doc}, {"append", (PyCFunction)listappend, METH_O, append_doc}, @@ -2526,7 +2569,7 @@ } Py_SIZE(self) -= slicelength; - list_resize(self, Py_SIZE(self)); + list_resize(self, Py_SIZE(self), 0); for (i = 0; i < slicelength; i++) { Py_DECREF(garbage[i]);