python-peps/pep-0445.txt

774 lines
26 KiB
Plaintext
Raw Normal View History

PEP: 445
2013-06-18 16:33:41 -04:00
Title: Add new APIs to customize Python memory allocators
Version: $Revision$
Last-Modified: $Date$
Author: Victor Stinner <victor.stinner@gmail.com>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 15-june-2013
Python-Version: 3.4
Abstract
========
This PEP proposes new Application Programming Interfaces (API) to customize
Python memory allocators.
Rationale
=========
Use cases:
* Applications embedding Python which want to isolate Python memory from
the memory of the application, or want to use a different memory
2013-06-18 16:05:17 -04:00
allocator optimized for its Python usage
* Python running on embedded devices with low memory and slow CPU.
A custom memory allocator can be used for efficiency and/or to get
access all the memory of the device.
* Debug tools for memory allocators:
2013-06-17 20:46:10 -04:00
- track the memory usage (find memory leaks)
- get the location of a memory allocation: Python filename and line
number, and the size of a memory block
- detect buffer underflow, buffer overflow and misuse of Python
allocator APIs (see `Redesign Debug Checks on Memory Block
Allocators as Hooks`_)
- force memory allocations to fail to test handling of the
``MemoryError`` exception
Proposal
========
New Functions and Structures
----------------------------
2013-06-17 19:02:16 -04:00
* Add a new GIL-free (no need to hold the GIL) memory allocator:
2013-06-17 19:02:16 -04:00
- ``void* PyMem_RawMalloc(size_t size)``
- ``void* PyMem_RawRealloc(void *ptr, size_t new_size)``
- ``void PyMem_RawFree(void *ptr)``
- The newly allocated memory will not have been initialized in any
way.
- Requesting zero bytes returns a distinct non-*NULL* pointer if
possible, as if ``PyMem_Malloc(1)`` had been called instead.
* Add a new ``PyMemAllocator`` structure::
2013-06-18 08:14:17 -04:00
typedef struct {
/* user context passed as the first argument to the 3 functions */
2013-06-18 08:14:17 -04:00
void *ctx;
2013-06-18 15:04:34 -04:00
/* allocate a memory block */
2013-06-18 08:14:17 -04:00
void* (*malloc) (void *ctx, size_t size);
2013-06-18 15:04:34 -04:00
/* allocate or resize a memory block */
2013-06-18 08:14:17 -04:00
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
2013-06-18 15:04:34 -04:00
/* release a memory block */
2013-06-18 08:14:17 -04:00
void (*free) (void *ctx, void *ptr);
} PyMemAllocator;
2013-06-18 08:14:17 -04:00
* Add a new ``PyMemAllocatorDomain`` enum to choose the Python
allocator domain. Domains:
- ``PYMEM_DOMAIN_RAW``: ``PyMem_RawMalloc()``, ``PyMem_RawRealloc()``
and ``PyMem_RawFree()``
2013-06-18 08:14:17 -04:00
- ``PYMEM_DOMAIN_MEM``: ``PyMem_Malloc()``, ``PyMem_Realloc()`` and
``PyMem_Free()``
2013-06-18 08:14:17 -04:00
- ``PYMEM_DOMAIN_OBJ``: ``PyObject_Malloc()``, ``PyObject_Realloc()``
and ``PyObject_Free()``
2013-06-18 15:04:34 -04:00
* Add new functions to get and set memory block allocators:
2013-06-18 15:04:34 -04:00
- ``void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)``
- ``void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)``
- The new allocator must return a distinct non-*NULL* pointer when
requesting zero bytes
- For the ``PYMEM_DOMAIN_RAW`` domain, the allocator must be
thread-safe: the GIL is not held when the allocator is called.
2013-06-18 15:04:34 -04:00
* Add a new ``PyObjectArenaAllocator`` structure::
2013-06-18 15:04:34 -04:00
typedef struct {
/* user context passed as the first argument to the 2 functions */
2013-06-18 15:04:34 -04:00
void *ctx;
/* allocate an arena */
2013-06-18 15:59:48 -04:00
void* (*alloc) (void *ctx, size_t size);
2013-06-18 15:04:34 -04:00
/* release an arena */
2013-06-18 15:04:34 -04:00
void (*free) (void *ctx, void *ptr, size_t size);
} PyObjectArenaAllocator;
2013-06-18 15:04:34 -04:00
* Add new functions to get and set the arena allocator used by
*pymalloc*:
2013-06-18 15:04:34 -04:00
- ``void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)``
- ``void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)``
* Add a new function to reinstall the debug checks on memory allocators when
a memory allocator is replaced with ``PyMem_SetAllocator()``:
2013-06-17 19:02:16 -04:00
- ``void PyMem_SetupDebugHooks(void)``
- Install the debug hooks on all memory block allocators. The function can be
called more than once, hooks are only installed once.
- The function does nothing is Python is not compiled in debug mode.
* Memory block allocators always return *NULL* if *size* is greater than
``PY_SSIZE_T_MAX``. The check is done before calling the inner
function.
The *pymalloc* allocator is optimized for objects smaller than 512 bytes
with a short lifetime. It uses memory mappings with a fixed size of 256
KB called "arenas".
Default allocators:
2013-06-20 17:55:57 -04:00
* ``PYMEM_DOMAIN_RAW``, ``PYMEM_DOMAIN_MEM``: ``malloc()``,
``realloc()`` and ``free()``; call ``malloc(1)`` when requesting zero
bytes
* ``PYMEM_DOMAIN_OBJ``: *pymalloc* allocator which falls back on
``PyMem_Malloc()`` for allocations larger than 512 bytes
* *pymalloc* arena allocator: ``VirualAlloc()`` and ``VirtualFree()`` on
Windows, ``mmap()`` and ``munmap()`` when available, or ``malloc()``
and ``free()``
2013-06-18 15:59:48 -04:00
Redesign Debug Checks on Memory Block Allocators as Hooks
---------------------------------------------------------
Since Python 2.3, Python implements different checks on memory
allocators in debug mode:
2013-06-17 19:52:14 -04:00
2013-06-18 16:05:17 -04:00
* Newly allocated memory is filled with the byte ``0xCB``, freed memory
is filled with the byte ``0xDB``.
* Detect API violations, ex: ``PyObject_Free()`` called on a memory
block allocated by ``PyMem_Malloc()``
2013-06-18 15:04:34 -04:00
* Detect write before the start of the buffer (buffer underflow)
* Detect write after the end of the buffer (buffer overflow)
2013-06-18 08:14:17 -04:00
In Python 3.3, the checks are installed by replacing ``PyMem_Malloc()``,
``PyMem_Realloc()``, ``PyMem_Free()``, ``PyObject_Malloc()``,
``PyObject_Realloc()`` and ``PyObject_Free()`` using macros. The new
allocator allocates a larger buffer and write a pattern to detect buffer
underflow, buffer overflow and use after free (fill the buffer with the
pattern ``0xDB``). It uses the original ``PyObject_Malloc()``
function to allocate memory. So ``PyMem_Malloc()`` and
``PyMem_Realloc()`` call indirectly ``PyObject_Malloc()`` and
``PyObject_Realloc()``.
This PEP redesigns the debug checks as hooks on the existing allocators
in debug mode. Examples of call traces without the hooks:
* ``PyMem_RawMalloc()`` => ``_PyMem_RawMalloc()`` => ``malloc()``
* ``PyMem_Realloc()`` => ``_PyMem_RawRealloc()`` => ``realloc()``
* ``PyObject_Free()`` => ``_PyObject_Free()``
Call traces when the hooks are installed (debug mode):
* ``PyMem_RawMalloc()`` => ``_PyMem_DebugMalloc()``
=> ``_PyMem_RawMalloc()`` => ``malloc()``
* ``PyMem_Realloc()`` => ``_PyMem_DebugRealloc()``
=> ``_PyMem_RawRealloc()`` => ``realloc()``
* ``PyObject_Free()`` => ``_PyMem_DebugFree()``
=> ``_PyObject_Free()``
As a result, ``PyMem_Malloc()`` and ``PyMem_Realloc()`` now call
``malloc()`` and ``realloc()`` in release mode and in debug mode,
instead of calling ``PyObject_Malloc()`` and ``PyObject_Realloc()`` in
debug mode.
When at least one memory allocator is replaced with
``PyMem_SetAllocator()``, the ``PyMem_SetupDebugHooks()`` function must
be called to reinstall the debug hooks on top on the new allocator.
2013-06-18 08:14:17 -04:00
Don't call malloc() directly anymore
------------------------------------
``PyObject_Malloc()`` falls back on ``PyMem_Malloc()`` instead of
``malloc()`` if size is greater or equal than 512 bytes, and
``PyObject_Realloc()`` falls back on ``PyMem_Realloc()`` instead of
``realloc()``
2013-06-15 22:03:15 -04:00
Direct calls to ``malloc()`` are replaced with ``PyMem_Malloc()``, or
``PyMem_RawMalloc()`` if the GIL is not held.
Configure external libraries like zlib or OpenSSL to allocate memory
using ``PyMem_Malloc()`` or ``PyMem_RawMalloc()``. If the allocator of a
library can only be replaced globally, the allocator is not replaced if
Python is embedded in an application.
2013-06-17 19:52:14 -04:00
For the "track memory usage" use case, it is important to track memory
allocated in external libraries to have accurate reports, because these
allocations can be large (can raise a ``MemoryError`` exception).
If an hook is used to the track memory usage, the memory allocated by
direct calls to ``malloc()`` will not be tracked. Remaining ``malloc()``
in external libraries like OpenSSL or bz2 can allocate large memory
blocks and so would be missed in memory usage reports.
2013-06-17 19:52:14 -04:00
2013-06-17 19:02:16 -04:00
Examples
========
Use case 1: Replace Memory Allocators, keep pymalloc
2013-06-17 19:30:05 -04:00
----------------------------------------------------
2013-06-17 19:02:16 -04:00
2013-06-18 16:33:41 -04:00
Dummy example wasting 2 bytes per memory block,
and 10 bytes per *pymalloc* arena::
2013-06-17 19:02:16 -04:00
2013-06-17 19:30:05 -04:00
#include <stdlib.h>
2013-06-17 19:02:16 -04:00
size_t alloc_padding = 2;
size_t arena_padding = 10;
2013-06-17 19:02:16 -04:00
2013-06-17 19:30:05 -04:00
void* my_malloc(void *ctx, size_t size)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void* my_realloc(void *ctx, void *ptr, size_t new_size)
{
int padding = *(int *)ctx;
return realloc(ptr, new_size + padding);
}
void my_free(void *ctx, void *ptr)
{
free(ptr);
}
void* my_alloc_arena(void *ctx, size_t size)
2013-06-17 19:30:05 -04:00
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void my_free_arena(void *ctx, void *ptr, size_t size)
2013-06-17 19:30:05 -04:00
{
free(ptr);
}
2013-06-17 19:02:16 -04:00
2013-06-18 15:04:34 -04:00
void setup_custom_allocator(void)
2013-06-17 19:02:16 -04:00
{
PyMemAllocator alloc;
PyObjectArenaAllocator arena;
2013-06-17 19:30:05 -04:00
alloc.ctx = &alloc_padding;
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
2013-06-17 19:02:16 -04:00
2013-06-20 17:55:57 -04:00
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
/* leave PYMEM_DOMAIN_OBJ unchanged, use pymalloc */
2013-06-17 19:30:05 -04:00
arena.ctx = &arena_padding;
arena.alloc = my_alloc_arena;
arena.free = my_free_arena;
PyObject_SetArenaAllocator(&arena);
2013-06-17 19:02:16 -04:00
PyMem_SetupDebugHooks();
}
Use case 2: Replace Memory Allocators, override pymalloc
2013-06-18 08:14:17 -04:00
--------------------------------------------------------
2013-06-17 19:02:16 -04:00
If your allocator is optimized for allocations of objects smaller than
512 bytes with a short lifetime, pymalloc can be overriden (replace
``PyObject_Malloc()``).
2013-06-17 20:46:10 -04:00
2013-06-18 16:33:41 -04:00
Dummy example wasting 2 bytes per memory block::
2013-06-17 19:02:16 -04:00
2013-06-17 19:30:05 -04:00
#include <stdlib.h>
size_t padding = 2;
2013-06-17 19:30:05 -04:00
void* my_malloc(void *ctx, size_t size)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void* my_realloc(void *ctx, void *ptr, size_t new_size)
{
int padding = *(int *)ctx;
return realloc(ptr, new_size + padding);
}
2013-06-17 19:02:16 -04:00
2013-06-17 19:30:05 -04:00
void my_free(void *ctx, void *ptr)
{
free(ptr);
}
2013-06-17 19:02:16 -04:00
2013-06-18 15:04:34 -04:00
void setup_custom_allocator(void)
2013-06-17 19:02:16 -04:00
{
PyMemAllocator alloc;
2013-06-17 19:30:05 -04:00
alloc.ctx = &padding;
2013-06-17 19:02:16 -04:00
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
2013-06-20 17:55:57 -04:00
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
2013-06-17 19:30:05 -04:00
2013-06-17 19:02:16 -04:00
PyMem_SetupDebugHooks();
}
The *pymalloc* arena does not need to be replaced, because it is no more
used by the new allocator.
2013-06-17 19:02:16 -04:00
Use case 3: Setup Hooks On Memory Block Allocators
--------------------------------------------------
2013-06-17 19:02:16 -04:00
Example to setup hooks on all memory block allocators::
2013-06-17 19:02:16 -04:00
struct {
PyMemAllocator raw;
PyMemAllocator mem;
PyMemAllocator obj;
2013-06-17 19:30:05 -04:00
/* ... */
2013-06-17 19:02:16 -04:00
} hook;
2013-06-17 19:30:05 -04:00
static void* hook_malloc(void *ctx, size_t size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr;
2013-06-17 19:30:05 -04:00
/* ... */
ptr = alloc->malloc(alloc->ctx, size);
/* ... */
return ptr;
}
2013-06-17 19:02:16 -04:00
2013-06-17 19:30:05 -04:00
static void* hook_realloc(void *ctx, void *ptr, size_t new_size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
2013-06-17 19:30:05 -04:00
void *ptr2;
/* ... */
ptr2 = alloc->realloc(alloc->ctx, ptr, new_size);
/* ... */
return ptr2;
}
static void hook_free(void *ctx, void *ptr)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
2013-06-17 19:30:05 -04:00
/* ... */
alloc->free(alloc->ctx, ptr);
/* ... */
}
void setup_hooks(void)
2013-06-17 19:02:16 -04:00
{
PyMemAllocator alloc;
2013-06-18 08:14:17 -04:00
static int installed = 0;
2013-06-17 19:30:05 -04:00
2013-06-18 08:14:17 -04:00
if (installed)
2013-06-17 19:30:05 -04:00
return;
2013-06-18 08:14:17 -04:00
installed = 1;
2013-06-17 19:02:16 -04:00
alloc.malloc = hook_malloc;
alloc.realloc = hook_realloc;
alloc.free = hook_free;
2013-06-20 17:55:57 -04:00
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &hook.raw);
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &hook.mem);
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &hook.obj);
2013-06-17 19:02:16 -04:00
2013-06-18 15:59:48 -04:00
alloc.ctx = &hook.raw;
2013-06-20 17:55:57 -04:00
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
2013-06-17 19:02:16 -04:00
2013-06-18 15:59:48 -04:00
alloc.ctx = &hook.mem;
2013-06-20 17:55:57 -04:00
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
2013-06-17 19:02:16 -04:00
2013-06-18 15:59:48 -04:00
alloc.ctx = &hook.obj;
2013-06-20 17:55:57 -04:00
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
2013-06-17 19:02:16 -04:00
}
.. note::
``PyMem_SetupDebugHooks()`` does not need to be called because
memory allocator are not replaced: the debug checks on memory
block allocators are installed automatically at startup.
2013-06-17 19:02:16 -04:00
Performances
============
The implementation of this PEP (issue #3329) has no visible overhead on
the Python benchmark suite.
2013-06-18 16:05:17 -04:00
Results of the `Python benchmarks suite
<http://hg.python.org/benchmarks>`_ (-b 2n3): some tests are 1.04x
faster, some tests are 1.04 slower. Results of pybench microbenchmark:
"+0.1%" slower globally (diff between -4.9% and +5.6%).
The full output of benchmarks is attached to the issue #3329.
Rejected Alternatives
=====================
More specific functions to get/set memory allocators
----------------------------------------------------
2013-06-18 15:04:34 -04:00
Replace the 2 functions:
2013-06-15 22:01:00 -04:00
* ``void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)``
* ``void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)``
2013-06-15 22:01:00 -04:00
with:
* ``void PyMem_GetRawAllocator(PyMemAllocator *allocator)``
* ``void PyMem_GetAllocator(PyMemAllocator *allocator)``
* ``void PyObject_GetAllocator(PyMemAllocator *allocator)``
* ``void PyMem_SetRawAllocator(PyMemAllocator *allocator)``
* ``void PyMem_SetAllocator(PyMemAllocator *allocator)``
* ``void PyObject_SetAllocator(PyMemAllocator *allocator)``
2013-06-18 15:59:48 -04:00
This alternative was rejected because it is not possible to write
generic code with more specific functions: code must be duplicated for
each memory allocator domain.
2013-06-18 16:33:41 -04:00
Make PyMem_Malloc() reuse PyMem_RawMalloc() by default
------------------------------------------------------
2013-06-18 15:04:34 -04:00
If ``PyMem_Malloc()`` would call ``PyMem_RawMalloc()`` by default,
calling ``PyMem_SetAllocator(PYMEM_DOMAIN_RAW, alloc)`` would also also
patch ``PyMem_Malloc()`` indirectly.
This alternative was rejected because ``PyMem_SetAllocator()`` would
have a different behaviour depending on the domain. Always having the
same behaviour is less error-prone.
2013-06-18 15:04:34 -04:00
2013-06-18 08:14:17 -04:00
2013-06-17 20:46:10 -04:00
Add a new PYDEBUGMALLOC environment variable
--------------------------------------------
Add a new ``PYDEBUGMALLOC`` environment variable to enable debug checks
on memory block allocators. The environment variable replaces the new
function ``PyMem_SetupDebugHooks()`` which is not needed anymore.
Another advantage is to allow to enable debug checks even in release
mode: debug checks are always compiled, but only enabled when the
environment variable is present and non-empty.
2013-06-17 20:46:10 -04:00
This alternative was rejected because a new environment variable would
make the Python initialization even more complex. The `PEP 432
<http://www.python.org/dev/peps/pep-0432/>`_ tries to simply the CPython
startup sequence.
Use macros to get customizable allocators
-----------------------------------------
2013-06-18 16:05:17 -04:00
To have no overhead in the default configuration, customizable
allocators would be an optional feature enabled by a configuration
option or by macros.
This alternative was rejected because the usage of macros implies having
to recompile extensions modules to use the new allocator and allocator
hooks. Not having to recompile Python nor extension modules makes debug
hooks easier to use in practice.
2013-06-17 21:00:17 -04:00
Pass the C filename and line number
-----------------------------------
2013-06-18 16:33:41 -04:00
Define allocator functions as macros using ``__FILE__`` and ``__LINE__``
to get the C filename and line number of a memory allocation.
2013-06-18 15:59:48 -04:00
2013-06-18 16:33:41 -04:00
Example of ``PyMem_Malloc`` macro with the modified
``PyMemAllocator`` structure::
2013-06-18 15:59:48 -04:00
typedef struct {
2013-06-18 16:05:17 -04:00
/* user context passed as the first argument
to the 3 functions */
2013-06-18 15:59:48 -04:00
void *ctx;
/* allocate a memory block */
void* (*malloc) (void *ctx, const char *filename, int lineno,
size_t size);
/* allocate or resize a memory block */
void* (*realloc) (void *ctx, const char *filename, int lineno,
void *ptr, size_t new_size);
/* release a memory block */
void (*free) (void *ctx, const char *filename, int lineno,
void *ptr);
} PyMemAllocator;
2013-06-18 15:59:48 -04:00
2013-06-18 16:05:17 -04:00
void* _PyMem_MallocTrace(const char *filename, int lineno,
size_t size);
2013-06-18 15:59:48 -04:00
/* the function is still needed for the Python stable ABI */
2013-06-18 15:59:48 -04:00
void* PyMem_Malloc(size_t size);
2013-06-18 16:33:41 -04:00
#define PyMem_Malloc(size) \
_PyMem_MallocTrace(__FILE__, __LINE__, size)
The GC allocator functions would also have to be patched. For example,
``_PyObject_GC_Malloc()`` is used in many C functions and so objects of
differenet types would have the same allocation location.
This alternative was rejected because passing a filename and a line
number to each allocator makes the API more complex: pass 3 new
arguments (ctx, filename, lineno) to each allocator function, instead of
just a context argument (ctx). Having to modify also GC allocator
functions adds too much complexity for a little gain.
2013-06-17 20:46:10 -04:00
2013-06-18 15:59:48 -04:00
GIL-free PyMem_Malloc()
-----------------------
In Python 3.3, when Python is compiled in debug mode, ``PyMem_Malloc()``
calls indirectly ``PyObject_Malloc()`` which requires the GIL to be
held. That's why ``PyMem_Malloc()`` must be called with the GIL held.
2013-06-18 15:59:48 -04:00
This PEP changes ``PyMem_Malloc()``: it now always call ``malloc()``.
The "GIL must be held" restriction could be removed from
2013-06-18 15:59:48 -04:00
``PyMem_Malloc()``.
2013-06-17 19:52:14 -04:00
This alternative was rejected because allowing to call
``PyMem_Malloc()`` without holding the GIL can break applications
which setup their own allocators or allocator hooks. Holding the GIL is
convinient to develop a custom allocator: no need to care of other
threads. It is also convinient for a debug allocator hook: Python
internal objects can be safetly inspected.
2013-06-18 08:14:17 -04:00
Calling ``PyGILState_Ensure()`` in
a memory allocator has unexpected behaviour, especially at Python
startup and at creation of a new Python thread state.
2013-06-17 19:52:14 -04:00
Don't add PyMem_RawMalloc()
---------------------------
2013-06-18 16:05:17 -04:00
Replace ``malloc()`` with ``PyMem_Malloc()``, but only if the GIL is
held. Otherwise, keep ``malloc()`` unchanged.
2013-06-17 19:52:14 -04:00
2013-06-18 16:05:17 -04:00
The ``PyMem_Malloc()`` is used without the GIL held in some Python
functions. For example, the ``main()`` and ``Py_Main()`` functions of
Python call ``PyMem_Malloc()`` whereas the GIL do not exist yet. In this
case, ``PyMem_Malloc()`` would be replaced with ``malloc()`` (or
2013-06-18 15:59:48 -04:00
``PyMem_RawMalloc()``).
2013-06-17 19:52:14 -04:00
This alternative was rejected because ``PyMem_RawMalloc()`` is required
for accurate reports of the memory usage. When a debug hook is used to
track the memory usage, the memory allocated by direct calls to
``malloc()`` cannot be tracked. ``PyMem_RawMalloc()`` can be hooked and
so all the memory allocated by Python can be tracked.
2013-06-18 08:14:17 -04:00
Use existing debug tools to analyze the memory
----------------------------------------------
2013-06-18 16:05:17 -04:00
There are many existing debug tools to analyze the memory. Some
examples: `Valgrind <http://valgrind.org/>`_, `Purify
<http://ibm.com/software/awdtools/purify/>`_, `Clang AddressSanitizer
<http://code.google.com/p/address-sanitizer/>`_, `failmalloc
<http://www.nongnu.org/failmalloc/>`_, etc.
2013-06-18 08:14:17 -04:00
2013-06-18 16:05:17 -04:00
The problem is to retrieve the Python object related to a memory pointer
to read its type and/or its content. Another issue is to retrieve the
2013-06-18 16:05:17 -04:00
location of the memory allocation: the C backtrace is usually useless
(same reasoning than macros using ``__FILE__`` and ``__LINE__``, see
`Pass the C filename and line number`_), the Python filename and line
number (or even the Python traceback) is more useful.
2013-06-18 08:14:17 -04:00
This alternative was rejected because classic tools are unable to
introspect Python internals to collect such information. Being able to
setup a hook on allocators called with the GIL held allow to collect a
lot of useful data from Python internals.
Add a msize() function
----------------------
2013-06-18 15:04:34 -04:00
Add another function to ``PyMemAllocator`` and
``PyObjectArenaAllocator`` structures::
2013-06-18 15:04:34 -04:00
size_t msize(void *ptr);
2013-06-18 16:05:17 -04:00
This function returns the size of a memory block or a memory mapping.
Return (size_t)-1 if the function is not implemented or if the pointer
is unknown (ex: NULL pointer).
2013-06-18 15:04:34 -04:00
On Windows, this function can be implemented using ``_msize()`` and
``VirtualQuery()``.
The function can be used to implement an hook tracking the memory usage.
The ``free()`` method of an allocator only gets the address of a memory
block, whereas the size of the memory block is required to update the
memory usage.
The additional ``msize()`` function was rejected because only few
platforms implement it. For example, Linux with the GNU libc does not
provide a function to get the size of a memory block. ``msize()`` is not
currently used in the Python source code. The function is only used to
track the memory usage, and makes the API more complex. A debug hook can
implement the function internally, there is no need to add it to
``PyMemAllocator`` and ``PyObjectArenaAllocator`` structures.
2013-06-18 15:04:34 -04:00
No context argument
-------------------
2013-06-18 16:05:17 -04:00
Simplify the signature of allocator functions, remove the context
argument:
2013-06-18 15:04:34 -04:00
* ``void* malloc(size_t size)``
* ``void* realloc(void *ptr, size_t new_size)``
* ``void free(void *ptr)``
2013-06-18 16:05:17 -04:00
It is likely for an allocator hook to be reused for
``PyMem_SetAllocator()`` and ``PyObject_SetAllocator()``, or even
``PyMem_SetRawAllocator()``, but the hook must call a different function
depending on the allocator. The context is a convenient way to reuse the
same custom allocator or hook for different Python allocators.
2013-06-18 15:04:34 -04:00
In C++, the context can be used to pass *this*.
2013-06-18 15:04:34 -04:00
External Libraries
2013-06-15 21:49:29 -04:00
==================
Examples of API used to customize memory allocators.
2013-06-18 15:59:48 -04:00
Libraries used by Python:
2013-06-17 21:00:17 -04:00
* OpenSSL: `CRYPTO_set_mem_functions()
<http://git.openssl.org/gitweb/?p=openssl.git;a=blob;f=crypto/mem.c;h=f7984fa958eb1edd6c61f6667f3f2b29753be662;hb=HEAD#l124>`_
to set memory management functions globally
* expat: `parserCreate()
<http://hg.python.org/cpython/file/cc27d50bd91a/Modules/expat/xmlparse.c#l724>`_
has a per-instance memory handler
* zlib: `zlib 1.2.8 Manual <http://www.zlib.net/manual.html#Usage>`_,
pass an opaque pointer
* bz2: `bzip2 and libbzip2, version 1.0.5
<http://www.bzip.org/1.0.5/bzip2-manual-1.0.5.html>`_,
pass an opaque pointer
* lzma: `LZMA SDK - How to Use
<http://www.asawicki.info/news_1368_lzma_sdk_-_how_to_use.html>`_,
pass an opaque pointer
* lipmpdec: no opaque pointer (classic malloc API)
2013-06-18 15:59:48 -04:00
Other libraries:
* glib: `g_mem_set_vtable()
<http://developer.gnome.org/glib/unstable/glib-Memory-Allocation.html#g-mem-set-vtable>`_
* libxml2:
`xmlGcMemSetup() <http://xmlsoft.org/html/libxml-xmlmemory.html>`_,
2013-06-17 21:00:17 -04:00
global
* Oracle's OCI: `Oracle Call Interface Programmer's Guide,
Release 2 (9.2)
<http://docs.oracle.com/cd/B10501_01/appdev.920/a96584/oci15re4.htm>`_,
pass an opaque pointer
2013-06-15 21:49:29 -04:00
The new *ctx* parameter of this PEP was inspired by the API of zlib and
Oracle's OCI libraries.
2013-06-17 19:02:16 -04:00
See also the `GNU libc: Memory Allocation Hooks
<http://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html>`_
which uses a different approach to hook memory allocators.
2013-06-17 19:02:16 -04:00
Memory Allocators
2013-06-15 21:49:29 -04:00
=================
2013-06-18 16:05:17 -04:00
The C standard library provides the well known ``malloc()`` function.
Its implementation depends on the platform and of the C library. The GNU
C library uses a modified ptmalloc2, based on "Doug Lea's Malloc"
(dlmalloc). FreeBSD uses `jemalloc
<http://www.canonware.com/jemalloc/>`_. Google provides *tcmalloc* which
2013-06-18 16:05:17 -04:00
is part of `gperftools <http://code.google.com/p/gperftools/>`_.
2013-06-15 21:49:29 -04:00
``malloc()`` uses two kinds of memory: heap and memory mappings. Memory
2013-06-18 16:05:17 -04:00
mappings are usually used for large allocations (ex: larger than 256
KB), whereas the heap is used for small allocations.
On UNIX, the heap is handled by ``brk()`` and ``sbrk()`` system calls,
and it is contiguous. On Windows, the heap is handled by
``HeapAlloc()`` and can be discontiguous. Memory mappings are handled by
``mmap()`` on UNIX and ``VirtualAlloc()`` on Windows, they can be
2013-06-18 16:05:17 -04:00
discontiguous.
Releasing a memory mapping gives back immediatly the memory to the
system. On UNIX, the heap memory is only given back to the system if the
released block is located at the end of the heap. Otherwise, the memory
will only be given back to the system when all the memory located after
the released memory is also released.
To allocate memory on the heap, an allocator tries to reuse free space.
If there is no contiguous space big enough, the heap must be enlarged,
even if there is more free space than required size. This issue is
2013-06-18 16:05:17 -04:00
called the "memory fragmentation": the memory usage seen by the system
is higher than real usage. On Windows, ``HeapAlloc()`` creates
2013-06-18 16:05:17 -04:00
a new memory mapping with ``VirtualAlloc()`` if there is not enough free
contiguous memory.
CPython has a *pymalloc* allocator for allocations smaller than 512
bytes. This allocator is optimized for small objects with a short
lifetime. It uses memory mappings called "arenas" with a fixed size of
256 KB.
2013-06-18 15:59:48 -04:00
Other allocators:
2013-06-15 21:49:29 -04:00
2013-06-18 15:59:48 -04:00
* Windows provides a `Low-fragmentation Heap
<http://msdn.microsoft.com/en-us/library/windows/desktop/aa366750%28v=vs.85%29.aspx>`_.
2013-06-15 21:49:29 -04:00
2013-06-18 15:59:48 -04:00
* The Linux kernel uses `slab allocation
<http://en.wikipedia.org/wiki/Slab_allocation>`_.
2013-06-15 21:49:29 -04:00
2013-06-18 15:59:48 -04:00
* The glib library has a `Memory Slice API
<https://developer.gnome.org/glib/unstable/glib-Memory-Slices.html>`_:
efficient way to allocate groups of equal-sized chunks of memory
2013-06-15 21:49:29 -04:00
This PEP permits to choose exactly which memory allocator is used for your
application depending on its usage of the memory (number of allocation, size of
allocations, lifetime of objects, etc.).
2013-06-15 21:49:29 -04:00
Links
=====
2013-06-15 21:49:29 -04:00
CPython issues related to memory allocation:
* `Issue #3329: Add new APIs to customize memory allocators
<http://bugs.python.org/issue3329>`_
2013-06-15 21:49:29 -04:00
* `Issue #13483: Use VirtualAlloc to allocate memory arenas
<http://bugs.python.org/issue13483>`_
* `Issue #16742: PyOS_Readline drops GIL and calls PyOS_StdioReadline,
which isn't thread safe <http://bugs.python.org/issue16742>`_
2013-06-18 16:05:17 -04:00
* `Issue #18203: Replace calls to malloc() with PyMem_Malloc() or
PyMem_RawMalloc() <http://bugs.python.org/issue18203>`_
* `Issue #18227: Use Python memory allocators in external libraries like
zlib or OpenSSL <http://bugs.python.org/issue18227>`_
2013-06-15 21:49:29 -04:00
Projects analyzing the memory usage of Python applications:
* `pytracemalloc
<https://pypi.python.org/pypi/pytracemalloc>`_
* `Meliae: Python Memory Usage Analyzer
<https://pypi.python.org/pypi/meliae>`_
* `Guppy-PE: umbrella package combining Heapy and GSL
<http://guppy-pe.sourceforge.net/>`_
* `PySizer (developed for Python 2.4)
<http://pysizer.8325.org/>`_
Copyright
=========
This document has been placed into the public domain.