It now has no effect if set to an empty string.
+.. envvar:: PYTHON_PYMALLOC_HUGEPAGES
+
+ If set to a non-zero integer, enable huge page support for
+ :ref:`pymalloc <pymalloc>` arenas. Set to ``0`` or unset to disable.
+ Python must be compiled with :option:`--with-pymalloc-hugepages` for this
+ variable to have any effect.
+
+ When enabled, arena allocation uses ``MAP_HUGETLB`` (Linux) or
+ ``MEM_LARGE_PAGES`` (Windows) with automatic fallback to regular pages if
+ huge pages are not available.
+
+ .. warning::
+
+ On Linux, if the huge-page pool is exhausted, page faults — including
+ copy-on-write faults triggered by :func:`os.fork` — deliver ``SIGBUS``
+ and kill the process. Only enable this in environments where the
+ huge-page pool is properly sized and fork-safety is not a concern.
+
+ .. versionadded:: next
+
+
.. envvar:: PYTHONLEGACYWINDOWSFSENCODING
If set to a non-empty string, the default :term:`filesystem encoding and
2 MiB and arena allocation uses ``MAP_HUGETLB`` (Linux) or
``MEM_LARGE_PAGES`` (Windows) with automatic fallback to regular pages.
+ Even when compiled with this option, huge pages are **not** used at runtime
+ unless the :envvar:`PYTHON_PYMALLOC_HUGEPAGES` environment variable is set
+ to ``1``. This opt-in is required because huge pages carry risks on Linux:
+ if the huge-page pool is exhausted, page faults (including copy-on-write
+ faults after :func:`os.fork`) deliver ``SIGBUS`` and kill the process.
+
The configure script checks that the platform supports ``MAP_HUGETLB``
and emits a warning if it is not available.
increases to 2 MiB and allocation uses ``MAP_HUGETLB`` (Linux) or
``MEM_LARGE_PAGES`` (Windows) with automatic fallback to regular pages.
On Windows, use ``build.bat --pymalloc-hugepages``.
+ At runtime, huge pages must be explicitly enabled by setting the
+ :envvar:`PYTHON_PYMALLOC_HUGEPAGES` environment variable to ``1``.
* Annotating anonymous mmap usage is now supported if Linux kernel supports
:manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
int dump_refs;
wchar_t *dump_refs_file;
int malloc_stats;
+ int pymalloc_hugepages;
wchar_t *filesystem_encoding;
wchar_t *filesystem_errors;
wchar_t *pycache_prefix;
debug_alloc_api_t obj;
} debug;
int is_debug_enabled;
+ int use_hugepages;
PyObjectArenaAllocator obj_arena;
};
("interactive", bool, None),
("isolated", bool, None),
("malloc_stats", bool, None),
+ ("pymalloc_hugepages", bool, None),
("module_search_paths", list[str], "path"),
("optimization_level", int, None),
("orig_argv", list[str], "orig_argv"),
'dump_refs': False,
'dump_refs_file': None,
'malloc_stats': False,
+ 'pymalloc_hugepages': False,
'filesystem_encoding': GET_DEFAULT_CONFIG,
'filesystem_errors': GET_DEFAULT_CONFIG,
'code_debug_ranges': False,
'show_ref_count': True,
'malloc_stats': True,
+ 'pymalloc_hugepages': True,
'stdio_encoding': 'iso8859-1',
'stdio_errors': 'replace',
'import_time': 1,
'code_debug_ranges': False,
'malloc_stats': True,
+ 'pymalloc_hugepages': True,
'inspect': True,
'optimization_level': 2,
'pythonpath_env': '/my/path',
'import_time': 1,
'code_debug_ranges': False,
'malloc_stats': True,
+ 'pymalloc_hugepages': True,
'inspect': True,
'optimization_level': 2,
'pythonpath_env': '/my/path',
#include <stdlib.h> // malloc()
#include <stdbool.h>
+#include <stdio.h> // fopen(), fgets(), sscanf()
#ifdef WITH_MIMALLOC
// Forward declarations of functions used in our mimalloc modifications
static void _PyMem_mi_page_clear_qsbr(mi_page_t *page);
# endif
#endif
+/* Return the system's default huge page size in bytes, or 0 if it
+ * cannot be determined. The result is cached after the first call.
+ *
+ * This is Linux-only (/proc/meminfo). On other systems that define
+ * MAP_HUGETLB the caller should skip huge pages gracefully. */
+#if defined(PYMALLOC_USE_HUGEPAGES) && defined(ARENAS_USE_MMAP) && defined(MAP_HUGETLB)
+static size_t
+_pymalloc_system_hugepage_size(void)
+{
+ static size_t hp_size = 0;
+ static int initialized = 0;
+
+ if (initialized) {
+ return hp_size;
+ }
+
+#ifdef __linux__
+ FILE *f = fopen("/proc/meminfo", "r");
+ if (f != NULL) {
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ unsigned long size_kb;
+ if (sscanf(line, "Hugepagesize: %lu kB", &size_kb) == 1) {
+ hp_size = (size_t)size_kb * 1024;
+ break;
+ }
+ }
+ fclose(f);
+ }
+#endif
+
+ initialized = 1;
+ return hp_size;
+}
+#endif
+
void *
_PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size)
{
#ifdef MS_WINDOWS
# ifdef PYMALLOC_USE_HUGEPAGES
- void *ptr = VirtualAlloc(NULL, size,
- MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES,
- PAGE_READWRITE);
- if (ptr != NULL)
- return ptr;
+ if (_PyRuntime.allocators.use_hugepages) {
+ SIZE_T lp_size = GetLargePageMinimum();
+ if (lp_size > 0 && size % lp_size == 0) {
+ void *ptr = VirtualAlloc(NULL, size,
+ MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES,
+ PAGE_READWRITE);
+ if (ptr != NULL)
+ return ptr;
+ }
+ }
/* Fall back to regular pages */
# endif
return VirtualAlloc(NULL, size,
void *ptr;
# ifdef PYMALLOC_USE_HUGEPAGES
# ifdef MAP_HUGETLB
- ptr = mmap(NULL, size, PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0);
- if (ptr != MAP_FAILED) {
- assert(ptr != NULL);
- (void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc:hugepage");
- return ptr;
+ if (_PyRuntime.allocators.use_hugepages) {
+ size_t hp_size = _pymalloc_system_hugepage_size();
+ /* Only use huge pages if the arena size is a multiple of the
+ * system's default huge page size. When the arena is smaller
+ * than the huge page, mmap still succeeds but silently
+ * allocates an entire huge page; the subsequent munmap with
+ * the smaller arena size then fails with EINVAL, leaking
+ * all of that memory. */
+ if (hp_size > 0 && size % hp_size == 0) {
+ ptr = mmap(NULL, size, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0);
+ if (ptr != MAP_FAILED) {
+ assert(ptr != NULL);
+ (void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc:hugepage");
+ return ptr;
+ }
+ }
}
/* Fall back to regular pages */
# endif
putenv("PYTHONMALLOCSTATS=0");
config.malloc_stats = 1;
+ config.pymalloc_hugepages = 1;
putenv("PYTHONPYCACHEPREFIX=env_pycache_prefix");
config_set_string(&config, &config.pycache_prefix, L"conf_pycache_prefix");
putenv("PYTHONPROFILEIMPORTTIME=1");
putenv("PYTHONNODEBUGRANGES=1");
putenv("PYTHONMALLOCSTATS=1");
+ putenv("PYTHON_PYMALLOC_HUGEPAGES=1");
putenv("PYTHONUTF8=1");
putenv("PYTHONVERBOSE=1");
putenv("PYTHONINSPECT=1");
SPEC(legacy_windows_stdio, BOOL, READ_ONLY, NO_SYS),
#endif
SPEC(malloc_stats, BOOL, READ_ONLY, NO_SYS),
+ SPEC(pymalloc_hugepages, BOOL, READ_ONLY, NO_SYS),
SPEC(orig_argv, WSTR_LIST, READ_ONLY, SYS_ATTR("orig_argv")),
SPEC(parse_argv, BOOL, READ_ONLY, NO_SYS),
SPEC(pathconfig_warnings, BOOL, READ_ONLY, NO_SYS),
assert(config->show_ref_count >= 0);
assert(config->dump_refs >= 0);
assert(config->malloc_stats >= 0);
+ assert(config->pymalloc_hugepages >= 0);
assert(config->site_import >= 0);
assert(config->bytes_warning >= 0);
assert(config->warn_default_encoding >= 0);
if (config_get_env(config, "PYTHONMALLOCSTATS")) {
config->malloc_stats = 1;
}
+ {
+ const char *env = _Py_GetEnv(use_env, "PYTHON_PYMALLOC_HUGEPAGES");
+ if (env) {
+ int value;
+ if (_Py_str_to_int(env, &value) < 0 || value < 0) {
+ /* PYTHON_PYMALLOC_HUGEPAGES=text or negative
+ behaves as PYTHON_PYMALLOC_HUGEPAGES=1 */
+ value = 1;
+ }
+ config->pymalloc_hugepages = (value > 0);
+ }
+ }
if (config->dump_refs_file == NULL) {
status = CONFIG_GET_ENV_DUP(config, &config->dump_refs_file,
return _PyStatus_NO_MEMORY();
}
+#ifdef PYMALLOC_USE_HUGEPAGES
+ runtime->allocators.use_hugepages = config->pymalloc_hugepages;
+#endif
+
return _PyStatus_OK();
}