banner
Heim / Nachricht / NVMe/TCP auf den neuesten Stand bringen
Nachricht

NVMe/TCP auf den neuesten Stand bringen

May 22, 2023May 22, 2023

Laden Sie die Präsentation herunter:NVMe/TCP auf den neuesten Stand bringen

Ich bin Sagi Grimberg. Ich bin CTO und Mitbegründer von Lightbits Labs und Mitautor der NVMe over TCP-Standardspezifikation.

Wir beginnen also mit einer kurzen Einführung. Was ist NVMe über TCP? NVMe über TCP ist die Standard-Transportbindung, die NVMe auf Standard-TCP/IP-Netzwerken ausführt. Es folgt der Standard-NVMe-Spezifikation, die die Warteschlangenschnittstelle und die Multi-Warteschlangenschnittstelle definiert, die direkt auf TCP/IP-Sockets ausgeführt wird. Es verfügt über den Standard-NVMe-Befehlssatz, ist aber zufällig in sogenannten NVMe/TCP-PDUs gekapselt, die die TCP-Streams abbilden. Im Diagramm hier haben wir also im Grunde die NVMe-Architektur, Kernarchitekturen, die den Admin, die E/A und andere Befehlssätze definieren.

01:14 SG: Darunter haben wir NVMe over Fabrics, das Kapseln, Eigenschaften und Entdeckung definiert. Und NVMe über TCP definiert im Grunde zusätzliche Funktionen und Messaging sowie die Transportzuordnung zur zugrunde liegenden Fabric selbst, in unserem Fall TCP/IP.

Wie wird also eine Warteschlange von NVMe über TCP verarbeitet oder wie ist sie in NVMe über TCP definiert? Grundsätzlich ist jede Warteschlange einer bidirektionalen TCP-Verbindung zugeordnet und die befohlene Datenübertragung wird normalerweise von einem dedizierten Kontext verarbeitet, sei es in der Software oder irgendwie in der Hardware. Im Diagramm hier haben wir also auf der linken Seite den Host, der über eine Warteschlangenschnittstelle zum NVMe-Transport selbst verfügt, eine Übermittlungswarteschlange und eine Abschlusswarteschlange hat. Alle Übermittlungen und Abschlüsse werden in einem so genannten NVMe-TCP-E/A-Thread oder einem E/A-Kontext abgewickelt, der entweder vom Host, der E/A sendet, oder vom Netzwerk ausgelöst wird und normalerweise E/A abschließt oder empfängt Daten.

02:24 SG: Das gleiche Bild ergibt sich auf der rechten Seite mit dem Controller, und im Grunde handelt es sich hierbei um die Kontexte, die für die Datenübertragung zwischen dem Host und dem Controller zuständig sind. Jede dieser Warteschlangen ist also normalerweise, aber nicht unbedingt, dedizierten CPUs zugeordnet. Es könnten mehr oder weniger sein. Der Punkt ist jedoch, dass es keine Controller-weite Serialisierung gibt, sodass nicht jede Warteschlange von einer gemeinsamen CPU abhängig ist Band mit anderen Warteschlangen, was es extrem parallel macht. Und das Diagramm hier ist ein Standarddiagramm, das zuvor über den Satz von Warteschlangen gezeigt wurde. Sie haben auch die Admin-Warteschlange zwischen dem Host und dem Controller und dann einen Satz von E/A-Warteschlangen, Warteschlangenpaaren, die Übermittlungs- und Abschlusswarteschlangen sind . Bei NVMe/TCP ist grundsätzlich jede dieser Warteschlangen einer bidirektionalen TCP-Verbindung zugeordnet. Wenn wir uns also die Latenzbeiträge ansehen, gibt es eine Reihe davon, die sich einschleichen könnten. Erstens bei der Serialisierung, aber bei NVMe/TCP ist es ziemlich leichtgewichtig, es erfolgt pro Warteschlange und kann daher skaliert werden ziemlich gut.

Dieser Artikel ist Teil von

03:43 SG: Kontextwechsel. Wir haben also mindestens zwei vom Treiber selbst beigesteuerte Speicherkopien, normalerweise Antiquitäten, wir sind in der Lage, als Kernel-Level-Treiber keine Kopien durchzuführen. Bei RX führen wir jedoch eine Speicherkopie durch. Das ist kein großer Faktor, aber bei sehr, sehr hoher Last kann es zu zusätzlicher Latenz führen. Interrupts – NIC-Interrupts – sind definitiv wirkungsvoll, sie verbrauchen CPU und wirken sich auf die Skalierbarkeit dessen aus, was eine einzelne Warteschlange erreichen kann. Wir haben LRO und GRO oder eine adaptive Interrupt-Moderation kann das etwas abmildern, aber dann könnte die Latenz weniger konsistent sein. Dann haben wir den Socket-Overhead. Er existiert, ist aber wirklich nicht riesig. Er ist ziemlich schnell, wenn man bedenkt, dass Sockets in einer Multi-Queue-Schnittstelle ziemlich unbestritten sind, aber bei kleinen I/O-Vorgängen könnte er einen Einfluss haben. Die Affinität zwischen Interrupts, Anwendungen und I/O-Threads kann sich definitiv auswirken, wenn sie nicht richtig konfiguriert wird, und wir werden darauf näher eingehen. Es gibt natürlich einige Cache-Verschmutzungen, die durch Speicherkopien entstehen, aber bei modernen CPU-Kernen, die über ausreichend große Caches verfügen, ist das nicht so übermäßig.

05:15 SG: Und dann haben wir noch die Head-of-Line-Blockierung, die bei gemischten Arbeitslasten auftreten kann, und wir werden darauf eingehen, wie wir damit umgehen. Nehmen wir also ein Beispiel für den direkten Host-Datenfluss. Es beginnt damit, dass der Benutzer grundsätzlich eine Datei ausgibt oder E/A-Vorgänge an ein Gerät oder einen Dateideskriptor blockiert, und wir dann viele Schichten im Stapel ignorieren, aber schließlich liegt es an dem Warteschlangenrückruf, der im Treiber selbst sitzt. Wir reden natürlich über Linux. Dieser Rückruf ist eine NVMe/TCP-Warteschlangenanforderung und bereitet eine NVMe/TCP-PDU vor, die sie zur weiteren Verarbeitung in eine interne Warteschlange stellt. Dann haben wir die I/O-Arbeit, das ist der I/O-Kontext oder der I/O-Thread nimmt diese E/A auf, beginnt mit der Verarbeitung und sendet sie tatsächlich an den Controller. Dann verarbeitet der Controller es, schließt die E/A ab und sendet Daten zurück, wenn es sich um einen Lesevorgang handelt, oder beendet den Abschluss an den Host.

06:21 SG: Sobald der Host es empfängt und als erster sieht, dass es sich um die Netzwerkkarte handelt, generiert er einen Interrupt, der besagt, dass er über weitere zusätzliche Datagramme verfügt, die vom Host verarbeitet werden sollten. Dann wird NAPI ausgelöst, das im Grunde alle diese Datagramme abruft und in das einfügt TCP/IP-Stack und verarbeitet ihn. Dann haben wir den Treiber, während der TCP-Verbraucher einen Datenbereit-Rückruf erhält, der an diesem Punkt den Kontext auslöst, um grundsätzlich Daten und den Abschluss zu verarbeiten und die E/A tatsächlich abzuschließen. Und die nächste besteht im Grunde darin, dass der Benutzerkontext die E/A vervollständigt. Wenn es sich um eine asynchrone Schnittstelle handelt, wird sie wahrscheinlich durch die Gate-Verteidigung oder ein Signal gelangen. Hier ist zunächst zu beachten, dass wir einen Kontextwechsel zwischen dem Teil haben, in dem wir die E/A vorbereiten, und dem Teil, der die E/A-Arbeit geplant hat, um die E/A aufzunehmen und zu verarbeiten Dann haben wir eine Software-E/A-Warteschlange zwischen dem Interrupt und dem Auslösen von NAPI und dann haben wir einen zusätzlichen Kontext, der umschaltet, sobald „Data Ready“ aufgerufen wird, und der E/A-Arbeitskontext geht tatsächlich weiter und verarbeitet die E/A .

07:36 SG: Dies sind also die drei Bereiche, bei deren Beseitigung wir helfen können. Nun wurden zunächst Optimierungen vorgenommen, um gemischte Arbeitslasten zu bewältigen. Wie bereits erwähnt, könnte die Head-of-Queue-Taktung in dem Fall sichtbar werden, in dem wir einen großen Schreibvorgang haben, der vom Host kommt, mit einer Warteschlange gesendet werden muss und dann von einem Host gefunden wird selbst sendet eine große Nachricht, da NVMe/TCP die Nachrichtenübertragung über dem TCP-Stream definiert und sich dahinter ein kleiner Lesevorgang befindet, der erst dann vorwärts kommen kann, wenn dieser große Schreibvorgang tatsächlich abgeschlossen ist, wenn er sich zufällig in einer einzelnen Warteschlange befindet.

08:16 SG: Dieses Problem ist also offensichtlich, und die Abhilfe beginnt mit der Trennung verschiedener Warteschlangenkarten, die die Linux-Blockschicht heute ermöglicht, die grundsätzlich die verschiedenen E/A-Typen definieren und unterschiedliche Warteschlangen verwenden kann. Unter Linux haben wir also drei verschiedene Warteschlangenkarten. Eine davon ist die Standardwarteschlange. Im Grunde wird sie den Standardsatz von Warteschlangen hosten, normalerweise, wenn nur die Standardwarteschlangenzuordnung definiert ist und alle E/As diese in einer Warteschlangenzuordnung verwenden.

Dann haben wir eine dedizierte Warteschlangenkarte nur für Lese-E/A, so dass den Lesevorgängen ein dedizierter Satz von Warteschlangen zugeordnet ist, und der Rest wird zur Standard-Warteschlangenkarte gehen, und die dritte ist dann die Pole-Warteschlangenkarte. Wenn die Anwendung dem Kernel normalerweise über ein Flag, ein I/O-Flag mit hoher Priorität, signalisiert, dass sie an einem I/O-Befehl mit hoher Priorität interessiert ist, wird sie an die Pole-Warteschlangenkarte weitergeleitet, und diese Pole-Warteschlangenkarte kann das tatsächlich entwerfen Latenzempfindliche I/Os des Hosts.

09:38 SG: Was wir damit gemacht haben, ist, dass wir, sobald wir gemischte Arbeitslasten haben, unterschiedliche Lese- und Schreibvorgänge haben, die alle auf derselben Anwendung oder sogar unterschiedlichen Anwendungen auf demselben Host gehostet werden. Diese Lese- und Schreibvorgänge werden nun über unterschiedliche Warteschlangen gesteuert Sie werden niemals eine Head-of-Line-Blockierung zwischen Lese- und Schreibvorgängen feststellen. Wir haben also im Wesentlichen je nach Benutzeranforderung zusätzliche Warteschlangen in NVMe/TCP zugewiesen und dann tatsächlich einen Benchmark durchgeführt, indem wir Plug-Ins durchgeführt haben. . . Wir haben es an das Blockband angeschlossen und einen Benchmark durchgeführt, um zu sehen, was die Verbesserung ist. Der Test bestand also im Grunde aus 16 Lesegeräten, von denen jedes synchrone Lesevorgänge ausführt, nur einen nach dem anderen, und im Hintergrund hatten wir eine Art ungebundener Schreiber, der einen hohen Burst von ein Megabyte großen Schreibvorgängen ausgibt. Dies ist nun ungebunden, was bedeutet, dass dieser Thread, der Schreibvorgänge von einem Megabyte in einer hohen Warteschlangentiefe ausgibt, zwischen den verschiedenen CPU-Kernen rotiert und am Ende in Warteschlangen mit den übrigen 16 Lesern geteilt wird.

11:10 SG: Mit der Basislinie sehen wir also die Lese-QoS, die Lese-IOPS liegen bei etwa 80.000 IOPS mit einer durchschnittlichen Latenz von 396 Mikrosekunden und die Tail-Latenz kann bis zu 14 Millisekunden betragen, was natürlich nicht großartig ist, aber jetzt, wo wir Lese- und Lese-IOPS getrennt haben In verschiedene Warteschlangenkarten schreibt, haben wir jetzt festgestellt, dass sich die IOPS mit 171.000 IOPS mehr als verdoppelt haben, die durchschnittliche Leselatenz mit 181 Mikrosekunden weniger als die Hälfte beträgt und die Read-Tail-Latenz von vier Neunen sich um eine Größenordnung verbessert hat. Das ist also wichtig zu verstehen.

Als nächstes haben wir Affinitätsoptimierungen. Da wir nun über unterschiedliche Warteschlangenkarten verfügen, stellt sich nun die Frage, wie diese zu E/A-Threads und zu Anwendungen passen. Grundsätzlich verfügt jede NVMe/TCP-Warteschlange unter Linux über eine I/O-CPU, die definiert, wo der I/O-Kontext ausgeführt wird. Was wir getan haben, ist, dass wir eine separate Ausrichtung für verschiedene Warteschlangenkarten verwendet haben, die jeweils bei Null beginnen, sodass sie einzeln abgerechnet und nicht alle zusammen kombiniert werden, wodurch eine sehr gute Ausrichtung zwischen erreicht wird. . . Das ist es, was Sie wollen, zwischen der Anwendung und dem E/A-Thread, unabhängig von der Warteschlangenkarte, auf der die E/A letztendlich landet.

12:50 SG: Also haben wir tatsächlich einen Mikro-Benchmark durchgeführt, um die Verbesserung zu testen, und im Benchmark hier haben wir die Warteschlangentiefe eine kanonische 4K-Latenz für das Lesen getestet und dann haben wir wiederum eine Single-Threaded-Anwendung und Single-Threaded-Workload mit einer Warteschlangentiefe von 32 verwendet. wieder 4K liest. Bei der Warteschlangentiefe 1 haben wir also eine Verbesserung von 10 % erzielt, was großartig ist, und dann haben wir bei der Warteschlangentiefe 32 tatsächlich mehr zwischen 179.000 IOPS und 231 erreicht. Das passiert also sofort, also war das auf jeden Fall schön Verbesserung.

Dann konzentrieren wir uns auf Kontaktwechsel und darauf, was getan wurde, um dies zu mildern. Denken Sie also daran, dass das Ziel auf dem Takes-Pfad einmal in den paar Folien zuvor gezeigt wurde, dass, sobald der Warteschlangenrückruf und der Treiber-Warteschlangenrückruf, die NVMe/TCP-Warteschlangenanforderung eine E/A erhält, sie tatsächlich auf ihrem internen Cue gepostet wurde und dann Es löst unseren Kontextwechsel zu dem E/A-Kontext aus, der den TCP-Stamm besitzt. Deshalb wollen wir das beseitigen.

14:10 SG: Was wir hier also im Grunde tun, ist, dass wir im Grunde im Rückruf selbst tatsächlich damit fortfahren und die E/A direkt aus diesem Kontext senden, wodurch der E/A-Thread-Kontext bei diesem Kontextwechsel eliminiert wird. Da wir nun zwei Kontexte haben, die tatsächlich gleichzeitig auf die TCP-Verbindung zugreifen können, müssen diese offensichtlich serialisiert werden. Daher müssen wir den Serialisierungskontext zwischen ihnen hinzufügen, der in einer Art Stummschaltungstest erfolgte, und außerdem ist nicht garantiert, dass der Netzwerksende atomar ist, sodass wir das Sperrschema im Herzen und die Warteschlangenschnittstelle im Block ändern müssen Schicht, die es von RC zu SRC ändert.

Angesichts der Tatsache, dass wir jetzt zwei Kontexte haben, die gemeinsam auf den Stream zugreifen können, möchten wir ihn möglicherweise nicht bestreiten. Daher führen wir diese Optimierung nur durch, wenn die Warteschlange leer ist, d auf dem . . . Wenn der E/A-Kontext und der Kontext, in dem die Übermittlung erfolgt, auf demselben CPU-Port ausgeführt werden, was bedeutet, dass, wenn wir mit jemandem streiten, es bei uns selbst liegt, sodass wir wahrscheinlich in der Lage sind, zu greifen, ohne dafür eingeplant zu werden sperren.

15:40 SG:Also haben wir das gemacht, und eine zweite Optimierung ist das Hinzufügen einer Softwarepriorität, die im Grunde die metrische Warteschlange mit einer ADQ-Technologie definiert, sodass der ausgehende Datenverkehr im Grunde an den dedizierten ADQ- und NCQ-Satz gelenkt wird.

Nun gehen wir zur RX-Ebene über, wie bereits erwähnt: Sobald wir einen Interrupt erhalten, bei dem wir die E/A nicht direkt vom Interrupt selbst verarbeiten, lösen wir tatsächlich die Arbeit der E/A-Kontexte des Worker-Threads aus, um diese tatsächlich zu verarbeiten eingehende Daten und Abschluss. Wenn die Anwendung also abfragt, haben wir die festgelegte Abfragewarteschlange und das Hyperprioritätsflag erwähnt, das in Linux unterstützt wird.

Jetzt haben wir Anwendungen, die entweder über direkte I/O oder über den I/O-Ring abfragen können. Daher haben wir grundsätzlich einen NVMe/TCP-Polling-Callback hinzugefügt und ihn in die Multi-Queue-Block-Poll-Schnittstelle eingebunden. Da es nun Schnittstellenaufrufe abruft, nutzt der Aufruf einfach die BC-Funktionalität im Netzwerkstapel, und wenn wir dann abfragen und die Anwendung abfragt, können wir es in den Daten identifizieren und wenn wir den Rückruf erhalten, tun wir es Da wir keinen zusätzlichen Kontext planen, planen wir den E/A-Arbeitsthread nicht, da wir wissen, dass wir die eingehenden Daten direkt aus der Umfrage verarbeiten werden.

17:27 SG: Grundsätzlich funktioniert die Interrupt-Moderation moderner NIC-CPUs relativ gut, um einige Interrupts zu reduzieren, aber mit ADQ sieht es so aus, als ob Interrupts aggressiver entschärft werden, und das funktioniert sehr gut. Also haben wir es getan. . .

Nachdem wir diese beiden Eliminierungen der Kontextwechsel hinzugefügt haben, führen wir erneut den gleichen Benchmark durch. Die Basislinie war nun die angewendete I/O-Optimierung und wir erreichten zusätzliche 10 % der kanonischen Latenz der Warteschlangentiefe 1 für 4K-Lesevorgänge, und wir erreichten meiner Meinung nach auch eine Verbesserung von etwa 5 bis 10 % der Warteschlangentiefe 32 zwischen 230.000 IOPS und 247.000 IOPS aus einem einzelnen Thread. Das ist gut. Für die Zukunft wurden natürlich mit der neuesten E800-Serie und der neuesten Netzwerkkarte von Intel einige ADQ-Verbesserungen eingeführt.

18:37 SG: Welche ADQ-Verbesserungen sind also verfügbar? Im Allgemeinen handelt es sich zunächst einmal um die Datenverkehrsisolation, bei der Sie einen bestimmten Warteschlangensatz zuordnen und die Netzwerkkarte konfigurieren können, sodass der spezifische Anwendungsworkflow auf einen dedizierten Warteschlangensatz gelenkt werden kann, der nicht mit der anderen Anwendung des Hosts geteilt wird. Bei der eingehenden Konfiguration besteht die Konfiguration grundsätzlich darin, die Konfiguration des Warteschlangensatzes zu definieren, die TC-Flower-Filterung anzuwenden und dann eine Warteschlangenauswahl über RSS oder Flow Director hinzuzufügen. Beim Outbound wird die Softwarepriorität festgelegt. Wir haben erwähnt, dass wir sie im NVMe/TCP-Controller über den Modusparameter aktivieren und dann auch Erweiterungen für die Übertragung von Paketen konfigurieren, um XPS so zu steuern, dass es der symmetrischen Warteschlange entspricht.

19:36 SG: Der Vorteil besteht darin, dass wir keinen lauten Datenverkehr zwischen Nachbar-Workloads haben und die Möglichkeit haben, Netzwerkparameter an einen bestimmten Arbeitsablauf anzupassen, und genau das nutzen wir bei NVMe/TCP. Weitere Verbesserungen sind natürlich die Minimierung des Kontaktwechsels und des Interrupt-Overheads durch die Anwendung von Polling. Sobald die Anwendung abruffreundlich ist, haben wir im Grunde jetzt dedizierte Warteschlangen, die mit ADQ konfiguriert sind und als unsere Abrufwarteschlangen in NVMe/TCP fungieren, das ist der Warteschlangensatz. Was wir tun, ist, dass wir die Netzwerkvervollständigung innerhalb des Anwendungskontexts während der Abfrage entleeren und Vervollständigungen direkt im Anwendungskontext verarbeiten. Wir bearbeiten die Anfrage, wenn wir die Antwort senden, wie gesagt, direkt und eliminieren den Kontextwechsel zwischen dem I/O-Kontext und der Anwendung.

Wir haben auch die Möglichkeit, mehrere Warteschlangen in einer einzigen NIC-Hardware-Warteschlange zusammenzufassen, um die gemeinsame Nutzung der NIC-Hardware-Warteschlangen grundsätzlich zu optimieren, ohne dass unnötige Kontextwechsel erforderlich sind. Wenn wir uns also daran erinnern, dass wir im Treiber zwei Kontextschalter hatten, einen DTX, PAX, einen RxPax und einen Soft-Interrupt, der vom NIC-Interrupt gesteuert wurde, haben wir jetzt alle drei zusammen mit den Verbesserungen und ADQ eliminiert . Der Wert hat also die CPU-Auslastung sowie die Latenz und den Latenz-Jitter reduziert, die insgesamt niedrig sind.

21:25 SG: Hier sind einige der Messungen, die NVB TCP mit aktiviertem oder deaktiviertem ADQ und der Latenz in QDAP1 erzielt. . . Das liegt natürlich daran, ich habe vergessen zu erwähnen, dass es sich um die zusätzliche Latenz zusätzlich zu den Medien selbst handelt, und der Transport-Overhead ist derzeit bei ADQ 17,7 und könnte je nach CPU-Klasse auch geringer sein. Und auf QDAP32 erzielen wir tatsächlich eine enorme Verbesserung von fast 30 %, wenn nicht sogar mehr, zwischen 245.000 IOPS und 341.000 IOPS in einem einzelnen Thread. Wenn Sie diesen Thread nun multiplizieren und auf den Gesamtdurchsatz anwenden, den Sie vom Host erhalten können, können Sie grundsätzlich ziemlich linear bis zu einem Punkt skalieren, an dem Sie mit etwas mehr als einer Handvoll Kernen oder 10 Kernen eine Host-Sättigung erreichen können.

22:37 SG: Okay. Jetzt führen wir zu den endgültigen Optimierungen, die fast unabhängig von der gesamten Latenzoptimierung bei Polling-Workloads sind, und gehen zu Latenzoptimierungen über, die sich auf die Stapelverarbeitung beziehen und eher für Anwendungen gedacht sind, bei denen kanonische Latenz und hohe Priorität I weniger wichtig sind /O, aber es geht mehr darum, eine bandbreitenorientierte Arbeitslast zu haben und dennoch eine gute Latenz bei hoher Bandbreite zu erreichen. Die Optimierungen sind also stapelorientiert. Wir wollen Informationen nutzen, die die Blockschicht im Treiber über den Aufbau einer Warteschlange bereitstellen kann. Aus Fahrersicht haben Sie also keinen wirklichen Überblick über die gesamte Warteschlange, die sich in der Blockschicht aufbaut.

23:35 SG: Das erste, was wir tun wollen, ist also, was als erstes verfügbar ist, und die Blockschicht kann dem Treiber grundsätzlich anzeigen, ob die in der Warteschlange befindliche Anfrage tatsächlich die letzte ist oder nicht. Was wir dann tun, ist, dass wir die Warteschlange und die Art und Weise ändern, wie sie mit der internen Warteschlange umgeht. Wir machen das grundsätzlich einfacher, indem wir es von einer Liste, die durch eine Sperre geschützt ist, in eine Liste ohne Sperre verschieben. Und damit sind wir im Grunde . . . Zwischen der Schnittstelle der Warteschlange und dem E/A-Kontext, der Anforderungen aus dieser Warteschlange abruft, machen wir es zu einem stapelorientierten Push-and-Pull, sodass wir eine bessere Auslastung oder weniger häufige atomare Vorgänge und mehr Stapelverarbeitung erreichen können.

24:43 SG: Mit all diesen Informationen haben wir im Grunde jetzt einen besseren Überblick über die Warteschlange, die sich aufbaut, und wir haben die Verarbeitung um sie herum effizienter gestaltet. Dann fügen wir diese Informationen hinzu und gelangen über Nachrichtenflags über unsere Sendevorgänge zum Netzwerkstapel Lassen Sie das Netzwerk effizienter agieren. Wenn wir beispielsweise wissen, dass wir ein Datenelement an das Netzwerk senden werden und dass sich hinter uns eine Warteschlange aufbaut, zeigen wir dies mit der Nachricht „mehr“ an. Daher weisen wir den Stapel darauf hin, dass er auf weitere Daten warten muss und diese nicht unbedingt zusammen senden muss, sondern die Möglichkeit zur Stapelverarbeitung hat. Wenn dies jedoch das letzte Datenelement in der Warteschlange ist, das wir senden werden, und wir nichts wissen, was sich dahinter aufbaut, aktivieren wir tatsächlich das Ende des Datensatzes der Nachricht, um dies dem Stapel anzuzeigen Was auch immer es aufgebaut hat, es sollte weitermachen und es jetzt einfach abschicken.

25:53 SG: Und das letzte ist eine Arbeit eines Teams der Cornell University, das einen I/O-Scheduler entwickelt hat, der für diese Art von TCP-Stream-Batching-Arbeitslast optimiert ist und hinsichtlich Bandbreite und Latenz optimiert ist. Sie können dies im i10-Papier finden, das über einen Link verfügbar ist, oder Sie können einfach nach „i10 Cornell“ suchen.

Dieser I/O-Planer ist auf dem Weg zum Upstream und wird bald eingereicht. Ein paar Benchmark-Ergebnisse, die das Cornell-Team durchgeführt hat, können wir hier auf der linken Seite sehen, dies ist nur Standard, oder das Upstream-NVMe/TCP ohne den i10-I/O-Scheduler und die Optimierung. Wir sehen die 4K-Lese-IOPS, die es erreichen kann, wir sehen hier, dass die Latenz etwa 100 Mikrosekunden beträgt, und sobald wir 145 oder knapp 150 erreichen, beginnt die Latenz einfach anzusteigen, weil wir keine mehr Effizienz haben und die Latenz einfach anfängt Wenn wir uns ansammeln, stoßen wir irgendwie an eine Wand. Und mit i10 gibt es einige Einbußen, kleine Einbußen bei der Latenz bei niedrigem QDAP, aber bei höheren QDAPs können wir sehen, dass wir über 200.000 IOPS oder fast 225.000 IOPS steigern können, bis wir an diese Grenze stoßen, an der die Latenz einfach zunimmt. Es zeigt uns also, dass es zunächst einmal einen höheren Durchsatz gibt, und zwar für einen einzelnen Thread. Höherer Durchsatz und höhere IOPS, und auch die Latenz bleibt ziemlich konstant.

28:01 SG: Auf der rechten Seite können wir nur den Durchsatz sehen, wir haben 16 Kerne gegenüber dem RAM-Gerät. Wir haben 16 Kerne und sehen, dass insgesamt der Durchsatz bei allen Anforderungsgrößen, bis er die Netzwerkkarte fast sättigt, mit i10 verbessert wird, und das sollte uns darauf hinweisen, dass Stapelverarbeitung und Stapelverarbeitung grundsätzlich hilfreich sind, insbesondere wenn wir über einen Stream sprechen. basierte Anwendung, die NVMe/TCP eigentlich ist. Okay. Da wir uns gerade auf einer Aufnahme befinden, haben wir keine Fragen. Vielen Dank fürs Zuhören.

Laden Sie die Präsentation herunter:01:14 SG:02:24 SG:03:43 SG:05:15 SG:06:21 SG:07:36 SG:08:16 SG:09:38 SG:11:10 SG:12:50 SG:14:10 SG:15:40 SG:17:27 SG:18:37 SG:19:36 SG:21:25 SG:22:37 SG:23:35 SG:24:43 SG:25:53 SG:28:01 SG: