bpo-32030: Rework memory allocators by vstinner · Pull Request #4625 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions Doc/c-api/memory.rst
19 changes: 9 additions & 10 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,8 @@ conflict.

Set the family of memory allocators used by Python:

* ``default``: use the :ref:`default memory allocators
<default-memory-allocators>`.
* ``malloc``: use the :c:func:`malloc` function of the C library
for all domains (:c:data:`PYMEM_DOMAIN_RAW`, :c:data:`PYMEM_DOMAIN_MEM`,
:c:data:`PYMEM_DOMAIN_OBJ`).
Expand All @@ -696,20 +698,17 @@ conflict.

Install debug hooks:

* ``debug``: install debug hooks on top of the default memory allocator
* ``debug``: install debug hooks on top of the :ref:`default memory
allocators <default-memory-allocators>`.
* ``malloc_debug``: same as ``malloc`` but also install debug hooks
* ``pymalloc_debug``: same as ``pymalloc`` but also install debug hooks

When Python is compiled in release mode, the default is ``pymalloc``. When
compiled in debug mode, the default is ``pymalloc_debug`` and the debug hooks
are used automatically.
See the :ref:`default memory allocators <default-memory-allocators>` and the
:c:func:`PyMem_SetupDebugHooks` function (install debug hooks on Python
memory allocators).

If Python is configured without ``pymalloc`` support, ``pymalloc`` and
``pymalloc_debug`` are not available, the default is ``malloc`` in release
mode and ``malloc_debug`` in debug mode.

See the :c:func:`PyMem_SetupDebugHooks` function for debug hooks on Python
memory allocators.
.. versionchanged:: 3.7
Added the ``"default"`` allocator.

.. versionadded:: 3.6

Expand Down
10 changes: 9 additions & 1 deletion Include/pymem.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ PyAPI_FUNC(void) PyMem_RawFree(void *ptr);
allocators. */
PyAPI_FUNC(int) _PyMem_SetupAllocators(const char *opt);

/* Try to get the allocators name set by _PyMem_SetupAllocators(). */
PyAPI_FUNC(const char*) _PyMem_GetAllocatorsName(void);

#ifdef WITH_PYMALLOC
PyAPI_FUNC(int) _PyMem_PymallocEnabled(void);
#endif
Expand Down Expand Up @@ -230,7 +233,12 @@ PyAPI_FUNC(void) PyMem_SetupDebugHooks(void);
#endif

#ifdef Py_BUILD_CORE
PyAPI_FUNC(void) _PyMem_GetDefaultRawAllocator(PyMemAllocatorEx *alloc);
/* Set the memory allocator of the specified domain to the default.
Save the old allocator into *old_alloc if it's non-NULL.
Return on success, or return -1 if the domain is unknown. */
PyAPI_FUNC(int) _PyMem_SetDefaultAllocator(
PyMemAllocatorDomain domain,
PyMemAllocatorEx *old_alloc);
#endif

#ifdef __cplusplus
Expand Down
50 changes: 32 additions & 18 deletions Lib/test/pythoninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ def copy_attributes(info_add, obj, name_fmt, attributes, *, formatter=None):
info_add(name, value)


def copy_attr(info_add, name, mod, attr_name):
try:
value = getattr(mod, attr_name)
except AttributeError:
return
info_add(name, value)


def call_func(info_add, name, mod, func_name, *, formatter=None):
try:
func = getattr(mod, func_name)
Expand Down Expand Up @@ -168,11 +176,10 @@ def format_attr(attr, value):
call_func(info_add, 'os.gid', os, 'getgid')
call_func(info_add, 'os.uname', os, 'uname')

if hasattr(os, 'getgroups'):
groups = os.getgroups()
groups = map(str, groups)
groups = ', '.join(groups)
info_add("os.groups", groups)
def format_groups(groups):
return ', '.join(map(str, groups))

call_func(info_add, 'os.groups', os, 'getgroups', formatter=format_groups)

if hasattr(os, 'getlogin'):
try:
Expand All @@ -184,11 +191,7 @@ def format_attr(attr, value):
else:
info_add("os.login", login)

if hasattr(os, 'cpu_count'):
cpu_count = os.cpu_count()
if cpu_count:
info_add('os.cpu_count', cpu_count)

call_func(info_add, 'os.cpu_count', os, 'cpu_count')
call_func(info_add, 'os.loadavg', os, 'getloadavg')

# Get environment variables: filter to list
Expand Down Expand Up @@ -219,7 +222,9 @@ def format_attr(attr, value):
)
for name, value in os.environ.items():
uname = name.upper()
if (uname in ENV_VARS or uname.startswith(("PYTHON", "LC_"))
if (uname in ENV_VARS
# Copy PYTHON* and LC_* variables
or uname.startswith(("PYTHON", "LC_"))
# Visual Studio: VS140COMNTOOLS
or (uname.startswith("VS") and uname.endswith("COMNTOOLS"))):
info_add('os.environ[%s]' % name, value)
Expand Down Expand Up @@ -313,12 +318,10 @@ def collect_time(info_add):
)
copy_attributes(info_add, time, 'time.%s', attributes)

if not hasattr(time, 'get_clock_info'):
return

for clock in ('time', 'perf_counter'):
tinfo = time.get_clock_info(clock)
info_add('time.%s' % clock, tinfo)
if hasattr(time, 'get_clock_info'):
for clock in ('time', 'perf_counter'):
tinfo = time.get_clock_info(clock)
info_add('time.%s' % clock, tinfo)


def collect_sysconfig(info_add):
Expand All @@ -331,14 +334,14 @@ def collect_sysconfig(info_add):
'CCSHARED',
'CFLAGS',
'CFLAGSFORSHARED',
'PY_LDFLAGS',
'CONFIG_ARGS',
'HOST_GNU_TYPE',
'MACHDEP',
'MULTIARCH',
'OPT',
'PY_CFLAGS',
'PY_CFLAGS_NODIST',
'PY_LDFLAGS',
'Py_DEBUG',
'Py_ENABLE_SHARED',
'SHELL',
Expand Down Expand Up @@ -422,6 +425,16 @@ def collect_decimal(info_add):
copy_attributes(info_add, _decimal, '_decimal.%s', attributes)


def collect_testcapi(info_add):
try:
import _testcapi
except ImportError:
return

call_func(info_add, 'pymem.allocator', _testcapi, 'pymem_getallocatorsname')
copy_attr(info_add, 'pymem.with_pymalloc', _testcapi, 'WITH_PYMALLOC')


def collect_info(info):
error = False
info_add = info.add
Expand All @@ -444,6 +457,7 @@ def collect_info(info):
collect_zlib,
collect_expat,
collect_decimal,
collect_testcapi,
):
try:
collect_func(info_add)
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2861,3 +2861,8 @@ def save(self):
def restore(self):
for signum, handler in self.handlers.items():
self.signal.signal(signum, handler)


def with_pymalloc():
import _testcapi
return _testcapi.WITH_PYMALLOC
3 changes: 1 addition & 2 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,7 @@ class PyMemMallocDebugTests(PyMemDebugTests):
PYTHONMALLOC = 'malloc_debug'


@unittest.skipUnless(sysconfig.get_config_var('WITH_PYMALLOC') == 1,
'need pymalloc')
@unittest.skipUnless(support.with_pymalloc(), 'need pymalloc')
class PyMemPymallocDebugTests(PyMemDebugTests):
PYTHONMALLOC = 'pymalloc_debug'

Expand Down
52 changes: 50 additions & 2 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import os
import subprocess
import sys
import sysconfig
import tempfile
import unittest
from test import support
Expand Down Expand Up @@ -559,10 +560,14 @@ def test_xdev(self):
except ImportError:
pass
else:
code = "import _testcapi; _testcapi.pymem_api_misuse()"
code = "import _testcapi; print(_testcapi.pymem_getallocatorsname())"
with support.SuppressCrashReport():
out = self.run_xdev("-c", code, check_exitcode=False)
self.assertIn("Debug memory block at address p=", out)
if support.with_pymalloc():
alloc_name = "pymalloc_debug"
else:
alloc_name = "malloc_debug"
self.assertEqual(out, alloc_name)

try:
import faulthandler
Expand All @@ -573,6 +578,49 @@ def test_xdev(self):
out = self.run_xdev("-c", code)
self.assertEqual(out, "True")

def check_pythonmalloc(self, env_var, name):
code = 'import _testcapi; print(_testcapi.pymem_getallocatorsname())'
env = dict(os.environ)
if env_var is not None:
env['PYTHONMALLOC'] = env_var
else:
env.pop('PYTHONMALLOC', None)
args = (sys.executable, '-c', code)
proc = subprocess.run(args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
env=env)
self.assertEqual(proc.stdout.rstrip(), name)
self.assertEqual(proc.returncode, 0)

def test_pythonmalloc(self):
# Test the PYTHONMALLOC environment variable
pydebug = hasattr(sys, "gettotalrefcount")
pymalloc = support.with_pymalloc()
if pymalloc:
default_name = 'pymalloc_debug' if pydebug else 'pymalloc'
default_name_debug = 'pymalloc_debug'
else:
default_name = 'malloc_debug' if pydebug else 'malloc'
default_name_debug = 'malloc_debug'

tests = [
(None, default_name),
('debug', default_name_debug),
('malloc', 'malloc'),
('malloc_debug', 'malloc_debug'),
]
if pymalloc:
tests.extend((
('pymalloc', 'pymalloc'),
('pymalloc_debug', 'pymalloc_debug'),
))

for env_var, name in tests:
with self.subTest(env_var=env_var, name=name):
self.check_pythonmalloc(env_var, name)


class IgnoreEnvironmentTest(unittest.TestCase):

Expand Down
9 changes: 8 additions & 1 deletion Lib/test/test_sys.py
Loading