2009-02-11 6 views
11

Ich unterstütze eine Legacy-Java-Anwendung, die flache Dateien (Nur-Text) für die Persistenz verwendet. Aufgrund der Art der Anwendung kann die Größe dieser Dateien 100 MB pro Tag erreichen, und häufig ist der begrenzende Faktor für die Anwendungsleistung Datei-IO. Derzeit verwendet die Anwendung eine einfache ol 'java.io.FileOutputStream, um Daten auf die Festplatte zu schreiben.Leistung/Stabilität einer Memory Mapped-Datei - Native oder MappedByteBuffer - vs. Plain ol 'FileOutputStream

Vor kurzem hatten wir mehrere Entwickler behaupten, dass die Verwendung von Speicher-Mapped-Dateien, implementiert in nativem Code (C/C++) und Zugriff über JNI, eine höhere Leistung bieten würde. FileOutputStream verwendet jedoch bereits native Methoden für seine Kernmethoden (z. B. write (byte [])), so dass es eine schwache Annahme ohne harte Daten oder zumindest anekdotische Beweise zu sein scheint.

Ich habe einige Fragen zu diesem Thema:

  1. Ist diese Behauptung wirklich wahr? Werden im Speicher abgebildete Dateien immer bieten schneller IO im Vergleich zu Java FileOutputStream?

  2. Ist die Klasse MappedByteBuffer von einem Filechannel zugegriffen über JNI die gleiche Funktionalität wie eine native Memory-Mapped-Datei Bibliothek bieten zugegriffen? Was ist MappedByteBuffer fehlt, was dazu führen könnte, dass Sie eine JNI-Lösung verwenden?

  3. Welche Risiken bestehen bei der Verwendung von Speicherkarten für Disk IO in einer Produktion Anwendung? Das heißt, Anwendungen , die kontinuierliche Uptime mit minimale Neustarts (einmal im Monat, max) haben. Real-Life-Anekdoten aus der Produktion Anwendungen (Java oder anders) bevorzugt.

Frage # 3 ist wichtig - ich selbst diese Frage teilweise durch das Schreiben einer „Spielzeug“ Anwendung, die Tests perf antworten konnte die verschiedenen oben beschriebenen Optionen IO verwenden, aber durch die Veröffentlichung SO Ich hoffe auf reale Anekdoten/Daten zum Kauen.

[EDIT] Klarstellung - jeden Tag der Operation erstellt die Anwendung mehrere Dateien in der Größe von 100 MB bis 1 Gig. Insgesamt könnte die Anwendung mehrere Gigs Daten pro Tag schreiben.

Antwort

4

Sie können die Vorgänge möglicherweise etwas beschleunigen, indem Sie untersuchen, wie Ihre Daten beim Schreiben gepuffert werden. Dies ist tendenziell anwendungsspezifisch, da Sie eine Vorstellung von den erwarteten Datenschreibmustern benötigen würden. Wenn Datenkonsistenz wichtig ist, gibt es hier Kompromisse.

Wenn Sie nur neue Daten von Ihrer Anwendung auf die Festplatte schreiben, wird die Speicherzuordnung wahrscheinlich nicht viel helfen. Ich sehe keinen Grund, warum Sie Zeit in eine benutzerdefinierte codierte native Lösung investieren möchten. Es scheint nur zu viel Komplexität für Ihre Anwendung, von dem, was Sie bisher bereitgestellt haben.

Wenn Sie sicher sind, dass Sie wirklich eine bessere I/O-Leistung benötigen - oder nur O-Leistung in Ihrem Fall, würde ich in eine Hardware-Lösung wie ein abgestimmtes Disk-Array schauen. Der Einsatz von mehr Hardware für das Problem ist aus wirtschaftlicher Sicht oft kosteneffizienter als die zeitoptimierte Software. Es ist auch normalerweise schneller zu implementieren und zuverlässiger.

Im Allgemeinen gibt es viele Fallstricke bei der Überoptimierung von Software.Sie werden neue Arten von Problemen in Ihre Anwendung einführen. Es könnte zu Speicherproblemen/GC-Thrashing kommen, die zu mehr Wartung/Tuning führen würden. Der schlimmste Teil ist, dass viele dieser Probleme schwer zu testen sind, bevor sie in Produktion gehen.

Wenn es meine App wäre, würde ich wahrscheinlich mit dem FileOutputStream mit einigen möglicherweise abgestimmte Pufferung bleiben. Danach würde ich die altehrwürdige Lösung verwenden, mehr Hardware darauf zu werfen.

+0

Wählte diese Antwort, um die Punkte herum zu verbreiten. Auch der Satz "oder nur O Leistung in Ihrem Fall" ist mir wirklich treu geblieben. – noahlz

+1

Yup, alles über den O. – Gary

1

Wie für Punkt 3 - wenn der Computer abstürzt und es irgendwelche Seiten gibt, die nicht auf Platte geleert wurden, dann sind sie verloren. Eine andere Sache ist die Verschwendung des Adressraumes - das Zuordnen einer Datei zum Speicher verbraucht Adressraum (und benötigt einen zusammenhängenden Bereich), und gut, auf 32-Bit-Maschinen ist es ein bisschen begrenzt. Aber du hast über 100MB gesagt - also sollte das kein Problem sein. Und noch eine Sache - die Größe der mmaped-Datei zu erweitern erfordert etwas Arbeit.

Übrigens, this SO discussion kann Ihnen auch einige Einblicke geben.

+0

Eigentlich 100 MB - also bis zu einem Gig pro Datei. Und einige Bereitstellungen der Anwendung haben mehrere solcher Dateien! Ich werde bearbeiten, um klarer zu sein. – noahlz

2

Aus meiner Erfahrung, Memory-Mapped-Dateien führen VIEL besser als einfacher Dateizugriff in Echtzeit und Persistenz Anwendungsfälle. Ich habe hauptsächlich mit C++ unter Windows gearbeitet, aber die Linux-Leistung ist ähnlich, und Sie planen ohnehin, JNI zu verwenden, also denke ich, dass es sich auf Ihr Problem bezieht.

Ein Beispiel für eine Persistenz-Engine, die auf einer Memory-Mapped-Datei basiert, finden Sie unter Metakit. Ich habe es in einer Anwendung verwendet, in der Objekte einfache Ansichten über gespeicherte Daten waren, die Engine kümmerte sich um das gesamte Mapping hinter den Kulissen. Dies war sowohl schnell als auch speichereffizient (zumindest im Vergleich zu herkömmlichen Ansätzen wie denen der vorherigen Version), und wir haben Commit/Rollback-Transaktionen kostenlos.

In einem anderen Projekt musste ich Multicast-Netzwerkanwendungen schreiben. Die Daten wurden in einer randomisierten Reihenfolge gesendet, um die Auswirkung von aufeinanderfolgenden Paketverlusten (kombiniert mit FEC und Blockierungsschemata) zu minimieren. Außerdem könnten die Daten durchaus den Adressraum überschreiten (Videodateien waren größer als 2 GB), so dass die Speicherzuweisung nicht in Frage kam. Auf der Serverseite wurden Dateiabschnitte bei Bedarf im Speicher zugeordnet und die Netzwerkschicht wählte direkt die Daten aus diesen Ansichten aus; Folglich war der Speicherverbrauch sehr gering. Auf der Empfängerseite gab es keine Möglichkeit, die Reihenfolge, in der die Pakete empfangen wurden, vorherzusagen. Daher musste die Anzahl der aktiven Ansichten der Zieldatei begrenzt bleiben, und die Daten wurden direkt in diese Ansichten kopiert. Wenn ein Paket in einen nicht zugeordneten Bereich eingefügt werden musste, wurde die älteste Ansicht nicht zugeordnet (und schließlich vom System in die Datei gespült) und durch eine neue Ansicht im Zielbereich ersetzt. Die Leistungen waren hervorragend, vor allem, weil das System bei der Übertragung von Daten als Hintergrundaufgabe hervorragende Arbeit geleistet hat und Echtzeitanforderungen problemlos erfüllt werden konnten.

Seitdem bin ich davon überzeugt, dass selbst das beste fein ausgearbeitete Softwareschema die Standard-E/A-Richtlinie des Systems mit Speicherabbilddatei nicht schlagen kann, weil das System mehr als Benutzerraumanwendungen darüber weiß, wann und wie Daten gespeichert werden müssen geschrieben sein. Außerdem ist es wichtig zu wissen, dass die Speicherzuordnung ein Muss ist, wenn große Daten verarbeitet werden, da die Daten nie zugewiesen werden (daher Speicher verbrauchen), sondern dynamisch in den Adressraum gemapped und vom virtuellen Speichermanager des Systems verwaltet werden immer schneller als der Haufen. So nutzt das System den Speicher immer optimal und überträgt Daten, wann immer es nötig ist, hinter die Rückseite der Anwendung, ohne sie zu beeinflussen.

Ich hoffe, es hilft.

5

Speicherzugewiesene E/A lassen Ihre Festplatten nicht schneller laufen (!). Für den linearen Zugriff scheint es ein wenig sinnlos.

Ein NIO-gemappter Puffer ist die reale Sache (üblicher Vorbehalt gegenüber jeder vernünftigen Implementierung).

Wie bei anderen direkt zugewiesenen NIO-Puffern sind die Puffer kein normaler Speicher und werden nicht so effizient wie GCed behandelt. Wenn Sie viele davon erstellen, stellen Sie möglicherweise fest, dass Sie keinen Speicher-/Adressraum mehr haben, ohne den Java-Heapspeicher zu verlassen. Dies ist offensichtlich eine Sorge bei lang laufenden Prozessen.

+0

Die Anwendung schreibt auf Festplatte * far * häufiger (> 99%) als sie liest. Könnten Sie bitte erläutern, was Sie unter "Für den linearen Zugang scheint es ein bisschen sinnlos" zu verstehen - gilt es für Anhänge Operationen? – noahlz

+0

Append-Operationen sind linear (das Dateisystem kann die Datei fragmentieren, aber das sollte ein kleines Problem sein). –

0

Wenn Sie weniger Bytes schreiben, wird es schneller. Was ist, wenn Sie es durch gzipoutputstream gefiltert haben, oder was, wenn Sie Ihre Daten in ZipFiles oder JarFiles geschrieben haben?

+0

Dann handeln Sie den Overhead von IO-Operationen für den Overhead der Codierung/Decodierung der Daten. Es würde einige Experimente erfordern, um zu sehen, ob dies möglich ist. – noahlz

0

Wie oben erwähnt, verwenden Sie NIO (a.k.a. new IO). Es gibt auch eine neue, neue IO, die herauskommt.

Die richtige Verwendung einer RAID-Festplatte Lösung würde Ihnen helfen, aber das wäre ein Schmerz.

Ich mag die Idee, die Daten zu komprimieren. Geh für den gzipoutputstream Kumpel! Das würde Ihren Durchsatz verdoppeln, wenn die CPU mithalten kann. Es ist wahrscheinlich, dass Sie die Standard-Doppelkern-Maschinen nutzen können, wie?

-Stosh

1

Ich habe ein study, wo ich die Schreibleistung zu einem rohen ByteBuffer gegen die Schreibleistung auf einen MappedByteBuffer vergleichen. Memory-Mapped-Dateien werden vom Betriebssystem unterstützt und ihre Schreiblatenzen sind sehr gut, wie Sie in meinen Benchmark-Zahlen sehen können. Die Ausführung von synchronen Schreibvorgängen über einen FileChannel ist ungefähr 20-mal langsamer und deshalb führen die Benutzer ständig eine asynchrone Protokollierung durch. In meiner Studie gebe ich auch ein Beispiel, wie asynchrone Protokollierung durch eine blockierungsfreie und müllfreie Warteschlange für ultimative Leistung sehr nahe an einem rohen ByteBuffer implementieren.

+0

Mein Befund war, dass ich regelmäßig 'force() 'aufrufen musste, um sicherzustellen, dass meine Änderungen in die Datei gelangten. Daher war es langsamer. Natürlich, das war vor einigen Jahren ... – noahlz

+0

Sie müssen nicht force() aufrufen, außer wenn Sie sich vor einem Absturz des Betriebssystems schützen möchten. – TraderJoeChicago