Bereitschaft vs. Fertigstellung Asynchrone E / A-Speichernutzung?

12

Ich habe diesen Vortrag über die Implementierung von Async IO in Rust gesehen und Carl erwähnt zwei mögliche Modelle. Bereitschaft und Fertigstellung.

Bereitschaftsmodell:

  • Sie teilen dem Kernel mit, dass Sie aus einem Socket lesen möchten
  • mache für eine Weile andere Dinge…
  • Der Kernel teilt Ihnen mit, wann der Socket bereit ist
  • Sie lesen (füllen Sie einen Puffer)
  • Mach was du brauchst
  • Geben Sie den Puffer frei (geschieht automatisch mit Rust)

Abschlussmodell:

  • Sie weisen dem zu füllenden Kernel einen Puffer zu
  • mache für eine Weile andere Dinge…
  • Der Kernel teilt Ihnen mit, wann der Puffer gefüllt wurde
  • Machen Sie mit den Daten alles, was Sie brauchen
  • Geben Sie den Puffer frei

In Carls Beispiel für die Verwendung des Bereitschaftsmodells könnten Sie über fertige Sockets iterieren, die einen globalen Puffer füllen und freigeben, wodurch es so aussieht, als würde viel weniger Speicher benötigt.

Nun meine Annahmen:

Unter der Haube (im Kernelraum) sind die Daten bereits vorhanden, wenn ein Socket als "bereit" bezeichnet wird. Es ist über das Netzwerk (oder von wo auch immer) in den Socket gekommen und das Betriebssystem hält an den Daten fest.

Es ist nicht so, dass diese Speicherzuweisung im Bereitschaftsmodell auf magische Weise nicht vorkommt. Es ist nur so, dass das Betriebssystem es von Ihnen abstrahiert. Im Completion-Modell fordert das Betriebssystem Sie auf, Speicher zuzuweisen, bevor Daten tatsächlich eingehen, und es ist offensichtlich, was passiert.

Hier ist meine geänderte Version des Bereitschaftsmodells:

  • Sie teilen dem Kernel mit, dass Sie aus einem Socket lesen möchten
  • mache für eine Weile andere Dinge…
  • ÄNDERUNG: Daten kommen in das Betriebssystem (irgendwo im Kernelspeicher)
  • Der Kernel teilt Ihnen mit, dass der Socket bereit ist
  • Sie lesen (füllen Sie einen anderen Puffer, der vom obigen Kernel-Puffer getrennt ist (oder erhalten Sie einen Zeiger darauf?))
  • Mach was du brauchst
  • Geben Sie den Puffer frei (geschieht automatisch mit Rust)

/ Meine Annahmen

Ich mag es, das User-Space-Programm klein zu halten, aber ich wollte nur etwas Klarheit darüber, was in Wirklichkeit hier passiert. Ich sehe nicht, dass ein Modell von Natur aus weniger Speicher benötigt oder eine höhere Ebene gleichzeitiger E / A unterstützt. Ich würde gerne Gedanken und tiefere Erklärungen dazu hören.

kjs3
quelle
Ich bin auch von diesem YouTube-Vortrag hierher gekommen. Für alle, die lernen möchten, wie asynchrone E / A-Vorgänge ausgeführt werden oder wie Event-Loops implementiert werden, bietet das Rust-Team diese Wiedergabeliste mit "Aysnc-Interviews" , in der sehr sachkundige Leute aus der Community
interviewt werden

Antworten:

5

Im Bereitschaftsmodell ist der Speicherverbrauch proportional zur Datenmenge, die von der Anwendung nicht verbraucht wird.

Im Abschlussmodell ist der Speicherverbrauch proportional zur Anzahl der ausstehenden Socket-Anrufe.

Wenn es viele Sockets gibt, die größtenteils inaktiv sind, verbraucht das Bereitschaftsmodell weniger Speicher.

Es gibt eine einfache Lösung für das Abschlussmodell: Initiieren Sie einen Lesevorgang von 1 Byte. Dies verbraucht nur einen winzigen Puffer. Wenn der Lesevorgang abgeschlossen ist, geben Sie einen weiteren (möglicherweise synchronen) Lesevorgang aus, der den Rest der Daten abruft.

In einigen Sprachen ist das Abschlussmodell äußerst einfach zu implementieren. Ich halte es für eine gute Standardwahl.

usr
quelle
1

Im Completion-Modell fordert das Betriebssystem Sie auf, Speicher zuzuweisen, bevor Daten tatsächlich eingehen, und es ist offensichtlich, was passiert.

Aber was passiert, wenn mehr Daten eingehen, als Sie zugewiesen haben? Der Kernel muss weiterhin einen eigenen Puffer zuweisen, um die Daten nicht zu löschen. (Aus diesem Grund funktioniert beispielsweise der in der Antwort von usr erwähnte 1-Byte-Lesetrick.)

Der Nachteil ist, dass das Abschlussmodell zwar mehr Speicher benötigt, aber (manchmal) auch weniger Kopiervorgänge ausführen kann, da die Hardware durch Beibehalten des Puffers direkt aus oder in den DMA gelangen kann. Ich vermute auch (bin mir aber weniger sicher), dass das Abschlussmodell dazu neigt, den eigentlichen Kopiervorgang (sofern vorhanden) für einen anderen Thread auszuführen, zumindest für Windows IOCP, während das Bereitschaftsmodell dies als Teil des nicht blockierenden read()oder ausführt write()Anruf.

rpjohnst
quelle