Hallo allerseits! So endete das lange Märzwochenende. Wir möchten die erste Veröffentlichung nach den Feiertagen der von vielen Kursen geliebten Person widmen -
"Python Developer" , die in weniger als zwei Wochen beginnt. Lass uns gehen.
Inhalt- Die Erinnerung ist ein leeres Buch.
- Speicherverwaltung: Von der Hardware zur Software
- Python-Basisimplementierung
- Global Interpreter Lock (GIL) -Konzept
- Müllsammler
- Speicherverwaltung in CPython:
- Fazit

Haben Sie sich jemals gefragt, wie Python Backstage Ihre Daten verarbeitet? Wie werden Ihre Variablen gespeichert? Wann werden sie entfernt?
In diesem Artikel werden wir uns eingehender mit der internen Struktur von Python befassen, um zu verstehen, wie die Speicherverwaltung funktioniert.
Nach dem Lesen dieses Artikels haben Sie:
- Erfahren Sie mehr über Operationen auf niedriger Ebene, insbesondere über Speicher.
- Verstehen Sie, wie Python Operationen auf niedriger Ebene abstrahiert.
- Erfahren Sie mehr über Speicherverwaltungsalgorithmen in Python.
Wenn Sie die interne Struktur von Python kennen, können Sie die Prinzipien seines Verhaltens besser verstehen. Ich hoffe, Sie können Python aus einer neuen Perspektive betrachten. Hinter den Kulissen gibt es so viele logische Operationen, damit Ihr Programm ordnungsgemäß funktioniert.
Die Erinnerung ist ein leeres BuchSie können sich den Speicher des Computers als leeres Buch vorstellen, das darauf wartet, dass es viele Kurzgeschichten schreibt. Es gibt noch nichts auf seinen Seiten, aber bald werden Autoren erscheinen, die ihre Geschichten darin schreiben wollen. Dazu brauchen sie einen Platz.
Da sie eine Geschichte nicht übereinander schreiben können, müssen sie sehr vorsichtig mit den Seiten sein, auf denen sie schreiben. Bevor Sie mit dem Schreiben beginnen, wenden Sie sich an den Buchmanager. Der Manager entscheidet, wo in dem Buch die Autoren ihre Geschichte aufschreiben können.
Da es das Buch seit vielen Jahren gibt, sind viele Geschichten darin veraltet. Wenn niemand die Geschichte liest oder anspricht, entfernt er sie, um neuen Geschichten Platz zu machen.
Der Computerspeicher ist im Kern wie ein leeres Buch. Kontinuierliche Speicherblöcke mit fester Länge werden normalerweise als Seiten bezeichnet, daher ist diese Analogie nützlich.
Die Autoren können verschiedene Anwendungen oder Prozesse sein, die Daten im Speicher speichern müssen. Ein Manager, der entscheidet, wo Autoren ihre Geschichten schreiben können, spielt die Rolle eines Speichermanagers - eines Sortierers. Und derjenige, der alte Geschichten löscht, ist ein Müllsammler.
Speicherverwaltung: Von der Hardware zur SoftwareSpeicherverwaltung ist der Prozess, bei dem Softwareanwendungen Daten lesen und schreiben. Der Speichermanager bestimmt, wo die Programmdaten abgelegt werden sollen. Da die Speichermenge natürlich der Anzahl der Seiten im Buch entspricht, muss der Manager freien Speicherplatz finden, um sie für die Anwendung bereitzustellen. Dieser Vorgang wird als "Speicherzuordnung" bezeichnet.
Wenn Daten jedoch nicht mehr benötigt werden, können sie gelöscht werden. In diesem Fall geht es darum, Speicherplatz freizugeben. Aber wovon wird es befreit und woher kommt es?
Irgendwo im Computer befindet sich ein physisches Gerät, das Daten speichert, wenn Sie Python-Programme ausführen. Python-Code durchläuft viele Abstraktionsebenen, bevor er auf dieses Gerät gelangt.
Eine der Hauptebenen über dem Gerät (RAM, Festplatte usw.) ist das Betriebssystem. Es verwaltet Lese- und Schreibanforderungen in den Speicher.
Über dem Betriebssystem befindet sich eine Anwendungsschicht, auf der sich eine der Python-Implementierungen befindet (mit Ihrem Betriebssystem verbunden oder von python.org heruntergeladen). Die Speicherverwaltung für Code in dieser Programmiersprache wird durch spezielle Python-Tools geregelt. Die Algorithmen und Strukturen, mit denen Python den Speicher verwaltet, sind das Hauptthema dieses Artikels.
Python-BasisimplementierungDie Basisimplementierung von Python oder „reinem Python“ ist CPython, geschrieben in C.
Ich war sehr überrascht, als ich zum ersten Mal davon hörte. Wie kann eine Sprache in einer anderen Sprache geschrieben werden ?! Natürlich nicht wörtlich, aber die Idee ist ungefähr so.
Die Python-Sprache wird in einem
speziellen Referenzhandbuch in Englisch beschrieben . Diese Anleitung allein ist jedoch nicht sehr nützlich. Sie benötigen noch ein Tool, um Code zu interpretieren, der nach den Regeln des Verzeichnisses geschrieben wurde.
Sie benötigen auch etwas, um den Code auf Ihrem Computer auszuführen. Die grundlegende Python-Implementierung bietet beide Bedingungen. Es konvertiert Python-Code in Anweisungen, die in einer virtuellen Maschine ausgeführt werden.
Hinweis: Virtuelle Maschinen ähneln physischen Computern, sind jedoch in die Software eingebettet. Sie verarbeiten grundlegende Anweisungen ähnlich dem Assembler-Code .
Python ist eine interpretierte Programmiersprache. Ihr Python-Code wird anhand von Anweisungen kompiliert, die vom Computer-
Bytecode besser verstanden werden. Diese Anweisungen werden von der virtuellen Maschine interpretiert, wenn Sie den Code ausführen.
Haben Sie jemals Dateien mit der Erweiterung
.pyc oder dem Ordner
__pycache__ gesehen ? Dies ist der gleiche Bytecode, der von der virtuellen Maschine interpretiert wird.
Es ist wichtig zu verstehen, dass es neben CPython noch andere Implementierungen gibt, z. B.
IronPython , das in der Microsoft Common Language Runtime (CLR) kompiliert und ausgeführt wird.
Jython kompiliert zu Java-Bytecode, um in einer virtuellen Java-Maschine ausgeführt zu werden. Und es gibt
PyPy, über das Sie einen separaten Artikel schreiben können, daher werde ich ihn nur beiläufig erwähnen.
In diesem Artikel konzentrieren wir uns auf die Speicherverwaltung mit CPython-Tools.
Hinweis: Python-Versionen werden aktualisiert und in Zukunft kann alles passieren. Zum Zeitpunkt des Schreibens war die neueste Version
Python 3.7 .
Ok, wir haben CPython in C geschrieben, das Python-Bytecode interpretiert. Wie hängt das mit der Speicherverwaltung zusammen? Zunächst sind im CPython-Code in C Algorithmen und Strukturen zum Verwalten des Speichers vorhanden. Um diese Prinzipien in Python zu verstehen, benötigen Sie ein grundlegendes Verständnis von CPython.
CPython ist in C geschrieben, was wiederum keine objektorientierte Programmierung unterstützt. Aus diesem Grund hat CPython-Code eine ziemlich interessante Struktur.
Sie müssen gehört haben, dass alles in Python ein Objekt ist, auch Typen wie int und str. Dies gilt auf der CPython-Implementierungsebene. Es gibt eine Struktur namens PyObject, die jedes Objekt in CPython verwendet.
Hinweis: Eine Struktur in C ist ein benutzerdefinierter Datentyp, der verschiedene Datentypen in sich gruppiert. Wir können eine Analogie mit objektorientierten Sprachen ziehen und sagen, dass eine Struktur eine Klasse mit Attributen, aber ohne Methoden ist.
PyObject ist der Vorläufer aller Objekte in Python und enthält nur zwei Dinge:
- ob_refcnt : Referenzzähler;
- ob_type : Zeiger auf einen anderen Typ.
Für die Speicherbereinigung ist ein Referenzzähler erforderlich. Wir haben auch einen Zeiger auf einen bestimmten Objekttyp. Ein Objekttyp ist nur eine andere Struktur, die Objekte in Python beschreibt (z. B. dict oder int).
Jedes Objekt verfügt über einen objektorientierten Speicherzuweiser, der weiß, wie Speicher zugewiesen und das Objekt gespeichert wird. Jedes Objekt verfügt außerdem über einen objektorientierten Ressourcen-Liberator, der den Speicher bereinigt, wenn sein Inhalt nicht mehr benötigt wird.
Es gibt einen wichtigen Faktor, wenn es um die Speicherzuweisung und deren Bereinigung geht. Speicher ist eine gemeinsam genutzte Ressource eines Computers, und etwas Unangenehmes kann passieren, wenn zwei Prozesse gleichzeitig versuchen, Daten an denselben Speicherort zu schreiben.
Globale Interpretationssperre (GIL)GIL ist eine Lösung für das allgemeine Problem der gemeinsamen Nutzung von Speicher zwischen gemeinsam genutzten Ressourcen wie Computerspeicher. Wenn zwei Threads versuchen, dieselbe Ressource gleichzeitig zu ändern, treten sie sich gegenseitig auf die Fersen. Infolgedessen bildet sich im Speicher ein vollständiges Durcheinander, und kein Prozess beendet seine Arbeit mit dem gewünschten Ergebnis.
Um auf die Analogie mit dem Buch zurückzukommen, nehmen wir an, dass jeder der beiden Autoren beschließt, seine Geschichte zu diesem bestimmten Zeitpunkt auf der aktuellen Seite zu schreiben. Jeder von ihnen ignoriert die Versuche des anderen, eine Geschichte zu schreiben, und beginnt hartnäckig, auf die Seite zu schreiben. Als Ergebnis haben wir zwei Geschichten übereinander und eine absolut unlesbare Seite.
Eine der Lösungen für dieses Problem ist genau GIL, das den Interpreter blockiert, während der Thread mit der zugewiesenen Ressource interagiert, sodass nur ein Thread in den zugewiesenen Speicherbereich schreiben kann. Wenn CPython Speicher zuweist, verwendet es die GIL, um sicherzustellen, dass es richtig funktioniert.
Dieser Ansatz hat sowohl viele Vor- als auch viele Nachteile, weshalb GIL in der Python-Community zu Konflikten führt. Um mehr über GIL zu erfahren, empfehle ich, den folgenden
Artikel zu lesen.
MüllsammlerKehren wir zu unserer Analogie mit dem Buch zurück und stellen wir uns vor, dass einige darin enthaltene Geschichten hoffnungslos veraltet sind. Niemand liest sie und spricht sie an. In diesem Fall wäre eine natürliche Lösung, sie als unnötig loszuwerden und dadurch Platz für neue Geschichten zu schaffen.
Solche alten nicht verwendeten Storys können mit Objekten in Python verglichen werden, deren Referenzanzahl auf 0 gesunken ist. Denken Sie daran, dass jedes Objekt in Python eine Referenzanzahl und einen Zeiger auf einen Typ hat.
Die Referenzanzahl kann sich aus mehreren Gründen erhöhen. Sie erhöht sich beispielsweise, wenn Sie eine Variable einer anderen Variablen zuweisen.

Sie erhöht sich auch, wenn Sie das Objekt als Argument übergeben.

Im letzten Beispiel erhöht sich die Referenzanzahl, wenn Sie das Objekt in die Liste aufnehmen.

Python informiert Sie über den aktuellen Wert des Referenzzählers mithilfe des sys-Moduls. Sie können
sys.getrefcount(numbers)
, aber denken Sie daran, dass der Aufruf von
getrefcount()
den Referenzzähler um einen anderen erhöht.
In jedem Fall, wenn das Objekt in Ihrem Code noch benötigt wird, ist sein Wert für seinen Referenzzähler größer als 0. Wenn es auf Null fällt, wird eine spezielle Funktion gestartet, um den Speicher zu löschen, wodurch es freigegeben und für andere Objekte verfügbar gemacht wird.
Aber was bedeutet es, „Speicher freizugeben“ und wie verwenden andere Objekte ihn? Lassen Sie uns direkt in die Speicherverwaltung in CPython eintauchen.
Speicherverwaltung in CPythonIn diesem Teil werden wir uns mit der CPython-Speicherarchitektur und den Algorithmen befassen, mit denen sie arbeitet.
Wie bereits erwähnt, gibt es Abstraktionsstufen zwischen physischer Ausrüstung und CPython. Das Betriebssystem abstrahiert den physischen Speicher und erstellt eine Ebene des virtuellen Speichers, auf die Anwendungen, einschließlich Python, zugreifen können.
Ein betriebssystemorientierter virtueller Speichermanager weist Python-Prozessen einen bestimmten Speicherbereich zu. Im Bild sind die dunkelgrauen Bereiche der Raum, den der Python-Prozess einnimmt.

Python verwendet einen Teil des Speichers für den internen Gebrauch und für Nicht-Objektspeicher. Der andere Teil gliedert sich in die Speicherung von Objekten (Ihr
Int, Diktat usw.). Jetzt spreche ich in einer sehr einfachen Sprache. Sie können jedoch direkt unter die Haube schauen,
dh in den
Quellcode von CPython und sehen, wie dies alles aus praktischer Sicht geschieht .
In CPython gibt es einen Objektzuweiser, der für die Zuweisung von Speicher innerhalb eines Objektspeicherbereichs verantwortlich ist. In diesem Vertreiber von Objekten wird alle Magie ausgeführt. Es wird jedes Mal aufgerufen, wenn jedes neue Objekt Speicher belegen oder freigeben muss.
Normalerweise werden beim Hinzufügen und Entfernen von Daten in Python, wie z. B. int oder list, nicht viele Daten gleichzeitig verwendet. Aus diesem Grund konzentriert sich die Architektur des Spenders auf die Arbeit mit kleinen Datenmengen pro Zeiteinheit. Außerdem weist er Speicher nicht im Voraus zu, dh bis zu diesem Moment, bis er absolut notwendig wird.
Die Kommentare im Quellcode definieren den Allokator als "einen speziellen Schnellspeicher-Allokator, der wie die universelle Malloc-Funktion funktioniert". Dementsprechend wird in C malloc verwendet, um Speicher zuzuweisen.
Schauen wir uns nun die Speicherzuweisungsstrategie von CPython an. Lassen Sie uns zunächst über die drei Hauptteile und ihre Beziehung zueinander sprechen.
Arenen sind die größten Speicherbereiche, die bis zum Rand der Seiten im Speicher Platz beanspruchen. Der Seitenrand (Seitenverteilung) ist der äußerste Punkt eines fortlaufenden Speicherblocks mit fester Länge, der vom Betriebssystem verwendet wird. Python setzt den Seitenrahmen des Systems auf 256 KB.

Innerhalb der Arenen befinden sich Pools (Pools), die als eine virtuelle Speicherseite (4 KB) betrachtet werden. Sie sehen aus wie Seiten in unserer Analogie. Pools sind in noch kleinere Speicherblöcke unterteilt.
Alle Blöcke im Pool befinden sich in einer „Größenklasse“. Die Größenklasse bestimmt die Größe des Blocks mit einer bestimmten Menge angeforderter Daten. Die Abstufung in der folgenden Tabelle stammt direkt aus den Kommentaren im Quellcode:

Wenn beispielsweise 42 Bytes benötigt werden, werden die Daten in einem 48-Byte-Block abgelegt.
PoolsPools bestehen aus Blöcken derselben Größenklasse. Jeder Pool arbeitet nach dem Prinzip einer doppelt verknüpften Liste mit anderen Pools derselben Größenklasse. Daher kann der Algorithmus selbst unter vielen Pools leicht den erforderlichen Platz für die erforderliche Blockgröße finden.
In der
usedpools list
der verwendeten Pools werden alle Pools
usedpools list
, in denen für Daten jeder Größenklasse freier Speicherplatz verfügbar ist. Wenn die erforderliche Blockgröße angefordert wird, überprüft der Algorithmus die Liste der verwendeten Pools, um einen geeigneten Pool dafür zu finden.
Pools befinden sich in drei Zuständen: verwendet, voll, leer. Der verwendete Pool enthält Blöcke, in die einige Informationen geschrieben werden können. Die Blöcke des vollständigen Pools sind alle verteilt und enthalten bereits Daten. Leere Pools enthalten keine Daten und können bei Bedarf in geeignete Größenklassen unterteilt werden.
Die Liste der leeren Pools (
freepools list
) enthält jeweils alle Pools in einem leeren Zustand. Aber ab wann werden sie eingesetzt?
Angenommen, Ihr Code benötigt einen Speicherbereich von 8 Byte. Wenn die Liste der verwendeten Pools mit einer Klassengröße von 8 Byte keine Pools enthält, wird ein neuer leerer Pool als Speicherblock mit 8 Byte initialisiert. Anschließend wird der leere Pool zur Liste der verwendeten Pools hinzugefügt und kann in den folgenden Abfragen verwendet werden.
Ein vollständiger Pool gibt einige Blöcke frei, wenn diese Informationen nicht mehr benötigt werden. Dieser Pool wird der Liste entsprechend seiner Größenklasse hinzugefügt. Sie können beobachten, wie die Pools ihre Zustände und sogar Größenklassen gemäß dem Algorithmus ändern.
Blöcke
Wie aus der Abbildung ersichtlich ist, enthalten die Pools Zeiger auf freie Speicherblöcke. Ihre Arbeit weist eine leichte Nuance auf. Laut den Kommentaren im Quellcode bemüht sich der Distributor, „niemals einen Speicherbereich auf einer der Ebenen (Arena, Pool, Block) zu berühren, bis er benötigt wird“.
Dies bedeutet, dass ein Block drei Zustände haben kann. Sie können wie folgt definiert werden:
- Unberührt : Speicherbereiche, die nicht zugewiesen wurden;
- Frei : Speicherbereiche, die zugewiesen, aber später von CPython freigegeben wurden, weil sie keine relevanten Informationen enthielten.
- Verteilt : Speicherbereiche, die derzeit aktuelle Informationen enthalten.
Der Freeblock-Zeiger ist eine einfach verknüpfte Liste von Free-Memory-Blöcken. Mit anderen Worten, dies ist eine Liste von freien Stellen, an denen Sie Informationen schreiben können. Wenn mehr Speicher benötigt wird als in freien Blöcken vorhanden ist, verwendet der Allokator die unberührten Blöcke im Pool.
Sobald der Speichermanager Blöcke freigibt, werden diese Blöcke am Anfang der Liste der freien Blöcke hinzugefügt. Die tatsächliche Liste enthält möglicherweise keine fortlaufende Folge von Speicherblöcken, wie in der ersten „erfolgreichen“ Abbildung.
ArenenArenen enthalten Pools. Arenen haben im Gegensatz zu Pools keine expliziten staatlichen Unterteilungen.
Sie selbst sind in einer doppelt verknüpften Liste organisiert, die als Liste der verwendbaren Arenen (usable_arenas) bezeichnet wird. Diese Liste ist nach der Anzahl der freien Pools sortiert. Je weniger freie Pools, desto näher steht die Arena ganz oben auf der Liste.

Dies bedeutet, dass die vollständigste Arena ausgewählt wird, um noch mehr Daten aufzuzeichnen. Aber warum genau? Warum nicht Daten dorthin schreiben, wo der größte freie Speicherplatz vorhanden ist?
Dies bringt uns zu der Idee, das Gedächtnis vollständig freizugeben. Tatsache ist, dass in einigen Fällen, wenn Speicher freigegeben wird, für das Betriebssystem immer noch kein Zugriff möglich ist. Der Python-Prozess hält es verteilt und verwendet es später für neue Daten. Die Freigabe des vollen Speichers gibt den Speicher an das Betriebssystem zurück.
Arenen sind nicht die einzigen Bereiche, die vollständig geräumt werden können. Wir verstehen daher, dass die Arenen, die auf der Liste „näher an der Leere“ stehen, freigegeben werden sollten. In diesem Fall kann der Speicherbereich wirklich vollständig freigegeben werden, und dementsprechend wird die Gesamtspeicherkapazität Ihres Python-Programms reduziert.
FazitDie Speicherverwaltung ist einer der wichtigsten Teile bei der Arbeit mit einem Computer. Auf die eine oder andere Weise führt Python praktisch alle seine Operationen im Stealth-Modus aus.
Aus diesem Artikel haben Sie gelernt:
- Was ist Speicherverwaltung und warum ist sie wichtig?
- Was ist CPython, eine grundlegende Python-Implementierung?
- Wie Datenstrukturen und Algorithmen in der Speicherverwaltung von CPython funktionieren und Ihre Daten speichern.
Python abstrahiert die vielen Nuancen der Arbeit mit einem Computer. Dies ermöglicht es, auf einer höheren Ebene zu arbeiten und die Kopfschmerzen beim Thema, wo und wie Bytes Ihres Programms gespeichert werden, loszuwerden.
Also haben wir in Python etwas über Speicherverwaltung gelernt. Traditionell warten wir auf Ihre Kommentare und laden Sie zu einem
Tag der
offenen Tür in den Python Developer-Kurs ein, der am 13. März stattfinden wird