Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

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 MemoryError zu 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 PyMemAllocator hinzufügen
    typedef 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 PyMemAllocatorDomain hinzufügen, um die Python-Allokatordomäne auszuwählen. Domänen
    • PYMEM_DOMAIN_RAW: PyMem_RawMalloc(), PyMem_RawRealloc() und PyMem_RawFree()
    • PYMEM_DOMAIN_MEM: PyMem_Malloc(), PyMem_Realloc() und PyMem_Free()
    • PYMEM_DOMAIN_OBJ: PyObject_Malloc(), PyObject_Realloc() und PyObject_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_RAW muss der Allokator threadsicher sein: Das GIL wird nicht gehalten, wenn der Allokator aufgerufen wird.
  • Eine neue Struktur PyObjectArenaAllocator hinzufügen
    typedef 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_MAX ist. 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() und free(); Aufruf von malloc(1) bei Anforderung von null Bytes
  • PYMEM_DOMAIN_OBJ: pymalloc-Allokator, der bei Allokationen größer als 512 Bytes auf PyMem_Malloc() zurückfällt.
  • pymalloc Arena-Allokator: VirtualAlloc() und VirtualFree() unter Windows, mmap() und munmap(), wenn verfügbar, oder malloc() und free()

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 0xCB gefüllt, freigegebener Speicher wird mit dem Byte 0xDB gefüllt.
  • Erkennen von API-Verstößen, z. B. PyObject_Free() aufgerufen für einen von PyMem_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

Andere Bibliotheken

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

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.).


Quelle: https://github.com/python/peps/blob/main/peps/pep-0445.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT