Commit db067af1 authored by Victor Stinner's avatar Victor Stinner

Issue #21233: Add new C functions: PyMem_RawCalloc(), PyMem_Calloc(),

PyObject_Calloc(), _PyObject_GC_Calloc(). bytes(int) and bytearray(int) are now
using ``calloc()`` instead of ``malloc()`` for large objects which is faster
and use less memory (until the bytearray buffer is filled with data).
parent d50c3f3f
......@@ -92,8 +92,8 @@ functions are thread-safe, the :term:`GIL <global interpreter lock>` does not
need to be held.
The default raw memory block allocator uses the following functions:
:c:func:`malloc`, :c:func:`realloc` and :c:func:`free`; call ``malloc(1)`` when
requesting zero bytes.
:c:func:`malloc`, :c:func:`calloc`, :c:func:`realloc` and :c:func:`free`; call
``malloc(1)`` (or ``calloc(1, 1)``) when requesting zero bytes.
.. versionadded:: 3.4
......@@ -106,6 +106,17 @@ requesting zero bytes.
been initialized in any way.
.. c:function:: void* PyMem_RawCalloc(size_t nelem, size_t elsize)
Allocates *nelem* elements each whose size in bytes is *elsize* and returns
a pointer of type :c:type:`void\*` to the allocated memory, or *NULL* if the
request fails. The memory is initialized to zeros. Requesting zero elements
or elements of size zero bytes returns a distinct non-*NULL* pointer if
possible, as if ``PyMem_RawCalloc(1, 1)`` had been called instead.
.. versionadded:: 3.5
.. c:function:: void* PyMem_RawRealloc(void *p, size_t n)
Resizes the memory block pointed to by *p* to *n* bytes. The contents will
......@@ -136,8 +147,8 @@ behavior when requesting zero bytes, are available for allocating and releasing
memory from the Python heap.
The default memory block allocator uses the following functions:
:c:func:`malloc`, :c:func:`realloc` and :c:func:`free`; call ``malloc(1)`` when
requesting zero bytes.
:c:func:`malloc`, :c:func:`calloc`, :c:func:`realloc` and :c:func:`free`; call
``malloc(1)`` (or ``calloc(1, 1)``) when requesting zero bytes.
.. warning::
......@@ -152,6 +163,17 @@ requesting zero bytes.
been called instead. The memory will not have been initialized in any way.
.. c:function:: void* PyMem_Calloc(size_t nelem, size_t elsize)
Allocates *nelem* elements each whose size in bytes is *elsize* and returns
a pointer of type :c:type:`void\*` to the allocated memory, or *NULL* if the
request fails. The memory is initialized to zeros. Requesting zero elements
or elements of size zero bytes returns a distinct non-*NULL* pointer if
possible, as if ``PyMem_Calloc(1, 1)`` had been called instead.
.. versionadded:: 3.5
.. c:function:: void* PyMem_Realloc(void *p, size_t n)
Resizes the memory block pointed to by *p* to *n* bytes. The contents will be
......@@ -222,11 +244,17 @@ Customize Memory Allocators
+----------------------------------------------------------+---------------------------------------+
| ``void* malloc(void *ctx, size_t size)`` | allocate a memory block |
+----------------------------------------------------------+---------------------------------------+
| ``void* calloc(void *ctx, size_t nelem, size_t elsize)`` | allocate a memory block initialized |
| | with zeros |
+----------------------------------------------------------+---------------------------------------+
| ``void* realloc(void *ctx, void *ptr, size_t new_size)`` | allocate or resize a memory block |
+----------------------------------------------------------+---------------------------------------+
| ``void free(void *ctx, void *ptr)`` | free a memory block |
+----------------------------------------------------------+---------------------------------------+
.. versionchanged:: 3.5
Add a new field ``calloc``.
.. c:type:: PyMemAllocatorDomain
Enum used to identify an allocator domain. Domains:
......
......@@ -164,7 +164,10 @@ Optimizations
Major performance enhancements have been added:
* None yet.
* Construction of ``bytes(int)`` and ``bytearray(int)`` (filled by zero bytes)
is faster and use less memory (until the bytearray buffer is filled with
data) for large objects. ``calloc()`` is used instead of ``malloc()`` to
allocate memory for these objects.
Build and C API Changes
......@@ -172,7 +175,12 @@ Build and C API Changes
Changes to Python's build process and to the C API include:
* None yet.
* New ``calloc`` functions:
* :c:func:`PyMem_RawCalloc`
* :c:func:`PyMem_Calloc`
* :c:func:`PyObject_Calloc`
* :c:func:`_PyObject_GC_Calloc`
Deprecated
......@@ -209,6 +217,9 @@ Porting to Python 3.5
This section lists previously described changes and other bugfixes
that may require changes to your code.
Changes in the Python API
-------------------------
* Before Python 3.5, a :class:`datetime.time` object was considered to be false
if it represented midnight in UTC. This behavior was considered obscure and
error-prone and has been removed in Python 3.5. See :issue:`13936` for full
......@@ -217,3 +228,8 @@ that may require changes to your code.
* :meth:`ssl.SSLSocket.send()` now raises either :exc:`ssl.SSLWantReadError`
or :exc:`ssl.SSLWantWriteError` on a non-blocking socket if the operation
would block. Previously, it would return 0. See :issue:`20951`.
Changes in the C API
--------------------
* The :c:type:`PyMemAllocator` structure has a new ``calloc`` field.
......@@ -95,6 +95,7 @@ PyObject_{New, NewVar, Del}.
the raw memory.
*/
PyAPI_FUNC(void *) PyObject_Malloc(size_t size);
PyAPI_FUNC(void *) PyObject_Calloc(size_t nelem, size_t elsize);
PyAPI_FUNC(void *) PyObject_Realloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyObject_Free(void *ptr);
......@@ -321,7 +322,8 @@ extern PyGC_Head *_PyGC_generation0;
(!PyTuple_CheckExact(obj) || _PyObject_GC_IS_TRACKED(obj)))
#endif /* Py_LIMITED_API */
PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t);
PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t size);
PyAPI_FUNC(PyObject *) _PyObject_GC_Calloc(size_t size);
PyAPI_FUNC(PyObject *) _PyObject_GC_New(PyTypeObject *);
PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t);
PyAPI_FUNC(void) PyObject_GC_Track(void *);
......
......@@ -13,6 +13,7 @@ extern "C" {
#ifndef Py_LIMITED_API
PyAPI_FUNC(void *) PyMem_RawMalloc(size_t size);
PyAPI_FUNC(void *) PyMem_RawCalloc(size_t nelem, size_t elsize);
PyAPI_FUNC(void *) PyMem_RawRealloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyMem_RawFree(void *ptr);
#endif
......@@ -57,6 +58,7 @@ PyAPI_FUNC(void) PyMem_RawFree(void *ptr);
*/
PyAPI_FUNC(void *) PyMem_Malloc(size_t size);
PyAPI_FUNC(void *) PyMem_Calloc(size_t nelem, size_t elsize);
PyAPI_FUNC(void *) PyMem_Realloc(void *ptr, size_t new_size);
PyAPI_FUNC(void) PyMem_Free(void *ptr);
......@@ -132,6 +134,9 @@ typedef struct {
/* allocate a memory block */
void* (*malloc) (void *ctx, size_t size);
/* allocate a memory block initialized by zeros */
void* (*calloc) (void *ctx, size_t nelem, size_t elsize);
/* allocate or resize a memory block */
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
......
......@@ -10,6 +10,12 @@ Release date: TBA
Core and Builtins
-----------------
- Issue #21233: Add new C functions: PyMem_RawCalloc(), PyMem_Calloc(),
PyObject_Calloc(), _PyObject_GC_Calloc(). bytes(int) and bytearray(int)
are now using ``calloc()`` instead of ``malloc()`` for large objects which
is faster and use less memory (until the bytearray buffer is filled with
data).
- Issue #21377: PyBytes_Concat() now tries to concatenate in-place when the
first argument has a reference count of 1. Patch by Nikolaus Rath.
......
......@@ -2710,6 +2710,20 @@ test_pymem_alloc0(PyObject *self)
{
void *ptr;
ptr = PyMem_RawMalloc(0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyMem_RawMalloc(0) returns NULL");
return NULL;
}
PyMem_RawFree(ptr);
ptr = PyMem_RawCalloc(0, 0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyMem_RawCalloc(0, 0) returns NULL");
return NULL;
}
PyMem_RawFree(ptr);
ptr = PyMem_Malloc(0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc(0) returns NULL");
......@@ -2717,6 +2731,13 @@ test_pymem_alloc0(PyObject *self)
}
PyMem_Free(ptr);
ptr = PyMem_Calloc(0, 0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyMem_Calloc(0, 0) returns NULL");
return NULL;
}
PyMem_Free(ptr);
ptr = PyObject_Malloc(0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyObject_Malloc(0) returns NULL");
......@@ -2724,6 +2745,13 @@ test_pymem_alloc0(PyObject *self)
}
PyObject_Free(ptr);
ptr = PyObject_Calloc(0, 0);
if (ptr == NULL) {
PyErr_SetString(PyExc_RuntimeError, "PyObject_Calloc(0, 0) returns NULL");
return NULL;
}
PyObject_Free(ptr);
Py_RETURN_NONE;
}
......@@ -2731,6 +2759,8 @@ typedef struct {
PyMemAllocator alloc;
size_t malloc_size;
size_t calloc_nelem;
size_t calloc_elsize;
void *realloc_ptr;
size_t realloc_new_size;
void *free_ptr;
......@@ -2743,6 +2773,14 @@ static void* hook_malloc (void* ctx, size_t size)
return hook->alloc.malloc(hook->alloc.ctx, size);
}
static void* hook_calloc (void* ctx, size_t nelem, size_t elsize)
{
alloc_hook_t *hook = (alloc_hook_t *)ctx;
hook->calloc_nelem = nelem;
hook->calloc_elsize = elsize;
return hook->alloc.calloc(hook->alloc.ctx, nelem, elsize);
}
static void* hook_realloc (void* ctx, void* ptr, size_t new_size)
{
alloc_hook_t *hook = (alloc_hook_t *)ctx;
......@@ -2765,16 +2803,14 @@ test_setallocators(PyMemAllocatorDomain domain)
const char *error_msg;
alloc_hook_t hook;
PyMemAllocator alloc;
size_t size, size2;
size_t size, size2, nelem, elsize;
void *ptr, *ptr2;
hook.malloc_size = 0;
hook.realloc_ptr = NULL;
hook.realloc_new_size = 0;
hook.free_ptr = NULL;
memset(&hook, 0, sizeof(hook));
alloc.ctx = &hook;
alloc.malloc = &hook_malloc;
alloc.calloc = &hook_calloc;
alloc.realloc = &hook_realloc;
alloc.free = &hook_free;
PyMem_GetAllocator(domain, &hook.alloc);
......@@ -2831,6 +2867,33 @@ test_setallocators(PyMemAllocatorDomain domain)
goto fail;
}
nelem = 2;
elsize = 5;
switch(domain)
{
case PYMEM_DOMAIN_RAW: ptr = PyMem_RawCalloc(nelem, elsize); break;
case PYMEM_DOMAIN_MEM: ptr = PyMem_Calloc(nelem, elsize); break;
case PYMEM_DOMAIN_OBJ: ptr = PyObject_Calloc(nelem, elsize); break;
default: ptr = NULL; break;
}
if (ptr == NULL) {
error_msg = "calloc failed";
goto fail;
}
if (hook.calloc_nelem != nelem || hook.calloc_elsize != elsize) {
error_msg = "calloc invalid nelem or elsize";
goto fail;
}
switch(domain)
{
case PYMEM_DOMAIN_RAW: PyMem_RawFree(ptr); break;
case PYMEM_DOMAIN_MEM: PyMem_Free(ptr); break;
case PYMEM_DOMAIN_OBJ: PyObject_Free(ptr); break;
}
Py_INCREF(Py_None);
res = Py_None;
goto finally;
......
......@@ -476,17 +476,22 @@ tracemalloc_remove_trace(void *ptr)
}
static void*
tracemalloc_malloc(void *ctx, size_t size)
tracemalloc_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr;
ptr = alloc->malloc(alloc->ctx, size);
assert(nelem <= PY_SIZE_MAX / elsize);
if (use_calloc)
ptr = alloc->calloc(alloc->ctx, nelem, elsize);
else
ptr = alloc->malloc(alloc->ctx, nelem * elsize);
if (ptr == NULL)
return NULL;
TABLES_LOCK();
if (tracemalloc_add_trace(ptr, size) < 0) {
if (tracemalloc_add_trace(ptr, nelem * elsize) < 0) {
/* Failed to allocate a trace for the new memory block */
TABLES_UNLOCK();
alloc->free(alloc->ctx, ptr);
......@@ -560,13 +565,16 @@ tracemalloc_free(void *ctx, void *ptr)
}
static void*
tracemalloc_malloc_gil(void *ctx, size_t size)
tracemalloc_alloc_gil(int use_calloc, void *ctx, size_t nelem, size_t elsize)
{
void *ptr;
if (get_reentrant()) {
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
return alloc->malloc(alloc->ctx, size);
if (use_calloc)
return alloc->calloc(alloc->ctx, nelem, elsize);
else
return alloc->malloc(alloc->ctx, nelem * elsize);
}
/* Ignore reentrant call. PyObjet_Malloc() calls PyMem_Malloc() for
......@@ -574,12 +582,24 @@ tracemalloc_malloc_gil(void *ctx, size_t size)
allocation twice. */
set_reentrant(1);
ptr = tracemalloc_malloc(ctx, size);
ptr = tracemalloc_alloc(use_calloc, ctx, nelem, elsize);
set_reentrant(0);
return ptr;
}
static void*
tracemalloc_malloc_gil(void *ctx, size_t size)
{
return tracemalloc_alloc_gil(0, ctx, 1, size);
}
static void*
tracemalloc_calloc_gil(void *ctx, size_t nelem, size_t elsize)
{
return tracemalloc_alloc_gil(1, ctx, nelem, elsize);
}
static void*
tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size)
{
......@@ -614,7 +634,7 @@ tracemalloc_realloc_gil(void *ctx, void *ptr, size_t new_size)
#ifdef TRACE_RAW_MALLOC
static void*
tracemalloc_raw_malloc(void *ctx, size_t size)
tracemalloc_raw_alloc(int use_calloc, void *ctx, size_t nelem, size_t elsize)
{
#ifdef WITH_THREAD
PyGILState_STATE gil_state;
......@@ -623,7 +643,10 @@ tracemalloc_raw_malloc(void *ctx, size_t size)
if (get_reentrant()) {
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
return alloc->malloc(alloc->ctx, size);
if (use_calloc)
return alloc->calloc(alloc->ctx, nelem, elsize);
else
return alloc->malloc(alloc->ctx, nelem * elsize);
}
/* Ignore reentrant call. PyGILState_Ensure() may call PyMem_RawMalloc()
......@@ -633,16 +656,28 @@ tracemalloc_raw_malloc(void *ctx, size_t size)
#ifdef WITH_THREAD
gil_state = PyGILState_Ensure();
ptr = tracemalloc_malloc(ctx, size);
ptr = tracemalloc_alloc(use_calloc, ctx, nelem, elsize);
PyGILState_Release(gil_state);
#else
ptr = tracemalloc_malloc(ctx, size);
ptr = tracemalloc_alloc(use_calloc, ctx, nelem, elsize);
#endif
set_reentrant(0);
return ptr;
}
static void*
tracemalloc_raw_malloc(void *ctx, size_t size)
{
return tracemalloc_raw_alloc(0, ctx, 1, size);
}
static void*
tracemalloc_raw_calloc(void *ctx, size_t nelem, size_t elsize)
{
return tracemalloc_raw_alloc(1, ctx, nelem, elsize);
}
static void*
tracemalloc_raw_realloc(void *ctx, void *ptr, size_t new_size)
{
......@@ -856,6 +891,7 @@ tracemalloc_start(int max_nframe)
#ifdef TRACE_RAW_MALLOC
alloc.malloc = tracemalloc_raw_malloc;
alloc.calloc = tracemalloc_raw_calloc;
alloc.realloc = tracemalloc_raw_realloc;
alloc.free = tracemalloc_free;
......@@ -865,6 +901,7 @@ tracemalloc_start(int max_nframe)
#endif
alloc.malloc = tracemalloc_malloc_gil;
alloc.calloc = tracemalloc_calloc_gil;
alloc.realloc = tracemalloc_realloc_gil;
alloc.free = tracemalloc_free;
......
......@@ -1703,15 +1703,19 @@ PyObject_GC_UnTrack(void *op)
_PyObject_GC_UNTRACK(op);
}
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
static PyObject *
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
size_t size;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
g = (PyGC_Head *)PyObject_MALLOC(
sizeof(PyGC_Head) + basicsize);
size = sizeof(PyGC_Head) + basicsize;
if (use_calloc)
g = (PyGC_Head *)PyObject_Calloc(1, size);
else
g = (PyGC_Head *)PyObject_Malloc(size);
if (g == NULL)
return PyErr_NoMemory();
g->gc.gc_refs = 0;
......@@ -1730,6 +1734,18 @@ _PyObject_GC_Malloc(size_t basicsize)
return op;
}
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
return _PyObject_GC_Alloc(0, basicsize);
}
PyObject *
_PyObject_GC_Calloc(size_t basicsize)
{
return _PyObject_GC_Alloc(1, basicsize);
}
PyObject *
_PyObject_GC_New(PyTypeObject *tp)
{
......
......@@ -813,9 +813,21 @@ bytearray_init(PyByteArrayObject *self, PyObject *args, PyObject *kwds)
}
else {
if (count > 0) {
if (PyByteArray_Resize((PyObject *)self, count))
void *sval;
Py_ssize_t alloc;
assert (Py_SIZE(self) == 0);
alloc = count + 1;
sval = PyObject_Calloc(1, alloc);
if (sval == NULL)
return -1;
memset(PyByteArray_AS_STRING(self), 0, count);
PyObject_Free(self->ob_bytes);
self->ob_bytes = self->ob_start = sval;
Py_SIZE(self) = count;
self->ob_alloc = alloc;
}
return 0;
}
......
......@@ -71,15 +71,11 @@ static PyBytesObject *nullstring;
PyBytes_FromStringAndSize()) or the length of the string in the `str'
parameter (for PyBytes_FromString()).
*/
PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
static PyObject *
_PyBytes_FromSize(Py_ssize_t size, int use_calloc)
{
PyBytesObject *op;
if (size < 0) {
PyErr_SetString(PyExc_SystemError,
"Negative size passed to PyBytes_FromStringAndSize");
return NULL;
}
assert(size >= 0);
if (size == 0 && (op = nullstring) != NULL) {
#ifdef COUNT_ALLOCS
null_strings++;
......@@ -87,15 +83,6 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
Py_INCREF(op);
return (PyObject *)op;
}
if (size == 1 && str != NULL &&
(op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
if (size > PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
PyErr_SetString(PyExc_OverflowError,
......@@ -104,19 +91,52 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
}
/* Inline PyObject_NewVar */
op = (PyBytesObject *)PyObject_MALLOC(PyBytesObject_SIZE + size);
if (use_calloc)
op = (PyBytesObject *)PyObject_Calloc(1, PyBytesObject_SIZE + size);
else
op = (PyBytesObject *)PyObject_Malloc(PyBytesObject_SIZE + size);
if (op == NULL)
return PyErr_NoMemory();
(void)PyObject_INIT_VAR(op, &PyBytes_Type, size);
op->ob_shash = -1;
if (str != NULL)
Py_MEMCPY(op->ob_sval, str, size);
op->ob_sval[size] = '\0';
/* share short strings */
if (!use_calloc)
op->ob_sval[size] = '\0';
/* empty byte string singleton */
if (size == 0) {
nullstring = op;
Py_INCREF(op);
} else if (size == 1 && str != NULL) {
}
return (PyObject *) op;
}
PyObject *
PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
{
PyBytesObject *op;
if (size < 0) {
PyErr_SetString(PyExc_SystemError,
"Negative size passed to PyBytes_FromStringAndSize");
return NULL;
}
if (size == 1 && str != NULL &&
(op = characters[*str & UCHAR_MAX]) != NULL)
{
#ifdef COUNT_ALLOCS
one_strings++;
#endif
Py_INCREF(op);
return (PyObject *)op;
}
op = (PyBytesObject *)_PyBytes_FromSize(size, 0);
if (op == NULL)
return NULL;
if (str == NULL)
return (PyObject *) op;
Py_MEMCPY(op->ob_sval, str, size);
/* share short strings */
if (size == 1) {
characters[*str & UCHAR_MAX] = op;
Py_INCREF(op);
}
......@@ -2482,7 +2502,7 @@ bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
"argument");
return NULL;
}
return PyBytes_FromString("");
return PyBytes_FromStringAndSize(NULL, 0);
}
if (PyUnicode_Check(x)) {
......@@ -2532,11 +2552,9 @@ bytes_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}
else {
new = PyBytes_FromStringAndSize(NULL, size);
new = _PyBytes_FromSize(size, 1);
if (new == NULL)
return NULL;
if (size > 0)
memset(((PyBytesObject*)new)->ob_sval, 0, size);
return new;
}
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment