目次

1. Einführung

Zielgruppe

Dieser Artikel richtet sich hauptsächlich an Anfänger bis Fortgeschrittene, die Python im Alltag verwenden. Der Inhalt ist besonders nützlich für Personen, die die Speicherverwendung ihrer Programme überprüfen und optimieren möchten.

Zweck des Artikels

Der Zweck dieses Artikels ist wie folgt:

  1. Das Speichermanagement in Python verstehen.
  2. Konkrete Methoden zum Messen der Speicherverwendung lernen.
  3. Optimierungstechniken zur Reduzierung der Speicherverwendung erlernen.

Durch das Verständnis dieses Inhalts wird es Ihnen helfen, die Leistung Ihrer Python-Programme zu verbessern.

2. Grundlagen der Speicherverwaltung in Python

Mechanismus der Speicherverwaltung

In Python wird die Speicherverwaltung durch zwei Hauptmechanismen durchgeführt: „Referenzzählung“ und „Garbage Collection“.

Referenzzählung

Die Referenzzählung ist ein Mechanismus, der zählt, wie viele Referenzen auf jedes Objekt existieren.
Python wird beim Erstellen eines Objekts sein Referenzzähler auf 1 gesetzt. Jedes Mal, wenn eine weitere Variable auf dieses Objekt verweist, erhöht sich der Zähler, und wenn eine Referenz aufgehoben wird, verringert er sich. Wenn der Referenzzähler 0 erreicht, wird das Objekt automatisch aus dem Speicher freigegeben.

Code-Beispiel
import sys

 a = [1, 2, 3]  ## List-Objekt wird erstellt
print(sys.getrefcount(a))  ## Initialer Referenzzähler (normalerweise 2, inklusive interner Referenzen)

b = a  ## Eine weitere Variable referenziert dasselbe Objekt
print(sys.getrefcount(a))  ## Referenzzähler erhöht sich

del b  ## Referenz wird aufgehoben
print(sys.getrefcount(a))  ## Referenzzähler verringert sich

Garbage Collection

Die Garbage Collection (Garbage Collection, GC) ist ein Mechanismus zur Sammlung von Speicher, der durch Referenzzählung nicht freigegeben werden kann (insbesondere zirkuläre Referenzen). In Python arbeitet ein eingebauter Garbage Collector periodisch und löscht unnötige Objekte automatisch.

Der Garbage Collector ist speziell für die Erkennung und Freigabe zirkulärer Referenzen optimiert und ist in Situationen wie der folgenden hilfreich:

class Node:
    def __init__(self):
        self.next = None

## Beispiel für zirkuläre Referenz
a = Node()
b = Node()
a.next = b
b.next = a

## In diesem Zustand wird der Referenzzähler nicht null und der Speicher nicht freigegeben

Wenn Sie den Garbage Collector explizit steuern möchten, können Sie das Modul gc verwenden, um Kontrolle zu erlangen.

import gc

## Garbage Collector zwangsweise ausführen
gc.collect()

Risiken von Speicherlecks

Die Speicherverwaltung in Python ist sehr leistungsfähig, aber nicht perfekt. Insbesondere in den folgenden Situationen besteht das Risiko von Speicherlecks:

  1. Zirkuläre Referenzen vorhanden sind, aber der Garbage Collector deaktiviert ist.
  2. In Programmen, die langfristig laufen, unnötige Objekte im Speicher verbleiben.

Um diese Probleme zu vermeiden, ist es wichtig, Designs zu verwenden, die zirkuläre Referenzen vermeiden, und unnötige Objekte explizit zu löschen.

Zusammenfassung dieses Abschnitts

  • Die Speicherverwaltung in Python erfolgt durch die Mechanismen „Referenzzählung“ und „Garbage Collection“.
  • Die Garbage Collection ist besonders nützlich zur Lösung zirkulärer Referenzen, aber durch angemessenes Design kann unnötiger Speicherverbrauch verhindert werden.
  • Im nächsten Abschnitt wird erläutert, wie der Speicherverbrauch spezifisch gemessen werden kann.
侍エンジニア塾

3. Methode zur Überprüfung des Speicherverbrauchs

Grundlegende Methoden

sys.getsizeof() zur Überprüfung der Objektgröße

Mit der getsizeof()-Funktion, die im sys-Modul der Python-Standardbibliothek enthalten ist, können Sie die Speichergröße eines beliebigen Objekts in Bytes abrufen.

Code-Beispiel
import sys

## Überprüfung der Speichergröße jedes Objekts
x = 42
y = [1, 2, 3, 4, 5]
z = {"a": 1, "b": 2}

print(f"Größe von x: {sys.getsizeof(x)} Bytes")
print(f"Größe von y: {sys.getsizeof(y)} Bytes")
print(f"Größe von z: {sys.getsizeof(z)} Bytes")
Hinweise
  • Die mit sys.getsizeof() abrufbare Größe umfasst nur die Größe des Objekts selbst; die Größen anderer referenzierter Objekte (z. B. Elemente in einer Liste) sind nicht enthalten.
  • Um den genauen Speicherverbrauch großer Objekte zu messen, sind zusätzliche Tools erforderlich.

Verwendung von Profiling-Tools

Messung des Speichers pro Funktion mit memory_profiler

memory_profiler ist eine externe Bibliothek zur detaillierten Messung des Speicherverbrauchs von Python-Programmen pro Funktion. Sie können leicht feststellen, wie viel Speicher bestimmte Stellen im Code verbrauchen.

Einrichtung

Zuerst installieren Sie memory_profiler:

pip install memory-profiler
Verwendung

Mit dem @profile-Dekorator können Sie den Speicherverbrauch pro Funktion messen.

from memory_profiler import profile

@profile
def example_function():
    a = [i for i in range(10000)]
    b = {i: i**2 for i in range(1000)}
    return a, b

if __name__ == "__main__":
    example_function()

Verwenden Sie beim Ausführen den folgenden Befehl:

python -m memory_profiler your_script.py
Ausgabebeispiel
Line ##    Mem usage    Increment   Line Contents
------------------------------------------------
     3     13.1 MiB     13.1 MiB   @profile
     4     16.5 MiB      3.4 MiB   a = [i for i in range(10000)]
     5     17.2 MiB      0.7 MiB   b = {i: i**2 for i in range(1000)}

Überwachung des gesamten Prozess-Speicherverbrauchs mit psutil

psutil ist eine leistungsstarke Bibliothek zur Überwachung des Speicherverbrauchs des gesamten Prozesses. Sie ist nützlich, um den gesamten Speicherverbrauch eines bestimmten Skripts oder einer Anwendung zu erfassen.

Einrichtung

Installieren Sie es mit dem folgenden Befehl:

pip install psutil
Verwendung
import psutil

process = psutil.Process()
print(f"Gesamter Prozess-Speicherverbrauch: {process.memory_info().rss / 1024**2:.2f} MB")
Hauptmerkmale
  • Den Speicherverbrauch des aktuellen Prozesses in Bytes abrufbar.
  • Beim Monitoring der Programmleistung Hinweise zur Optimierung erhalten.

Detailliertes Speichertracking

Verfolgung von Speicherzuweisungen mit tracemalloc

Mit der tracemalloc aus der Python-Standardbibliothek können Sie die Quellen von Speicherzuweisungen verfolgen und analysieren, welche Teile den meisten Speicher verbrauchen.

Verwendung
import tracemalloc

## Speichertracking starten
tracemalloc.start()

## Verarbeitung, die Speicher verbraucht
a = [i for i in range(100000)]

## Speichernutzung anzeigen
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")

print("[Speichernutzung]")
for stat in top_stats[:5]:
    print(stat)
Hauptverwendungen
  • Problembereiche bei Speicherzuweisungen identifizieren.
  • Mehrere Prozesse vergleichen, um Optimierungspotenziale zu finden.

Zusammenfassung dieses Abschnitts

  • Um den Speicherverbrauch in Python zu erfassen, gibt es viele Mittel, von grundlegenden Tools wie sys.getsizeof() bis hin zu Profiling-Tools wie memory_profiler oder psutil.
  • Wenn der Speicherverbrauch des Programms wichtig ist, wählen Sie das passende Tool aus und verwalten Sie es effizient.
  • Im nächsten Abschnitt werden konkrete Methoden zur tatsächlichen Optimierung des Speicherverbrauchs erläutert.

4. Methoden zur Optimierung des Speicherverbrauchs

Auswahl effizienter Datenstrukturen

Ersetzung von Listen durch Generatoren

Die Listenaufnahmeschreibung ist bequem, verbraucht aber bei der Handhabung großer Datenmengen viel Speicher. Durch die Verwendung von Generatoren stattdessen kann der benötigte Speicher schrittweise generiert werden, wodurch der Speicherverbrauch erheblich reduziert werden kann.

Code-Beispiel
## Liste verwenden
list_data = [i**2 for i in range(1000000)]
print(f"Speichergröße der Liste: {sys.getsizeof(list_data) / 1024**2:.2f} MB")

## Generator verwenden
gen_data = (i**2 for i in range(1000000))
print(f"Speichergröße des Generators: {sys.getsizeof(gen_data) / 1024**2:.2f} MB")

Durch die Verwendung von Generatoren kann der Speicherverbrauch erheblich reduziert werden.

collections.defaultdict als Alternative zu Wörterbüchern

Python-Wörterbücher sind bequem, verbrauchen aber bei großen Datenmengen viel Speicher.collections.defaultdict ermöglicht eine effiziente Standardwertsetzung und vereinfacht die Verarbeitung.

Code-Beispiel
from collections import defaultdict

## Normales Wörterbuch
data = {}
data["key"] = data.get("key", 0) + 1

## defaultdict verwenden
default_data = defaultdict(int)
default_data["key"] += 1

Verwaltung unnötiger Objekte

Explizite Löschung mit der del-Anweisung

In Python können unnötige Objekte manuell gelöscht werden. Dadurch wird die Belastung der Garbage Collection reduziert.

Code-Beispiel
## Unnötige Variable löschen
a = [1, 2, 3]
del a

Nach der Löschung wird die Variable a aus dem Speicher freigegeben.

Nutzung des Garbage Collectors

Mit dem gc-Modul kann der Garbage Collector manuell ausgeführt werden. Dadurch können Speicherlecks durch zirkuläre Referenzen behoben werden.

Code-Beispiel
import gc

## Ausführung des Garbage Collectors
gc.collect()

Optimierung durch Nutzung externer Bibliotheken

Nutzung von NumPy und Pandas

NumPy und Pandas sind so konzipiert, dass sie Speicher effizient verwalten. Besonders bei der Handhabung großer Mengen numerischer Daten können diese Bibliotheken den Speicherverbrauch erheblich reduzieren.

Beispiel zur Verwendung von NumPy
import numpy as np

## Python-Liste
data_list = [i for i in range(1000000)]
print(f"Speichergröße der Liste: {sys.getsizeof(data_list) / 1024**2:.2f} MB")

## NumPy-Array
data_array = np.arange(1000000)
print(f"Speichergröße des NumPy-Arrays: {data_array.nbytes / 1024**2:.2f} MB")

NumPy-Arrays haben im Vergleich zu Listen eine höhere Speichereffizienz.

Verhinderung von Speicherlecks

Um Speicherlecks zu verhindern, ist es wichtig, auf die folgenden Punkte zu achten.

  1. Zirkuläre Referenzen vermeiden
    Die Objekte so designen, dass sie sich nicht gegenseitig referenzieren.
  2. Kontrolle der Scopes
    Die Scopes von Funktionen und Klassen beachten und unnötige Objekte nicht verbleiben lassen.

Zusammenfassung dieses Abschnitts

  • Um den Speicherverbrauch zu optimieren, ist es wichtig, effiziente Datenstrukturen auszuwählen und unnötige Objekte angemessen zu löschen.
  • Durch die Nutzung externer Bibliotheken wie NumPy und Pandas ist eine noch effizientere Speicherverwaltung möglich.
  • Im nächsten Abschnitt wird das Troubleshooting erläutert, das bei der Lösung tatsächlicher Probleme hilfreich ist.

5. Fehlerbehebung

Maßnahmen bei plötzlichem Anstieg der Speicherauslastung

Den Garbage Collector anpassen

Falls der Garbage Collector nicht richtig funktioniert, wird unnötiger Speicher nicht freigegeben, und die Auslastung kann stark ansteigen. Um dieses Problem zu lösen, passen Sie den Garbage Collector mit dem gc-Modul an.

Code-Beispiel
import gc

## Status des Garbage Collectors überprüfen
print(gc.get_threshold())

## Garbage Collector manuell ausführen
gc.collect()

## Einstellungen des Garbage Collectors ändern (Beispiel: Schwellenwerte anpassen)
gc.set_threshold(700, 10, 10)

Den Lebenszyklus der Objekte überprüfen

In manchen Fällen bleiben bestimmte Objekte auch nach Bedarf im Speicher. In diesem Fall sollten Sie den Lebenszyklus der Objekte überprüfen und deren Löschung zum richtigen Zeitpunkt in Betracht ziehen.

Speicherlecks durch zirkuläre Referenzen

Übersicht über das Problem

Zirkuläre Referenzen entstehen, wenn zwei oder mehr Objekte sich gegenseitig referenzieren. In diesem Fall wird der Referenzzähler nicht auf Null gesetzt und das Objekt wird möglicherweise nicht vom Garbage Collector freigegeben.

Lösungsansätze
  • Schwache Referenzen (weakref-Modul) verwenden, um zirkuläre Referenzen zu vermeiden.
  • Den Garbage Collector manuell ausführen, um zirkuläre Referenzen aufzulösen.
Code-Beispiel
import weakref

class Node:
    def __init__(self, name):
        self.name = name
        self.next = None

a = Node("A")
b = Node("B")

## Zirkuläre Referenzen mit schwachen Referenzen vermeiden
a.next = weakref.ref(b)
b.next = weakref.ref(a)

Falls Speicher-Profiling-Tools nicht funktionieren

Fehler bei memory_profiler

Beim Einsatz von memory_profiler funktioniert der @profile-Dekorator möglicherweise nicht. Dieses Problem entsteht, wenn das Skript nicht korrekt ausgeführt wird.

Lösungsansätze
  1. Führen Sie das Skript mit der Option -m memory_profiler aus:
   python -m memory_profiler your_script.py
  1. Stellen Sie sicher, dass die Funktion mit dem Dekorator korrekt angegeben ist.

Fehler bei psutil

Falls psutil keine Speicherinformationen abrufen kann, liegt möglicherweise ein Problem mit der Bibliotheksversion oder der Umgebung vor.

Lösungsansätze
  1. Überprüfen Sie die Version von psutil und installieren Sie die neueste Version:
   pip install --upgrade psutil
  1. Überprüfen Sie, ob Prozessinformationen korrekt abgerufen werden:
   import psutil
   process = psutil.Process()
   print(process.memory_info())

Maßnahmen bei Speichermangel-Fehlern

Übersicht über das Problem

Beim Umgang mit großen Datenmengen kann das Programm Speichermangel-Fehler (MemoryError) auslösen.

Lösungsansätze
  • Datensatzgröße reduzieren
    Entfernen Sie unnötige Daten und verwenden Sie effiziente Datenstrukturen.
   ## Generator verwenden
   large_data = (x for x in range(10**8))
  • Chunk-Verarbeitung durchführen
    Verarbeiten Sie die Daten in kleinen Chunks, um den Speicherverbrauch pro Durchlauf zu reduzieren.
   for chunk in range(0, len(data), chunk_size):
       process_data(data[chunk:chunk + chunk_size])
  • Externe Speicherung nutzen
    Speichern und verarbeiten Sie Daten auf der Festplatte statt im Speicher (z. B. SQLite, HDF5).

Zusammenfassung dieses Abschnitts

  • Nutzen Sie den Garbage Collector und das Lifecycle-Management, um die Speicherauslastung angemessen zu kontrollieren.
  • Bei zirkulären Referenzen oder Tool-Fehlern können schwache Referenzen oder korrekte Einstellungen helfen.
  • Speichermangel-Fehler können durch Überprüfung der Datenstrukturen, Chunk-Verarbeitung und Nutzung externer Speicher vermieden werden.

6. Praktisches Beispiel: Messung des Speicherverbrauchs in Python-Skripten

Hier zeigen wir konkrete Beispiele, wie man die bisher erläuterten Tools und Techniken nutzt, um den Speicherverbrauch innerhalb von Python-Skripten zu messen. Durch diese praktischen Beispiele lernen Sie, wie man den Speicherverbrauch analysiert und optimiert.

Beispielszenario: Vergleich des Speicherverbrauchs von Listen und Dictionaries

Code-Beispiel

Der folgende Skript misst den Speicherverbrauch von Listen und Dictionaries mit sys.getsizeof() und memory_profiler.

import sys

from memory_profiler import profile

@profile
def compare_memory_usage():
    ## Erstellung der Liste
    list_data = [i for i in range(100000)]
    print(f"Speicherverbrauch der Liste: {sys.getsizeof(list_data) / 1024**2:.2f} MB")

    ## Erstellung des Dictionaries
    dict_data = {i: i for i in range(100000)}
    print(f"Speicherverbrauch des Dictionaries: {sys.getsizeof(dict_data) / 1024**2:.2f} MB")

    return list_data, dict_data

if __name__ == "__main__":
    compare_memory_usage()

Schritte zur Ausführung

  1. Falls memory_profiler nicht installiert ist, führen Sie Folgendes aus:
   pip install memory-profiler
  1. Führen Sie das Skript mit memory_profiler aus:
   python -m memory_profiler script_name.py

Beispiel für Ausgaberesultate

Line ##    Mem usage    Increment   Line Contents
------------------------------------------------
     5     13.2 MiB     13.2 MiB   @profile
     6     17.6 MiB      4.4 MiB   list_data = [i for i in range(100000)]
     9     22.2 MiB      4.6 MiB   dict_data = {i: i for i in range(100000)]

Speicherverbrauch der Liste: 0.76 MB
Speicherverbrauch des Dictionaries: 3.05 MB

Aus diesem Beispiel geht hervor, dass Dictionaries im Vergleich zu Listen mehr Speicher verbrauchen. Dadurch erhalten Sie Kriterien, um die geeignete Datenstruktur basierend auf den Anforderungen der Anwendung auszuwählen.

Beispielszenario: Überwachung des Speicherverbrauchs des gesamten Prozesses

Code-Beispiel

Das folgende Skript verwendet psutil, um den Speicherverbrauch des gesamten Prozesses in Echtzeit zu überwachen.

import psutil
import time

def monitor_memory_usage():
    process = psutil.Process()
    print(f"Anfänglicher Speicherverbrauch: {process.memory_info().rss / 1024**2:.2f} MB")

    ## Simulation des Speicherverbrauchs
    data = [i for i in range(10000000)]
    print(f"Speicherverbrauch während der Verarbeitung: {process.memory_info().rss / 1024**2:.2f} MB")

    del data
    time.sleep(2)  ## Warten auf die Ausführung des Garbage Collectors
    print(f"Speicherverbrauch nach Datenlöschung: {process.memory_info().rss / 1024**2:.2f} MB")

if __name__ == "__main__":
    monitor_memory_usage()

Schritte zur Ausführung

  1. Falls psutil nicht installiert ist, führen Sie Folgendes aus:
   pip install psutil
  1. Führen Sie das Skript aus:
   python script_name.py

Beispiel für Ausgaberesultate

Anfänglicher Speicherverbrauch: 12.30 MB
Speicherverbrauch während der Verarbeitung: 382.75 MB
Speicherverbrauch nach Datenlöschung: 13.00 MB

Aus diesem Ergebnis können Sie das Verhalten beim Verbrauch von Speicher durch große Datenmengen und die Freigabe von Speicher durch das Löschen unnötiger Objekte beobachten.

Punkte dieses Abschnitts

  • Um den Speicherverbrauch zu messen, ist es wichtig, Tools wie sys.getsizeof(), memory_profiler, psutil usw. angemessen zu kombinieren.
  • Durch die Visualisierung des Speicherverbrauchs von Datenstrukturen oder des gesamten Prozesses können Engpässe identifiziert und effizientes Programmierdesign ermöglicht werden.

7. Zusammenfassung und nächste Schritte

Schlüsselpunkte des Artikels

  1. Grundlagen der Speicherverwaltung in Python
  • Python verwendet „Referenzzählung“ und „Garbage Collection“, um den Speicher automatisch zu verwalten.
  • Um Probleme durch zirkuläre Referenzen zu vermeiden, ist ein angemessenes Design erforderlich.
  1. Wie man den Speicherverbrauch überprüft
  • Mit sys.getsizeof() kann die Speichergröße auf Objektebene überprüft werden.
  • Tools wie memory_profiler oder psutil können verwendet werden, um den Speicherverbrauch von Funktionen oder ganzen Prozessen detailliert zu messen.
  1. Methoden zur Optimierung des Speicherverbrauchs
  • Durch die Verwendung von Generatoren oder effizienten Datenstrukturen (z. B. NumPy-Arrays) kann der Speicherverbrauch bei der Verarbeitung großer Datenmengen reduziert werden.
  • Das Entfernen unnötiger Objekte und die Nutzung des Garbage Collectors verhindern Speicherlecks.
  1. Anwendung in praktischen Beispielen
  • Durch tatsächlichen Code haben wir die Schritte zur Speichermessung und Optimierungsmethoden gelernt.
  • Wir haben die Unterschiede im Speicherverbrauch von Listen und Dictionaries sowie Beispiele für die Überwachung des gesamten Prozesses praktiziert.

Nächste Schritte

  1. In eigenen Projekten umsetzen
  • Nehmen Sie die in diesem Artikel vorgestellten Methoden und Tools in Ihre täglichen Python-Projekte auf.
  • Zum Beispiel testen Sie memory_profiler in einem Skript, das große Datenmengen handhabt, und identifizieren Sie Bereiche mit hohem Speicherverbrauch.
  1. Fortgeschrittene Speicherverwaltung lernen
  1. Nutzung externer Tools und Dienste
  • In großen Projekten ermöglichen die Profiling-Funktionen von py-spy oder PyCharm eine detailliertere Analyse.
  • Bei der Ausführung in Cloud-Umgebungen sollten auch die Monitoring-Tools von AWS oder Google Cloud genutzt werden.
  1. Fortlaufende Code-Reviews und Verbesserungen
  • Wenn Sie in einem Team entwickeln, führen Sie in Code-Reviews Diskussionen über den Speicherverbrauch durch, um Optimierungsmöglichkeiten zu erhöhen.
  • Das Erlernen von Coding-Gewohnheiten, die auf Speichereffizienz achten, bringt langfristige Vorteile.

Zum Abschluss

Die Fähigkeit, den Speicherverbrauch in Python angemessen zu managen, trägt nicht nur zur Effizienz der Programme bei, sondern auch zur Verbesserung der Fähigkeiten als Entwickler. Basierend auf den in diesem Artikel vorgestellten Inhalten setzen Sie diese in realen Projekten um und vertiefen Sie Ihr Verständnis weiter.