PEP 445 – Hinzufügen neuer APIs zur Anpassung von Python-Speicherallokatoren
- Autor:
- Victor Stinner <vstinner at python.org>
- BDFL-Delegate:
- Antoine Pitrou <solipsis at pitrou.net>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 15. Juni 2013
- Python-Version:
- 3.4
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Begründung
- Vorschlag
- Beispiele
- Leistung
- Abgelehnte Alternativen
- Spezifischere Funktionen zum Abrufen/Festlegen von Speicherallokatoren
- PyMem_Malloc() standardmäßig PyMem_RawMalloc() wiederverwenden lassen
- Neue Umgebungsvariable PYDEBUGMALLOC hinzufügen
- Makros verwenden, um anpassbare Allokatoren abzurufen
- C-Dateiname und Zeilennummer übergeben
- GIL-freier PyMem_Malloc()
- PyMem_RawMalloc() nicht hinzufügen
- Vorhandene Debug-Tools zur Analyse der Speichernutzung verwenden
- Eine msize()-Funktion hinzufügen
- Kein Kontextargument
- Externe Bibliotheken
- Speicherallokatoren
- Links
- Urheberrecht
Zusammenfassung
Dieser PEP schlägt neue Application Programming Interfaces (API) zur Anpassung von Python-Speicherallokatoren vor. Die einzige Implementierung, die zur Einhaltung dieses PEP erforderlich ist, ist CPython, aber andere Implementierungen können sich entscheiden, kompatibel zu sein oder ein ähnliches Schema wiederzuverwenden.
Begründung
Anwendungsfälle
- Anwendungen, die Python einbetten und Python-Speicher vom Speicher der Anwendung isolieren möchten oder einen anderen Speicherallokator verwenden möchten, der für die Python-Nutzung optimiert ist.
- Python, das auf eingebetteten Geräten mit wenig Speicher und langsamer CPU läuft. Ein benutzerdefinierter Speicherallokator kann für Effizienz und/oder für den Zugriff auf den gesamten Speicher des Geräts verwendet werden.
- Debug-Tools für Speicherallokatoren
- den Speicherverbrauch verfolgen (Speicherlecks finden)
- den Ort einer Speicherallokation ermitteln: Python-Dateiname und Zeilennummer sowie die Größe eines Speicherblocks
- Buffer-Underflow, Buffer-Overflow und Missbrauch von Python-Allokator-APIs erkennen (siehe Neugestaltung von Debug-Prüfungen für Speicherblock-Allokatoren als Hooks)
- Speicherallokationen zum Fehlschlagen bringen, um die Behandlung der Ausnahme
MemoryErrorzu testen.
Vorschlag
Neue Funktionen und Strukturen
- Einen neuen GIL-freien (kein Halten des GIL erforderlich) Speicherallokator hinzufügen
void* PyMem_RawMalloc(size_t size)void* PyMem_RawRealloc(void *ptr, size_t new_size)void PyMem_RawFree(void *ptr)- Der neu zugewiesene Speicher wurde in keiner Weise initialisiert.
- Die Anforderung von null Bytes gibt, wenn möglich, einen eindeutigen Nicht-NULL-Zeiger zurück, als wäre stattdessen
PyMem_Malloc(1)aufgerufen worden.
- Eine neue Struktur
PyMemAllocatorhinzufügentypedef struct { /* user context passed as the first argument to the 3 functions */ void *ctx; /* allocate a memory block */ void* (*malloc) (void *ctx, size_t size); /* allocate or resize a memory block */ void* (*realloc) (void *ctx, void *ptr, size_t new_size); /* release a memory block */ void (*free) (void *ctx, void *ptr); } PyMemAllocator;
- Eine neue Enumeration
PyMemAllocatorDomainhinzufügen, um die Python-Allokatordomäne auszuwählen. DomänenPYMEM_DOMAIN_RAW:PyMem_RawMalloc(),PyMem_RawRealloc()undPyMem_RawFree()PYMEM_DOMAIN_MEM:PyMem_Malloc(),PyMem_Realloc()undPyMem_Free()PYMEM_DOMAIN_OBJ:PyObject_Malloc(),PyObject_Realloc()undPyObject_Free()
- Neue Funktionen zum Abrufen und Festlegen von Speicherblock-Allokatoren hinzufügen
void PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)void PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocator *allocator)- Der neue Allokator muss bei der Anforderung von null Bytes einen eindeutigen Nicht-NULL-Zeiger zurückgeben.
- Für die Domäne
PYMEM_DOMAIN_RAWmuss der Allokator threadsicher sein: Das GIL wird nicht gehalten, wenn der Allokator aufgerufen wird.
- Eine neue Struktur
PyObjectArenaAllocatorhinzufügentypedef struct { /* user context passed as the first argument to the 2 functions */ void *ctx; /* allocate an arena */ void* (*alloc) (void *ctx, size_t size); /* release an arena */ void (*free) (void *ctx, void *ptr, size_t size); } PyObjectArenaAllocator;
- Neue Funktionen zum Abrufen und Festlegen des von pymalloc verwendeten Arena-Allokators hinzufügen
void PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)void PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)
- Eine neue Funktion zum Neuinstallieren der Debug-Prüfungen für Speicherallokatoren hinzufügen, wenn ein Speicherallokator mit
PyMem_SetAllocator()ersetzt wird.void PyMem_SetupDebugHooks(void)- Installiert die Debug-Hooks auf allen Speicherblock-Allokatoren. Die Funktion kann mehr als einmal aufgerufen werden, Hooks werden nur einmal installiert.
- Die Funktion tut nichts, wenn Python nicht im Debug-Modus kompiliert ist.
- Speicherblock-Allokatoren geben immer NULL zurück, wenn size größer als
PY_SSIZE_T_MAXist. Die Prüfung erfolgt vor dem Aufruf der inneren Funktion.
Hinweis
Der pymalloc-Allokator ist für Objekte kleiner als 512 Bytes mit kurzer Lebensdauer optimiert. Er verwendet Speicherabbildungen mit einer festen Größe von 256 KB, die als „Arenen“ bezeichnet werden.
So werden die Allokatoren standardmäßig eingerichtet
PYMEM_DOMAIN_RAW,PYMEM_DOMAIN_MEM:malloc(),realloc()undfree(); Aufruf vonmalloc(1)bei Anforderung von null BytesPYMEM_DOMAIN_OBJ: pymalloc-Allokator, der bei Allokationen größer als 512 Bytes aufPyMem_Malloc()zurückfällt.- pymalloc Arena-Allokator:
VirtualAlloc()undVirtualFree()unter Windows,mmap()undmunmap(), wenn verfügbar, odermalloc()undfree()
Neugestaltung von Debug-Prüfungen für Speicherblock-Allokatoren als Hooks
Seit Python 2.3 implementiert Python verschiedene Prüfungen für Speicherallokatoren im Debug-Modus.
- Neu zugewiesener Speicher wird mit dem Byte
0xCBgefüllt, freigegebener Speicher wird mit dem Byte0xDBgefüllt. - Erkennen von API-Verstößen, z. B.
PyObject_Free()aufgerufen für einen vonPyMem_Malloc()allokierten Speicherblock - Schreiben vor dem Anfang des Puffers (Buffer-Underflow) erkennen.
- Schreiben nach dem Ende des Puffers (Buffer-Overflow) erkennen.
In Python 3.3 werden die Prüfungen installiert, indem PyMem_Malloc(), PyMem_Realloc(), PyMem_Free(), PyObject_Malloc(), PyObject_Realloc() und PyObject_Free() mithilfe von Makros ersetzt werden. Der neue Allokator weist einen größeren Puffer zu und schreibt ein Muster, um Buffer-Underflow, Buffer-Overflow und Use-after-free zu erkennen (indem der Puffer mit dem Byte 0xDB gefüllt wird). Er verwendet die ursprüngliche PyObject_Malloc()-Funktion, um Speicher zuzuweisen. Daher rufen PyMem_Malloc() und PyMem_Realloc() indirekt PyObject_Malloc() und PyObject_Realloc() auf.
Dieser PEP gestaltet die Debug-Prüfungen als Hooks für die vorhandenen Allokatoren im Debug-Modus neu. Beispiele für Aufrufspuren ohne die Hooks
PyMem_RawMalloc()=>_PyMem_RawMalloc()=>malloc()PyMem_Realloc()=>_PyMem_RawRealloc()=>realloc()PyObject_Free()=>_PyObject_Free()
Aufrufspuren, wenn die Hooks installiert sind (Debug-Modus)
PyMem_RawMalloc()=>_PyMem_DebugMalloc()=>_PyMem_RawMalloc()=>malloc()PyMem_Realloc()=>_PyMem_DebugRealloc()=>_PyMem_RawRealloc()=>realloc()PyObject_Free()=>_PyMem_DebugFree()=>_PyObject_Free()
Als Ergebnis rufen PyMem_Malloc() und PyMem_Realloc() nun malloc() und realloc() sowohl im Release- als auch im Debug-Modus auf, anstatt PyObject_Malloc() und PyObject_Realloc() im Debug-Modus aufzurufen.
Wenn mindestens ein Speicherallokator mit PyMem_SetAllocator() ersetzt wird, muss die Funktion PyMem_SetupDebugHooks() aufgerufen werden, um die Debug-Hooks über dem neuen Allokator neu zu installieren.
malloc() nicht mehr direkt aufrufen
PyObject_Malloc() fällt bei Größen größer oder gleich 512 Bytes auf PyMem_Malloc() zurück, anstatt auf malloc(), und PyObject_Realloc() fällt auf PyMem_Realloc() zurück, anstatt auf realloc().
Direkte Aufrufe von malloc() werden durch PyMem_Malloc() oder PyMem_RawMalloc() ersetzt, wenn das GIL nicht gehalten wird.
Externe Bibliotheken wie zlib oder OpenSSL können so konfiguriert werden, dass sie Speicher mit PyMem_Malloc() oder PyMem_RawMalloc() allokieren. Wenn der Allokator einer Bibliothek nur global (statt objektweise) ersetzt werden kann, sollte er nicht ersetzt werden, wenn Python in eine Anwendung eingebettet ist.
Für den Anwendungsfall „Speicherverbrauch verfolgen“ ist es wichtig, den in externen Bibliotheken allokierten Speicher zu verfolgen, um genaue Berichte zu erhalten, da diese Allokationen groß sein können (z. B. können sie eine MemoryError-Ausnahme auslösen) und sonst in Berichten über die Speichernutzung fehlen würden.
Beispiele
Anwendungsfall 1: Speicherallokatoren ersetzen, pymalloc beibehalten
Dummy-Beispiel, das 2 Bytes pro Speicherblock und 10 Bytes pro pymalloc-Arena verschwendet.
#include <stdlib.h>
size_t alloc_padding = 2;
size_t arena_padding = 10;
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)
{
int padding = *(int *)ctx;
return malloc(size + padding);
}
void my_free_arena(void *ctx, void *ptr, size_t size)
{
free(ptr);
}
void setup_custom_allocator(void)
{
PyMemAllocator alloc;
PyObjectArenaAllocator arena;
alloc.ctx = &alloc_padding;
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
/* leave PYMEM_DOMAIN_OBJ unchanged, use pymalloc */
arena.ctx = &arena_padding;
arena.alloc = my_alloc_arena;
arena.free = my_free_arena;
PyObject_SetArenaAllocator(&arena);
PyMem_SetupDebugHooks();
}
Anwendungsfall 2: Speicherallokatoren ersetzen, pymalloc überschreiben
Wenn Sie einen dedizierten Allokator haben, der für Allokationen von Objekten kleiner als 512 Bytes mit kurzer Lebensdauer optimiert ist, kann pymalloc überschrieben werden (Ersetzen von PyObject_Malloc()).
Dummy-Beispiel, das 2 Bytes pro Speicherblock verschwendet.
#include <stdlib.h>
size_t padding = 2;
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 setup_custom_allocator(void)
{
PyMemAllocator alloc;
alloc.ctx = &padding;
alloc.malloc = my_malloc;
alloc.realloc = my_realloc;
alloc.free = my_free;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
PyMem_SetupDebugHooks();
}
Die pymalloc-Arena muss nicht ersetzt werden, da sie vom neuen Allokator nicht mehr verwendet wird.
Anwendungsfall 3: Hooks für Speicherblock-Allokatoren einrichten
Beispiel für die Einrichtung von Hooks für alle Speicherblock-Allokatoren.
struct {
PyMemAllocator raw;
PyMemAllocator mem;
PyMemAllocator obj;
/* ... */
} hook;
static void* hook_malloc(void *ctx, size_t size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr;
/* ... */
ptr = alloc->malloc(alloc->ctx, size);
/* ... */
return ptr;
}
static void* hook_realloc(void *ctx, void *ptr, size_t new_size)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
void *ptr2;
/* ... */
ptr2 = alloc->realloc(alloc->ctx, ptr, new_size);
/* ... */
return ptr2;
}
static void hook_free(void *ctx, void *ptr)
{
PyMemAllocator *alloc = (PyMemAllocator *)ctx;
/* ... */
alloc->free(alloc->ctx, ptr);
/* ... */
}
void setup_hooks(void)
{
PyMemAllocator alloc;
static int installed = 0;
if (installed)
return;
installed = 1;
alloc.malloc = hook_malloc;
alloc.realloc = hook_realloc;
alloc.free = hook_free;
PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &hook.raw);
PyMem_GetAllocator(PYMEM_DOMAIN_MEM, &hook.mem);
PyMem_GetAllocator(PYMEM_DOMAIN_OBJ, &hook.obj);
alloc.ctx = &hook.raw;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &alloc);
alloc.ctx = &hook.mem;
PyMem_SetAllocator(PYMEM_DOMAIN_MEM, &alloc);
alloc.ctx = &hook.obj;
PyMem_SetAllocator(PYMEM_DOMAIN_OBJ, &alloc);
}
Hinweis
PyMem_SetupDebugHooks() muss nicht aufgerufen werden, da Speicherallokatoren nicht ersetzt werden: Die Debug-Prüfungen für Speicherblock-Allokatoren werden automatisch beim Start installiert.
Leistung
Die Implementierung dieses PEP (Issue #3329) hat keinen sichtbaren Overhead bei der Python-Benchmark-Suite.
Ergebnisse der Python-Benchmark-Suite (-b 2n3): Einige Tests sind 1,04x schneller, einige Tests sind 1,04x langsamer. Ergebnisse des pybench-Mikrobenchmarks: global „+0,1 %“ langsamer (Unterschied zwischen -4,9 % und +5,6 %).
Die vollständige Ausgabe der Benchmarks ist an Issue #3329 angehängt.
Abgelehnte Alternativen
Spezifischere Funktionen zum Abrufen/Festlegen von Speicherallokatoren
Ursprünglich wurde ein größerer Satz von C-API-Funktionen vorgeschlagen, mit einem Funktionspaar für jede Allokatordomäne.
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)
Diese Alternative wurde abgelehnt, da es nicht möglich ist, generischen Code mit spezifischeren Funktionen zu schreiben: Code muss für jede Speicherallokatordomäne dupliziert werden.
PyMem_Malloc() standardmäßig PyMem_RawMalloc() wiederverwenden lassen
Wenn PyMem_Malloc() standardmäßig PyMem_RawMalloc() aufrufen würde, würde auch der Aufruf von PyMem_SetAllocator(PYMEM_DOMAIN_RAW, alloc) indirekt PyMem_Malloc() patchen.
Diese Alternative wurde abgelehnt, da PyMem_SetAllocator() ein anderes Verhalten je nach Domäne hätte. Immer das gleiche Verhalten zu haben, ist weniger fehleranfällig.
Neue Umgebungsvariable PYDEBUGMALLOC hinzufügen
Es wurde vorgeschlagen, eine neue Umgebungsvariable PYDEBUGMALLOC hinzuzufügen, um Debug-Prüfungen für Speicherblock-Allokatoren zu aktivieren. Dies hätte denselben Effekt wie der Aufruf von PyMem_SetupDebugHooks(), ohne dass C-Code geschrieben werden müsste. Ein weiterer Vorteil ist die Möglichkeit, Debug-Prüfungen auch im Release-Modus zu aktivieren: Debug-Prüfungen wären immer kompiliert, würden aber nur aktiviert, wenn die Umgebungsvariable vorhanden und nicht leer ist.
Diese Alternative wurde abgelehnt, da eine neue Umgebungsvariable die Python-Initialisierung noch komplexer machen würde. PEP 432 versucht, die Startsequenz von CPython zu vereinfachen.
Makros verwenden, um anpassbare Allokatoren abzurufen
Um im Standardkonfiguration keine Leistungseinbußen zu haben, wären anpassbare Allokatoren eine optionale Funktion, die durch eine Konfigurationsoption oder durch Makros aktiviert wird.
Diese Alternative wurde abgelehnt, da die Verwendung von Makros die Neukompilierung von Erweiterungsmodulen erfordert, um den neuen Allokator und die Allokator-Hooks zu verwenden. Wenn weder Python noch Erweiterungsmodule neu kompiliert werden müssen, sind Debug-Hooks einfacher zu verwenden.
C-Dateiname und Zeilennummer übergeben
Definieren Sie Allokatorfunktionen als Makros, die __FILE__ und __LINE__ verwenden, um den C-Dateinamen und die Zeilennummer einer Speicherallokation zu erhalten.
Beispiel für das PyMem_Malloc-Makro mit der modifizierten PyMemAllocator-Struktur.
typedef struct {
/* user context passed as the first argument
to the 3 functions */
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;
void* _PyMem_MallocTrace(const char *filename, int lineno,
size_t size);
/* the function is still needed for the Python stable ABI */
void* PyMem_Malloc(size_t size);
#define PyMem_Malloc(size) \
_PyMem_MallocTrace(__FILE__, __LINE__, size)
Die GC-Allokatorfunktionen müssten ebenfalls gepatcht werden. Zum Beispiel wird _PyObject_GC_Malloc() in vielen C-Funktionen verwendet, und somit hätten Objekte verschiedener Typen denselben Allokationsort.
Diese Alternative wurde abgelehnt, da die Übergabe eines Dateinamens und einer Zeilennummer an jeden Allokator die API komplexer macht: Übergabe von 3 neuen Argumenten (ctx, filename, lineno) an jede Allokatorfunktion anstelle von nur einem Kontextargument (ctx). Auch die GC-Allokatorfunktionen ändern zu müssen, fügt für einen geringen Gewinn zu viel Komplexität hinzu.
GIL-freier PyMem_Malloc()
In Python 3.3 ruft PyMem_Malloc(), wenn Python im Debug-Modus kompiliert ist, indirekt PyObject_Malloc() auf, was erfordert, dass das GIL gehalten wird (es ist nicht threadsicher). Deshalb muss PyMem_Malloc() mit gehaltenem GIL aufgerufen werden.
Dieser PEP ändert PyMem_Malloc(): Er ruft nun immer malloc() anstelle von PyObject_Malloc() auf. Die Einschränkung „GIL muss gehalten werden“ könnte daher von PyMem_Malloc() entfernt werden.
Diese Alternative wurde abgelehnt, da das Aufrufen von PyMem_Malloc() ohne Halten des GIL Anwendungen, die ihre eigenen Allokatoren oder Allokator-Hooks einrichten, unterbrechen kann. Das Halten des GIL ist praktisch für die Entwicklung eines benutzerdefinierten Allokators: Man muss sich nicht um andere Threads kümmern. Es ist auch praktisch für einen Debug-Allokator-Hook: Python-Objekte können sicher inspiziert und die C-API zur Berichterstattung verwendet werden.
Darüber hinaus hat der Aufruf von PyGILState_Ensure() in einem Speicherallokator unerwartete Auswirkungen, insbesondere beim Start von Python und bei der Erstellung eines neuen Python-Thread-Zustands. Es ist besser, benutzerdefinierte Allokatoren von der Verantwortung für die GIL-Erfassung zu befreien.
PyMem_RawMalloc() nicht hinzufügen
Ersetzen Sie malloc() durch PyMem_Malloc(), aber nur, wenn das GIL gehalten wird. Andernfalls belassen Sie malloc() unverändert.
PyMem_Malloc() wird in einigen Python-Funktionen ohne gehaltenes GIL verwendet. Zum Beispiel rufen die Funktionen main() und Py_Main() von Python PyMem_Malloc() auf, während das GIL noch nicht existiert. In diesem Fall würde PyMem_Malloc() durch malloc() (oder PyMem_RawMalloc()) ersetzt.
Diese Alternative wurde abgelehnt, da PyMem_RawMalloc() für genaue Berichte über die Speichernutzung erforderlich ist. Wenn ein Debug-Hook zur Verfolgung der Speichernutzung verwendet wird, kann der von direkten Aufrufen von malloc() allokierte Speicher nicht verfolgt werden. PyMem_RawMalloc() kann gehookt werden, und so kann der gesamte von Python allokierte Speicher verfolgt werden, einschließlich des Speichers, der ohne Halten des GIL allokiert wurde.
Vorhandene Debug-Tools zur Analyse der Speichernutzung verwenden
Es gibt viele bestehende Debug-Tools zur Analyse der Speichernutzung. Einige Beispiele: Valgrind, Purify, Clang AddressSanitizer, failmalloc usw.
Das Problem besteht darin, das Python-Objekt, das einem Speicherzeiger zugeordnet ist, abzurufen, um seinen Typ und/oder seinen Inhalt zu lesen. Ein weiteres Problem ist das Abrufen der Quelle der Speicherallokation: Der C-Backtrace ist normalerweise nutzlos (gleiche Begründung wie Makros, die __FILE__ und __LINE__ verwenden, siehe C-Dateiname und Zeilennummer übergeben), der Python-Dateiname und die Zeilennummer (oder sogar der Python-Traceback) sind nützlicher.
Diese Alternative wurde abgelehnt, da klassische Tools keine Einsicht in Python-Interna haben, um solche Informationen zu sammeln. Die Möglichkeit, einen Hook für Allokatoren einzurichten, die mit gehaltenem GIL aufgerufen werden, ermöglicht das Sammeln vieler nützlicher Daten aus Python-Interna.
Eine msize()-Funktion hinzufügen
Eine weitere Funktion zu den Strukturen PyMemAllocator und PyObjectArenaAllocator hinzufügen
size_t msize(void *ptr);
Diese Funktion gibt die Größe eines Speicherblocks oder einer Speicherabbildung zurück. Gibt (size_t)-1 zurück, wenn die Funktion nicht implementiert ist oder der Zeiger unbekannt ist (z. B. NULL-Zeiger).
Unter Windows kann diese Funktion mit _msize() und VirtualQuery() implementiert werden.
Die Funktion kann verwendet werden, um einen Hook zur Verfolgung der Speichernutzung zu implementieren. Die free()-Methode eines Allokators erhält nur die Adresse eines Speicherblocks, während die Größe des Speicherblocks erforderlich ist, um die Speichernutzung zu aktualisieren.
Die zusätzliche Funktion msize() wurde abgelehnt, da nur wenige Plattformen sie implementieren. Zum Beispiel stellt Linux mit der GNU libc keine Funktion zur Verfügung, um die Größe eines Speicherblocks zu ermitteln. msize() wird derzeit nicht im Python-Quellcode verwendet. Die Funktion würde nur zur Verfolgung der Speichernutzung verwendet werden und die API komplexer machen. Ein Debug-Hook kann die Funktion intern implementieren, es ist nicht notwendig, sie zu den Strukturen PyMemAllocator und PyObjectArenaAllocator hinzuzufügen.
Kein Kontextargument
Vereinfachen Sie die Signatur von Allokatorfunktionen, entfernen Sie das Kontextargument.
void* malloc(size_t size)void* realloc(void *ptr, size_t new_size)void free(void *ptr)
Es ist wahrscheinlich, dass ein Allokator-Hook für PyMem_SetAllocator() und PyObject_SetAllocator() oder sogar PyMem_SetRawAllocator() wiederverwendet wird, aber der Hook muss je nach Allokator eine andere Funktion aufrufen. Der Kontext ist eine praktische Möglichkeit, denselben benutzerdefinierten Allokator oder Hook für verschiedene Python-Allokatoren wiederzuverwenden.
In C++ kann der Kontext verwendet werden, um this zu übergeben.
Externe Bibliotheken
Beispiele für APIs, die zur Anpassung von Speicherallokatoren verwendet werden.
Von Python verwendete Bibliotheken
- OpenSSL: CRYPTO_set_mem_functions() zum globalen Festlegen von Speicherverwaltungsfunktionen.
- expat: parserCreate() hat einen speicherhandler pro Instanz.
- zlib: zlib 1.2.8 Handbuch, Übergabe eines undurchsichtigen Zeigers.
- bz2: bzip2 und libbzip2, Version 1.0.5, Übergabe eines undurchsichtigen Zeigers.
- lzma: LZMA SDK – How to Use, Übergabe eines undurchsichtigen Zeigers.
- lipmpdec: kein undurchsichtiger Zeiger (klassische malloc API).
Andere Bibliotheken
- glib: g_mem_set_vtable()
- libxml2: xmlGcMemSetup(), global.
- Oracle’s OCI: Oracle Call Interface Programmer’s Guide, Release 2 (9.2), Übergabe eines undurchsichtigen Zeigers.
Der neue ctx-Parameter dieses PEP wurde von der API der zlib- und Oracle-OCI-Bibliotheken inspiriert.
Siehe auch die GNU libc: Memory Allocation Hooks, die einen anderen Ansatz zur Hooking von Speicherallokatoren verwendet.
Speicherallokatoren
Die C-Standardbibliothek bietet die bekannte Funktion malloc(). Ihre Implementierung hängt von der Plattform und der C-Bibliothek ab. Die GNU C-Bibliothek verwendet eine modifizierte ptmalloc2, basierend auf „Doug Lea’s Malloc“ (dlmalloc). FreeBSD verwendet jemalloc. Google bietet tcmalloc, das Teil von gperftools ist.
malloc() verwendet zwei Arten von Speicher: Heap und Speicherabbildungen. Speicherabbildungen werden normalerweise für große Allokationen (z. B. größer als 256 KB) verwendet, während der Heap für kleine Allokationen verwendet wird.
Unter UNIX wird der Heap von den Systemaufrufen brk() und sbrk() verwaltet und ist zusammenhängend. Unter Windows wird der Heap von HeapAlloc() verwaltet und kann diskontinuierlich sein. Speicherabbildungen werden unter UNIX von mmap() und unter Windows von VirtualAlloc() verwaltet, sie können diskontinuierlich sein.
Die Freigabe einer Speicherabbildung gibt den Speicher sofort an das System zurück. Unter UNIX wird der Heap-Speicher erst dann an das System zurückgegeben, wenn der freigegebene Block am Ende des Heaps liegt. Andernfalls wird der Speicher erst dann an das System zurückgegeben, wenn der gesamte Speicher nach dem freigegebenen Speicher ebenfalls freigegeben ist.
Um Speicher auf dem Heap allokieren zu können, versucht ein Allokator, freien Speicherplatz wiederzuverwenden. Wenn kein zusammenhängender Platz groß genug ist, muss der Heap vergrößert werden, auch wenn mehr freier Platz als die erforderliche Größe vorhanden ist. Dieses Problem wird als „Speicherfragmentierung“ bezeichnet: Die vom System gesehene Speichernutzung ist höher als die tatsächliche Nutzung. Unter Windows erstellt HeapAlloc() eine neue Speicherabbildung mit VirtualAlloc(), wenn nicht genügend freier zusammenhängender Speicher vorhanden ist.
CPython verfügt über einen pymalloc-Allokator für Allokationen kleiner als 512 Bytes. Dieser Allokator ist für kleine Objekte mit kurzer Lebensdauer optimiert. Er verwendet Speicherabbildungen, die als „Arenen“ mit einer festen Größe von 256 KB bezeichnet werden.
Andere Allokatoren
- Windows bietet einen Low-fragmentation Heap.
- Der Linux-Kernel verwendet Slab-Allokation.
- Die glib-Bibliothek verfügt über eine Memory Slice API: eine effiziente Methode zur Allokation von Gruppen gleich großer Speicherblöcke.
Dieser PEP ermöglicht die genaue Auswahl des Speicherallokators, der für Ihre Anwendung verwendet wird, abhängig von ihrer Speichernutzung (Anzahl der Allokationen, Größe der Allokationen, Lebensdauer der Objekte usw.).
Links
CPython-Probleme im Zusammenhang mit Speicherallokationen
- Issue #3329: Neue APIs zum Anpassen von Speicherallokatoren hinzufügen.
- Issue #13483: VirtualAlloc zur Allokation von Speicherarenen verwenden.
- Issue #16742: PyOS_Readline gibt das GIL frei und ruft PyOS_StdioReadline auf, was nicht threadsicher ist.
- Issue #18203: Aufrufe von malloc() durch PyMem_Malloc() oder PyMem_RawMalloc() ersetzen.
- Issue #18227: Python-Speicherallokatoren in externen Bibliotheken wie zlib oder OpenSSL verwenden.
Projekte zur Analyse der Speichernutzung von Python-Anwendungen
Urheberrecht
Dieses Dokument wurde in den öffentlichen Bereich gestellt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0445.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT