Sunday 12 March 2017

Ring Puffer Lmax Forex

I8217m gehen, um einen schnellen Abfluss auf, wie wir Nachrichten in den Ring-Puffer (die Kern-Datenstruktur innerhalb der Disruptor) ohne Verwendung von Sperren. Bevor es weiter geht, lohnt es sich einen schnellen Lesestoff von Trish8217s Post. Die einen übergeordneten Überblick über den Ringpuffer und dessen Funktionsweise gibt. Die markanten Punkte von diesem Beitrag sind: Der Ringpuffer ist nichts anderes als ein großes Array. Alle 8220pointers8221 in den Ringpuffer (sonst bekannt als Sequenzen oder Cursor) sind Java longs (64-Bit-signierte Zahlen) und zählen für immer aufwärts. (Don8217t Panik 8211 sogar bei 1.000.000 Nachrichten pro Sekunde, würde es den besten Teil der 300.000 Jahre dauern, um die Sequenz-Nummern wickeln). Diese Zeiger sind dann 8220mod8217ed8221 durch die Ringpuffergröße, um herauszufinden, welcher Array-Index den gegebenen Eintrag enthält. Für die Leistung erzwingen wir tatsächlich die Ringpuffergröße, um die nächste Energie von zwei größer als die Größe zu sein, die Sie fordern, und dann können wir eine einfache Bitmaske verwenden, um den Array-Index herauszufinden. Grundlegende Ringpufferstruktur WARNUNG: Hinsichtlich der Organisation des Codes. Vieles, was ich sagen will, ist eine Vereinfachung. Konzeptionell denke ich es einfacher zu verstehen, ab wie ich es beschreibe. Der Ringpuffer verwaltet zwei Zeiger, 8220next8221 und 8220cursor8221: In der Abbildung oben, ein Ringpuffer der Größe 7 (hey, Sie wissen, wie diese handgezeichneten Diagramme manchmal arbeiten) hat Steckplätze 0 bis 2 mit Daten gefüllt. Der nächste Zeiger bezieht sich auf den ersten freien Steckplatz. Der Cursor bezieht sich auf den zuletzt gefüllten Steckplatz. In einem Leerlaufringpuffer sind sie, wie gezeigt, benachbart zueinander. Beanspruchen eines Steckplatzes Die Disruptor-API hat ein transaktionsorientiertes Verhalten. Sie 8220claim8221 einen Steckplatz im Ringpuffer, dann schreiben Sie Ihre Daten in den beanspruchten Steckplatz, dann Sie 8220commit8221 die Daten. Let8217s nehmen dort8217s einen Thread an, der den Buchstaben 8220D8221 in den Ringpuffer setzen will. Sie beansprucht einen Schlitz. Der Claim-Vorgang ist nichts weiter als eine Operation CAS 8220get-and-increment8221 auf dem nächsten Pointer. Das heißt, dieser Thread (let8217s nennt es Thread D) macht einfach einen atomaren Get-und-Increment, der den nächsten Pointer auf 4 verschiebt und zurückgibt. Thread D hat nun Slot 3 beansprucht: Als nächstes beansprucht ein weiterer Thread (Thread E) Slot 4 in der gleichen Weise: Committing der Schreibvorgänge Nun können die Threads D und E beide Daten sicher und gleichzeitig in ihre jeweiligen Slots schreiben. Aber let8217s sagen, dass Thread E beendet zuerst aus irgendeinem Grund8230 Thread E versucht, seine Schreiben zu verpflichten. Die Commit-Operation besteht aus einer CAS-Operation in einer Besetztschleife. Da der Thread E den Slot 4 beansprucht, führt er einen CAS aus, der darauf wartet, dass der Cursor auf 3 gelangt und dann auf 4 gesetzt wird. Dies ist wiederum eine atomare Operation. So, wie der Ringpuffer steht jetzt, Thread E wird sich zu drehen, weil der Cursor auf 2 gesetzt ist und es (Thread E) wartet auf den Cursor auf 3 sein. Jetzt Thread D Commits. Es ist ein CAS-Vorgang und setzt den Cursor auf 3 (den Slot behauptet), wenn der Cursor derzeit auf 2. Der Cursor ist derzeit auf 2, so dass der CAS erfolgreich ist und das Commit erfolgreich ist. An diesem Punkt wurde der Cursor auf 3 aktualisiert und alle Daten bis zu dieser Sequenznummer stehen zum Lesen zur Verfügung. Dies ist ein wichtiger Punkt. Wenn 822bis8221 der Ringpuffer 8211 ist, d. h. wieviel Daten geschrieben worden sind, welche Sequenznummer den höchsten Schreibvorgang darstellt, usw. 8211 ist rein eine Funktion des Cursors. Der nächste Zeiger wird nur für das transaktionale Schreibprotokoll verwendet. Der letzte Schritt in das Puzzle ist, dass Thread E8217s sichtbar zu schreiben. Thread E ist immer noch versuchen, eine atomare Aktualisierung des Cursors von 3 bis 4 zu tun. Jetzt ist der Cursor auf 3, wird sein nächster Versuch erfolgreich sein: Die Reihenfolge, die schreibt sichtbar ist, wird durch die Reihenfolge, in der Threads behaupten Slots statt Die Reihenfolge, die sie verpflichten ihre Schriften, aber wenn Sie sich vorstellen, diese Threads sind das Ziehen von Nachrichten einer Netzwerk-Messaging-Schicht, dann ist dies wirklich nicht anders als die Nachrichten, die zu etwas anderen Zeiten oder die beiden Threads racing, um die Slot-Anspruch in einer anderen Reihenfolge. So dort haben wir es. It8217s ein ziemlich einfacher und eleganter Algorithmus. (OK, ich gebe zu, dass ich stark in seine Kreation involviert war) Schriften sind atomar, transaktional und lock-frei, auch mit mehreren Schreibfäden. (Danke an Trish für die Inspiration für die von Hand gezeichneten Diagramme) Dissecting the Disruptor: Was ist so besonders an einem Ringpuffer Kürzlich haben wir den LMAX Disruptor bezogen. Der Schlüssel zu dem, was unseren Austausch so schnell macht. Warum haben wir Open Source es Nun, wissen wir, dass konventionelle Weisheit um Hochleistungs-Programmierung ist. Ein bisschen falsch. Weve kommen mit einer besseren, schnelleren Möglichkeit, Daten zwischen Threads zu teilen, und es wäre egoistisch, es nicht mit der Welt zu teilen. Außerdem macht er uns tot klug aussehen lassen. Auf der Website können Sie einen technischen Artikel zu erklären, was der Disruptor ist und warum seine so clever und schnell. Ich bekomme sogar eine schriftliche Gutschrift auf sie, das ist erfreulich, wenn alles, was ich wirklich tat, ist einfügen Kommas und Re-Satz-Sätze ich didnt verstehen. Allerdings finde ich die ganze Sache ein bisschen viel zu verdauen, auf einmal, so Im werde es in kleineren Stücke zu erklären, wie passt meine NADD Publikum. Zuerst - der Ringpuffer. Zuerst war ich unter dem Eindruck der Disruptor war nur der Ringpuffer. Aber Ive kommen zu erkennen, dass, während diese Datenstruktur ist das Herzstück des Musters, das kluge über den Disruptor kontrolliert den Zugang zu ihm. Was auf der Erde ist ein Ringpuffer Nun, es tut, was es auf der Zinn sagt - es ist ein Ring (seine kreisförmigen und Wraps), und Sie verwenden es als Puffer, um Zeug aus einem Kontext (ein Thread) an einen anderen: (OK , Ich zog es in Paint. Im Experimentieren mit Skizze-Stile und hoffen, meine OCD doesnt Kick in und fordern perfekte Kreise und gerade Linien in präzisen Winkel). Also im Grunde seine ein Array mit einem Zeiger auf den nächsten verfügbaren Steckplatz. Wenn Sie den Puffer (und den voraussichtlichen Wert von ihm auch) füllen, wird die Sequenz inkrementiert und wickelt um den Ring: Um den Schlitz in dem Array zu finden, das die aktuelle Sequenz zeigt, verwenden Sie eine Mod-Operation: Sequenzmodray-Array-Länge-Array Index Also für den obigen Ringpuffer (mit Java-Mod-Syntax): 12 10 2. Einfach. Eigentlich war es ein totaler Unfall, dass das Bild zehn Slots hatte. Mächte von zwei arbeiten besser, weil Computer denken in binär. So was Wenn Sie bei Wikipedias Eintrag auf Circular Buffers. Youll sehen einen Hauptunterschied zur Weise, die wir unser eingeführt haben - wir haben nicht einen Zeiger zum Ende. Wir haben nur die nächste verfügbare Sequenznummer. Das ist bewusst - der ursprüngliche Grund, weshalb wir uns für einen Ringpuffer entschieden haben, war, dass wir zuverlässige Messaging unterstützen konnten. Wir benötigten einen Speicher der Nachrichten, die der Service gesendet hatte, also, als ein anderer Service ein nak schickte, um zu sagen, dass sie nicht empfangene Mitteilungen empfangen hatten, würde es in der Lage sein, sie zurückzusenden. Der Ringpuffer scheint dafür ideal zu sein. Es speichert die Sequenz, um anzuzeigen, wo das Ende des Puffers ist, und wenn es ein nak bekommt, kann es alles von diesem Punkt auf die aktuelle Sequenz wiederholen: Die Differenz zwischen dem Ringpuffer, wie wir es implementiert haben, und die Warteschlangen, die wir traditionell waren Dass wir dont verbrauchen die Elemente in den Puffer - sie bleiben dort, bis sie über-geschrieben werden. Aus diesem Grund brauchen wir den Endzeiger nicht, den Sie in der Wikipedia-Version sehen. Das Entscheiden, ob sein OK, um zu wickeln oder nicht, wird außerhalb der Datenstruktur selbst verwaltet (dieses ist ein Teil des Produzenten - und Verbraucherverhaltens - wenn Sie nicht warten können, um mich zu erhalten, um herum zu bloggen, überpasst die Störungaufstellungsort). Und seine so groß, weil. So verwenden wir diese Datenstruktur, weil es uns ein paar schöne Verhalten für zuverlässige Messaging. Es stellt sich heraus, dass es einige andere schöne Eigenschaften hat. Erstens, seine schneller als etwas wie eine verknüpfte Liste, weil es ein Array, und hat ein vorhersagbares Muster des Zugriffs. Das ist nett und CPU-Cache-freundlich - auf Hardware-Ebene können die Einträge vorab geladen werden, so dass die Maschine nicht ständig zurück zum Hauptspeicher geht, um das nächste Element in den Ring zu laden. Zweitens ist es ein Array und Sie können vorzuteilen, es vorne, so dass die Objekte effektiv unsterblich. Das bedeutet, dass der Garbage Collector so ziemlich nichts zu tun hat. Wieder anders als eine verknüpfte Liste, die Objekte für jedes Element erstellt, um die Liste - diese dann alle aufgeräumt werden, wenn das Element nicht mehr in der Liste erstellt wird. Die fehlenden Stücke ich havent sprach darüber, wie die Ringverpackung zu verhindern, oder Besonderheiten rund um, wie man Zeug zu schreiben und lesen Sie die Dinge aus dem Ringpuffer. Youll auch bemerken Ive Vergleich mit einer Datenstruktur wie eine verkettete Liste, die ich nicht glaube, jeder glaubt, ist die Antwort auf die Welten Probleme. Der interessante Teil kommt, wenn Sie den Disruptor mit einer Implementierung wie eine Warteschlange vergleichen. Warteschlangen kümmern sich in der Regel um alle Dinge wie den Anfang und das Ende der Warteschlange, Hinzufügen und Aufnehmen von Elementen und so weiter. Alle Sachen, die ich havent wirklich mit dem Ringpuffer berührt. Das ist, weil der Ringpuffer selbst nicht für diese Sachen verantwortlich ist, weve verschoben diese Interessen außerhalb der Datenstruktur. Für weitere Details youre nur gehen zu müssen, lesen Sie die Zeitung oder überprüfen Sie den Code. Oder sehen Sie Mike und Martin bei QCon San Francisco im letzten Jahr. Oder warten Sie, bis ich fünf Minuten Zeit habe, um meinen Kopf um den Rest zu bekommen. Wenn Sie don39t Elemente aus Ihrem Ringpuffer konsumieren, dann you39re halten sie erreichbar und verhindert, dass sie ausgelöst werden. Dies kann offensichtlich einen nachteiligen Effekt auf den Durchsatz und die Latenz des Garbage Collectors haben. Das Schreiben von Referenzen in verschiedene Orte in Ihrem Ringpuffer verursacht die Schreibbarriere, was auch den Durchsatz und die Latenz nachteilig beeinflussen kann. Ich frage mich, was die Kompromisse über diese Nachteile und wenn sie ins Spiel kommen. Im Hinblick auf die Nutzung von Speicher, keine echten Trade Offs werden durch den Disruptor. Im Gegensatz zu einer Warteschlange, haben Sie eine Wahl, wie man Speicher verwenden. Wenn die Lösung ein weiches Echtzeit-System ist, ist die Reduzierung der GC-Pausen von größter Bedeutung. Daher können Sie die Einträge im Ringpuffer, z. B. Kopieren von Byte-Arrays zu und von Netzwerk-IO-Puffern in den und aus dem Ringpuffer (unser gängiges Anwendungsmuster). Da die von dem System verwendete Speichermenge statisch bleibt, verringert sich die Häufigkeit der Speicherbereinigung. Es ist auch möglich, einen Eintrag zu implementieren, der einen Verweis auf ein unveränderliches Objekt enthält. Jedoch kann es in dieser Situation notwendig sein, dass der Verbraucher das Nachrichtenobjekt auf Null setzt, um die Menge an Speicher zu reduzieren, die von Eden gefördert werden muss. Daher ist ein wenig mehr Aufwand erforderlich, damit der Programmierer die passende Lösung baut. Wir glauben, dass die Flexibilität gerechtfertigt diese kleine zusätzliche Anstrengung. In Anbetracht der Schreibbarriere ist das primäre Ziel des Disruptors, Meldungen zwischen Threads zu übermitteln. Wir machen keine Absprachen hinsichtlich der Bestellung oder Konsistenz, daher ist es notwendig, Speicherbarrieren an den entsprechenden Stellen zu verwenden. Wir haben unser Möglichstes getan, um das auf ein Minimum zu beschränken. Allerdings sind wir viele Male schneller als die beliebten Alternativen, da die meisten von ihnen verwenden Schlösser Konsistenz bieten. Wie verhält sich dieser Ansatz mit dem Pool-Ansatz und anderen Ansätzen, die hier verwendet werden: cacm. acm. orgmagazines20113105308-data-Strukturen-in-der-multicore-agefulltext Warum nicht einen Pool anstelle einer Warteschlange verwenden Ist die LIFO-Anforderung wesentlich Leider kann ich nicht lesen Diesen Artikel, weil ich don39t ein Konto an dieser Stelle haben. FIFO (nicht LIFO) ist absolut notwendig - unser Austausch hängt von der vorhersagbaren Reihenfolge ab, und wenn Sie die gleichen Ereignisse in sie spielen, erhalten Sie immer das gleiche Ergebnis. Der Disruptor stellt diese Reihenfolge sicher, ohne die Leistungsstörungen zu beachten, die normalerweise mit FIFO-Strukturen verbunden sind. Flying Frog Consultancy sagte, wenn Sie don39t verbrauchen Elemente aus Ihrem Ring Puffer dann you39re halten sie erreichbar und verhindert, dass sie ausgelöst. Dies kann offensichtlich einen nachteiligen Effekt auf den Durchsatz und die Latenz der Garbage Collector. quot Der ganze Punkt ist, nicht auf den Garbage Collector aufrufen. Das Disruptor-Muster erlaubt die Datenübertragung zwischen CPU39s und dem theoretischen Maximum der Hardware - es ist gut durchdachtes I39m neues zum Disruptor-Muster. Ich habe eine sehr grundlegende Frage. Wie füge ich dem Ringpuffer Nachrichten aus einem Multi Threaded Producer hinzu. Sollten die Add-Aufrufe zum Ringpuffer synchronisiert werden Im Allgemeinen ist das Ziel nicht, irgendetwas Multithreaded auszuführen. Produzenten und Verbraucher sollten einfädig sein. Aber Sie können mehr als einen Hersteller haben: mechanitis. blogspot201107dissecting-disruptor-write-to-ring. html - das ist ein etwas veraltetes Post, die Namenskonventionen haben sich geändert und die Producer-Barriere wird nun vom Ringpuffer verwaltet Ich denke, das könnte ein guter Ort sein, um darüber nachzudenken, wie Sie Ihr Problem zu lösen. Vielen Dank für den Artikel. Ich bin nicht sicher, ob ich es verstehe, aber das Konzept des Festhaltens an der Erinnerung und Wiederverwendung bereits zugewiesenen Gegenstände zu vermeiden GC Pausen scheint nicht neu zu sein. Wie unterscheidet sich der Ringpuffer von einem Objektpool? Das Vermeiden von GC ist nicht das Hauptziel des RingBuffers, obwohl es zur Geschwindigkeit des Disruptors beiträgt. Die interessanten Eigenschaften des RingBuffers sind, dass it39s FIFO, und es ermöglicht einige wirklich nettes Batching, wenn Sie daraus lesen. Der RingBuffer ist nicht die geheime Sauce in der Disruptor39s-Performance, tatsächlich in der aktuellen Version des Disruptors braucht man ihn überhaupt nicht. Es lohnt sich zu bemerken, dass es nichts neues in der Disruptor überhaupt, in der Tat viele der Ideen gibt es seit Jahren. Aber ich glaube nicht, dass es irgendwelche anderen Frameworks in Java, die zusammen diese Konzepte auf diese Weise zu geben, die Art der Leistung, die wir sehen, wenn die Verwendung der Disruptor. Hallo Trisha, Von ein paar Tagen entdeckte ich die LMAX-Architektur und Disruptor auch, It39s nicht so klar, wie genau die Verbraucher die Nachrichten von RingBuffer extrahieren und wie genau ein Verbraucher, zum Beispiel C1, weiß, welche Nachrichten dafür sind und nicht für Andere Verbraucher, C2. Danke Sorin. Tatsächlich sind die Nachrichten für beide Verbraucher. Das Standardverhalten ist, dass alle Konsumenten (oder EventHandler, wie sie jetzt sind) alle Nachrichten im RingBuffer lesen. Wenn Sie verschiedene Arten von Ereignissen, die von verschiedenen Verbrauchern behandelt werden, haben, dann ist es an den Verbraucher zu entscheiden, ob das Ereignis zu ignorieren oder nicht. Also, wenn C1 alle blauen Meldungen und C2 Handles alle roten (über Vereinfachung natürlich) behandelt, dann muss C1 prüfen, es ist eine blaue Nachricht, bevor Sie fortfahren. In Bezug auf die Entnahme der Nachrichten - Sie don39t. Nachrichten live auf dem Ringpuffer, der von allen Konsumenten gelesen (und verarbeitet) wird, bis jeder Verbraucher getan hat, was er damit machen muss (dh jeder Verbraucher hat seine Sequenznummer auf mindestens diese Nummer erhöht), dann wird er erhalten Überschrieben, wenn der Ring wickelt. Wenn Sie etwas mit dieser Nachricht tun wollen, dann lesen Sie es einfach und tun, was Sie wollen mit ihm, auch wenn that39s es an einen anderen Disruptor oder einen anderen Teil des Systems. Hallo Trisha, Vielen Dank für diese und andere Präsentationen. Ich habe eine Frage zum Disruptor, die eher einfach ist. Die Konsumenten (Event-Prozessoren) sind nicht implementieren eine der Callable oder Runnable Schnittstellen implementieren sie EventHandler, Dann wie können sie parallel laufen, so zum Beispiel habe ich eine Disruptor-Implementierung, wo es ein Diamant-Muster wie dieses P1 - c1, c2, C3 - c4 - c5 Wo c1 bis c3 nach p1 parallel arbeiten können und C4 und C5 nach ihnen arbeiten. So konventionell I39d haben so etwas (mit P1 und C1-C5 ist runnablescallables) Aber im Falle des Disruptor keiner meiner Event-Handler implementieren Runnable oder Callable, so wie die Disruptor-Framework am Ende läuft sie parallel Nehmen Sie folgende sceanrio: Mein Verbraucher C2 benötigt, um einen Webservice-Aufruf für einige Annotation, um das Ereignis, In SEDA Ich kann 10 Threads für solche 10 C2-Anfragen für das Ziehen der Nachricht aus der Warteschlange machen Webservice Call und aktualisieren Sie die nächste SEDA-Warteschlange und das wird sicherzustellen, dass ich don39t Warten sequentiell auf eine Webdienstantwort für jede der 10 Anfragen, wo wie in diesem Fall mein Ereignisprozessor C2 (falls) die einzige Instanz sequentiell auf 10 C2-Anfragen warten würde. In Java wird beim Erstellen eines Arrays von Java-Objekten kein Speicher für die Objekte reserviert. Es reserviert nur Speicher für Verweise auf die Objekte. Wie funktioniert ein Array von Objektreferenzen dazu beitragen, CPU-Caching-Effizienz zu verbessern, weil die eigentlichen Objekte sind immer noch in der Heap You39re absolut richtig gestreut, weshalb im LMAX-Fall haben wir ein Array von Byte-Arrays, nicht ein Array von Objekten - zumindest für Die Hochleistungsinstanzen des Disruptors. Ein Array von Objektreferenzen ist immer noch wertvoll in vielen Fällen, aber wie Sie sagen, es doesn39t unbedingt geben Sie die Cache-Linie Affinität. Dies ist in den Google Group-Diskussionen (groups. googleforumforumlmax-disruptor) mehrfach aufgetaucht, ich glaube, Sie finden hier detailliertere Diskussionen. Ich weiß, ich bin sehr spät, aber ein Array von Byte-Arrays ist per Definition ein Array von Objekten. Bidimensionale Byte-Arrays don39t garantieren Lokalität, vor allem nach einem GC-Pass (die eindimensionalen Arrays, aus denen sich die bidimensionalen Objekte sind Objekte im Haufen, so dass sie sich bewegt). Lokalität innerhalb der eindimensionalen Byte-Arrays könnte beibehalten werden, aber nicht in dem gesamten bidimensionalen Array (d. h. it39s bewahrt intra-array, lost inter-array). Ich verbringe gerade einen guten Teil meines Tages, der durch Ihren disruptor geht und ich kann von Ihrem Beispiel die Hunderte von Millionen von Ops pro Sekunde und auch von der Darstellung 6 Million Trades pro Sekunde sehen. I39ve nur ein Beispiel mit dem Produzenten abrufen die Operation Anfrage aus einem Webservice mit 2 Verbrauchern eine für Marshalling und die andere für Business-Logik und mein Durchsatz ist etwas über 1000 OPS pro Sekunde Quelle (githubejosiahactivemq-vs-distruptor) Meine Frage tut irgendwelche von Ihre Metriken umfassen IO-Operationen von anderen Verbrauchern wie denen für (Journalling, Replikation, Serialisierung, etc.) Nein, die angegebenen Metriken sind nur für die Business-Logik, nicht für IO etc. Ich denke, wenn Sie die Google-Gruppe-History Sie finden, finden Sie mehr Spezifische Informationen darüber, was gemessen wurde und wie, diese Frage hat sich definitiv vor:


No comments:

Post a Comment