Frage

Ich arbeite an einem Programm, das Dateien mit einer Größe von möglicherweise 100 GB oder mehr verarbeitet.Die Dateien enthalten Sätze von Datensätzen variabler Länge.Ich habe eine erste Implementierung in Betrieb genommen und versuche nun, die Leistung zu verbessern, insbesondere I/O effizienter durchzuführen, da die Eingabedatei viele Male gescannt wird.

Gibt es eine Faustregel für die Verwendung? mmap() im Vergleich zum Einlesen von Blöcken über C++ fstream Bibliothek?Was ich tun möchte, ist, große Blöcke von der Festplatte in einen Puffer zu lesen, vollständige Datensätze aus dem Puffer zu verarbeiten und dann weitere zu lesen.

Der mmap() Der Code könnte seitdem möglicherweise sehr chaotisch werden mmap'd-Blöcke müssen auf seitengroßen Grenzen liegen (nach meinem Verständnis) und Datensätze könnten möglicherweise über Seitengrenzen hinweg liegen.Mit fstreams, ich kann einfach zum Anfang eines Datensatzes suchen und erneut mit dem Lesen beginnen, da wir nicht darauf beschränkt sind, Blöcke zu lesen, die an seitengroßen Grenzen liegen.

Wie kann ich mich zwischen diesen beiden Optionen entscheiden, ohne vorher eine vollständige Implementierung zu schreiben?Alle Faustregeln (z. B. mmap() ist 2x schneller) oder einfache Tests?

War es hilfreich?

Lösung

Ich habe versucht, das letzte Wort zur MMAP-/Leseleistung unter Linux zu finden, und bin auf einen schönen Beitrag gestoßen (Verknüpfung) auf der Linux-Kernel-Mailingliste.Es stammt aus dem Jahr 2000, daher gab es seitdem viele Verbesserungen an E/A und virtuellem Speicher im Kernel, aber es erklärt gut den Grund dafür mmap oder read könnte schneller oder langsamer sein.

  • Ein Aufruf an mmap hat mehr Overhead als read (so wie epoll hat mehr Overhead als poll, was mehr Overhead hat als read).Das Ändern der Zuordnungen des virtuellen Speichers ist auf manchen Prozessoren ein recht kostspieliger Vorgang, aus den gleichen Gründen wie der Wechsel zwischen verschiedenen Prozessen.
  • Das IO-System kann den Festplatten-Cache bereits nutzen. Wenn Sie also eine Datei lesen, treffen Sie auf den Cache oder verpassen ihn, egal welche Methode Sie verwenden.

Jedoch,

  • Speicherzuordnungen sind bei wahlfreiem Zugriff im Allgemeinen schneller, insbesondere wenn Ihre Zugriffsmuster spärlich und unvorhersehbar sind.
  • Speicherkarten ermöglichen es Ihnen halten Verwenden Sie Seiten aus dem Cache, bis Sie fertig sind.Das bedeutet, dass die Seiten weiterhin zwischengespeichert werden, wenn Sie eine Datei über einen längeren Zeitraum intensiv nutzen, sie dann schließen und erneut öffnen.Mit read, Ihre Datei wurde möglicherweise vor langer Zeit aus dem Cache geleert.Dies gilt nicht, wenn Sie eine Datei verwenden und diese sofort verwerfen.(Wenn Sie es versuchen mlock Seiten abzuspeichern, nur um sie im Cache zu behalten, versuchen Sie, den Festplatten-Cache auszutricksen, und diese Art von Dummheit trägt nur selten zur Systemleistung bei.
  • Das direkte Lesen einer Datei ist sehr einfach und schnell.

Die Diskussion über mmap/read erinnert mich an zwei andere Leistungsdiskussionen:

  • Einige Java-Programmierer waren schockiert, als sie feststellten, dass nicht blockierende I/O oft langsamer sind als blockierende I/O, was durchaus Sinn macht, wenn man weiß, dass nicht blockierende I/O mehr Systemaufrufe erfordert.

  • Einige andere Netzwerkprogrammierer waren schockiert, als sie das erfuhren epoll ist oft langsamer als poll, was durchaus Sinn macht, wenn man sich mit der Verwaltung auskennt epoll erfordert mehr Systemaufrufe.

Abschluss: Verwenden Sie Speicherkarten, wenn Sie zufällig auf Daten zugreifen, diese über einen längeren Zeitraum aufbewahren oder wissen, dass Sie sie mit anderen Prozessen teilen können (MAP_SHARED ist nicht sehr interessant, wenn es kein tatsächliches Teilen gibt).Lesen Sie Dateien normal, wenn Sie nacheinander auf die Daten zugreifen, oder verwerfen Sie sie nach dem Lesen.Und wenn eine der beiden Methoden Ihr Programm weniger komplex macht, tun Sie es Das.Für viele reale Fälle gibt es keine sichere Möglichkeit zu zeigen, dass eine Lösung schneller ist, ohne Ihre tatsächliche Anwendung zu testen und NICHT einen Benchmark.

(Es tut mir leid, dass ich diese Frage nicht beantwortet habe, aber ich habe nach einer Antwort gesucht und diese Frage tauchte immer wieder ganz oben in den Google-Ergebnissen auf.)

Andere Tipps

Der Hauptaufwand für die Leistung wird durch Festplatten-I/O entstehen.„mmap()“ ist sicherlich schneller als istream, aber der Unterschied ist möglicherweise nicht spürbar, da die Festplatten-E/A Ihre Laufzeiten dominiert.

Ich habe das Codefragment von Ben Collins (siehe oben/unten) ausprobiert, um seine Behauptung zu testen, dass „mmap()“ ist Weg schneller“ und konnte keinen messbaren Unterschied feststellen.Siehe meine Kommentare zu seiner Antwort.

Das würde ich auf jeden Fall tun nicht Ich empfehle, jeden Datensatz der Reihe nach einzeln zuzuordnen, es sei denn, Ihre „Datensätze“ sind riesig – das wäre furchtbar langsam, würde zwei Systemaufrufe für jeden Datensatz erfordern und möglicherweise die Seite aus dem Festplattenspeicher-Cache verlieren …

In Ihrem Fall denke ich, dass mmap(), istream und die Low-Level-Aufrufe open()/read() alle ungefähr gleich sein werden.In diesen Fällen würde ich mmap() empfehlen:

  1. Es gibt einen wahlfreien Zugriff (nicht sequentiell) innerhalb der Datei UND
  2. Das Ganze passt bequem in den Speicher ODER es gibt eine Referenzlokalität innerhalb der Datei, so dass bestimmte Seiten ein- und andere Seiten zugeordnet werden können.Auf diese Weise nutzt das Betriebssystem den verfügbaren RAM optimal aus.
  3. ODER wenn mehrere Prozesse dieselbe Datei lesen/bearbeiten, dann ist mmap() fantastisch, da alle Prozesse dieselben physischen Seiten verwenden.

(Übrigens – ich liebe mmap()/MapViewOfFile()).

mmap ist Weg Schneller.Sie könnten einen einfachen Benchmark schreiben, um es sich selbst zu beweisen:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
  in.read(data, 0x1000);
  // do something with data
}

gegen:

const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;

int fd = open("filename.bin", O_RDONLY);

while (off < file_size)
{
  data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
  // do stuff with data
  munmap(data, page_size);
  off += page_size;
}

Offensichtlich lasse ich Details weg (z. B. wie Sie feststellen können, wann Sie das Ende der Datei erreichen, falls Ihre Datei kein Vielfaches von ist). page_size, zum Beispiel), aber es sollte eigentlich nicht viel komplizierter sein.

Wenn Sie können, können Sie versuchen, Ihre Daten in mehrere Dateien aufzuteilen, die ganz statt in Teilen mit mmap() bearbeitet werden können (was viel einfacher ist).

Vor ein paar Monaten hatte ich eine unausgegorene Implementierung einer Sliding-Window-mmap()-ed-Stream-Klasse für boost_iostreams, aber niemand kümmerte sich darum und ich war mit anderen Dingen beschäftigt.Leider habe ich vor ein paar Wochen ein Archiv mit alten, unvollendeten Projekten gelöscht, und das war eines der Opfer :-(

Aktualisieren:Ich sollte auch den Vorbehalt hinzufügen, dass dieser Benchmark unter Windows ganz anders aussehen würde, da Microsoft einen praktischen Datei-Cache implementiert hat, der das meiste von dem erledigt, was Sie ursprünglich mit mmap tun würden.Das heißt, für Dateien, auf die häufig zugegriffen wird, könnten Sie einfach std::ifstream.read() ausführen und es wäre so schnell wie mmap, da der Dateicache bereits eine Speicherzuordnung für Sie durchgeführt hätte und es transparent ist.

Letztes Update:Schaut mal, Leute:Über viele verschiedene Plattformkombinationen von Betriebssystemen und Standardbibliotheken sowie Festplatten- und Speicherhierarchien hinweg kann ich nicht mit Sicherheit sagen, dass es sich um einen Systemaufruf handelt mmap, als Blackbox betrachtet, wird immer wesentlich schneller sein als read.Das war nicht unbedingt meine Absicht, auch wenn meine Worte so ausgelegt werden könnten. Letztendlich ging es mir darum, dass speicherzugeordnete E/A im Allgemeinen schneller ist als bytebasierte E/A;das ist immer noch wahr.Wenn Sie experimentell feststellen, dass es keinen Unterschied zwischen den beiden gibt, dann ist die einzige Erklärung, die mir vernünftig erscheint, die, dass Ihre Plattform die Speicherzuordnung im Verborgenen auf eine Weise implementiert, die sich positiv auf die Leistung von Aufrufen von auswirkt read.Die einzige Möglichkeit, absolut sicher zu sein, dass Sie speicherzugeordnete E/A portabel verwenden, ist die Verwendung von mmap.Wenn Ihnen die Portabilität egal ist und Sie sich auf die besonderen Eigenschaften Ihrer Zielplattformen verlassen können, dann ist die Verwendung von read geeignet sein, ohne messbare Leistungseinbußen hinnehmen zu müssen.

Bearbeiten, um die Antwortliste zu bereinigen:@jbl:

Das Siebfenster MMAP klingt interessant.Kannst du ein bisschen mehr darüber sagen?

Sicher – ich habe eine C++-Bibliothek für Git geschrieben (eine libgit++, wenn Sie so wollen) und bin auf ein ähnliches Problem gestoßen:Ich musste in der Lage sein, große (sehr große) Dateien zu öffnen, ohne dass die Leistung zu stark beeinträchtigt wurde (wie es bei anderen der Fall wäre). std::fstream).

Boost::Iostreams hat bereits eine mapped_file-Quelle, aber das Problem war, dass dies der Fall war mmapganze Dateien pingen, was Sie auf 2^(Wortgröße) beschränkt.Auf 32-Bit-Rechnern sind 4 GB nicht groß genug.Das ist nicht unvernünftig zu erwarten .pack Dateien in Git, die viel größer werden, also musste ich die Datei in Blöcken lesen, ohne auf normale Datei-E/A zurückgreifen zu müssen.Unter der Decke von Boost::Iostreams, Ich habe eine Quelle implementiert, die mehr oder weniger eine andere Sicht auf die Interaktion zwischen ihnen darstellt std::streambuf Und std::istream.Sie könnten auch einen ähnlichen Ansatz ausprobieren, indem Sie einfach erben std::filebuf in ein mapped_filebuf und ebenso erben std::fstream hinein a mapped_fstream.Es ist die Interaktion zwischen den beiden, die schwierig hinzubekommen ist. Boost::Iostreams hat einen Teil der Arbeit für Sie erledigt und bietet auch Hooks für Filter und Ketten, daher dachte ich, dass es sinnvoller wäre, es auf diese Weise zu implementieren.

Hier gibt es bereits viele gute Antworten, die viele der wichtigsten Punkte abdecken, daher füge ich nur ein paar Probleme hinzu, die oben nicht direkt angesprochen wurden.Das heißt, diese Antwort sollte nicht als eine Zusammenfassung der Vor- und Nachteile betrachtet werden, sondern eher als Ergänzung zu anderen Antworten hier.

mmap scheint wie Magie

Nehmen wir den Fall, dass die Datei bereits vollständig zwischengespeichert ist1 als Grundlinie2, mmap könnte ziemlich ähnlich aussehen Magie:

  1. mmap Es ist nur ein Systemaufruf erforderlich, um (möglicherweise) die gesamte Datei zuzuordnen. Danach sind keine weiteren Systemaufrufe erforderlich.
  2. mmap erfordert keine Kopie der Dateidaten vom Kernel in den Benutzerbereich.
  3. mmap ermöglicht Ihnen den Zugriff auf die Datei „als Speicher“, einschließlich der Verarbeitung mit allen erweiterten Tricks, die Sie gegen den Speicher anwenden können, wie etwa der automatischen Vektorisierung des Compilers, SIMD Intrinsics, Prefetching, optimierte In-Memory-Parsing-Routinen, OpenMP usw.

Für den Fall, dass sich die Datei bereits im Cache befindet, scheint es unschlagbar:Sie greifen einfach direkt auf den Kernel-Seiten-Cache als Speicher zu und schneller geht es nicht.

Nun, das kann es.

mmap ist eigentlich keine Magie, weil...

mmap funktioniert immer noch pro Seite

Ein primärer versteckter Preis von mmap vs read(2) (was wirklich der vergleichbare Systemaufruf auf Betriebssystemebene ist Leseblockaden) ist das mit mmap Sie müssen für jede 4K-Seite im Benutzerbereich „etwas Arbeit“ leisten, auch wenn sie möglicherweise durch den Seitenfehlermechanismus ausgeblendet wird.

Als Beispiel eine typische Implementierung, die einfach ist mmapDie gesamte Datei muss fehlerhaft sein, also 100 GB/4K = 25 Millionen Fehler, um eine 100-GB-Datei zu lesen.Nun, das werden sie sein kleinere Mängel, aber 25 Milliarden Seitenfehler werden immer noch nicht superschnell sein.Die Kosten für einen geringfügigen Fehler liegen im besten Fall wahrscheinlich im 100-Nano-Bereich.

mmap ist stark von der TLB-Leistung abhängig

Jetzt können Sie bestehen MAP_POPULATE Zu mmap um es anzuweisen, vor der Rückkehr alle Seitentabellen einzurichten, damit beim Zugriff darauf keine Seitenfehler auftreten.Dies hat nun das kleine Problem, dass es auch die gesamte Datei in den RAM einliest, der explodiert, wenn Sie versuchen, eine 100-GB-Datei zuzuordnen – aber ignorieren wir das vorerst3.Der Kernel muss es tun Arbeit pro Seite um diese Seitentabellen einzurichten (wird als Kernelzeit angezeigt).Dies führt letztendlich zu erheblichen Kosten in der mmap Ansatz, und es ist proportional zur Dateigröße (d. h. es wird nicht weniger wichtig, wenn die Dateigröße wächst)4.

Schließlich ist der Zugriff auf eine solche Zuordnung auch im Benutzerbereich nicht gerade kostenlos (im Vergleich zu großen Speicherpuffern, die nicht aus einer dateibasierten Datei stammen). mmap) – Selbst wenn die Seitentabellen einmal eingerichtet sind, führt jeder Zugriff auf eine neue Seite konzeptionell zu einem TLB-Fehler.Seit mmapWenn Sie eine Datei verwenden, indem Sie den Seitencache und ihre 4K-Seiten verwenden, entstehen Ihnen diese Kosten wiederum 25 Millionen Mal für eine 100-GB-Datei.

Nun hängen die tatsächlichen Kosten dieser TLB-Fehler stark von mindestens den folgenden Aspekten Ihrer Hardware ab:(a) Wie viele 4K-TLB-Entitäten haben Sie und wie funktioniert das übrige Übersetzungs-Caching? (b) Wie gut kommt Hardware-Prefetch mit dem TLB zurecht – kann Prefetch z. B. einen Pagewalk auslösen?(c) wie schnell und wie parallel die Page-Walking-Hardware ist.Auf modernen High-End-x86-Intel-Prozessoren ist die Page-Walking-Hardware im Allgemeinen sehr stark:Es gibt mindestens zwei parallele Page-Walker, ein Page-Walk kann gleichzeitig mit der fortgesetzten Ausführung erfolgen und Hardware-Prefetching kann einen Page-Walk auslösen.Die TLB-Auswirkungen auf a Streaming Die Leselast ist relativ gering – und eine solche Last verhält sich unabhängig von der Seitengröße oft ähnlich.Andere Hardware ist jedoch meist deutlich schlechter!

read() vermeidet diese Fallstricke

Der read() syscall, das im Allgemeinen den Aufrufen vom Typ „Block Read“ zugrunde liegt, die z. B. in C, C++ und anderen Sprachen angeboten werden, hat einen Hauptnachteil, der jedem bewusst ist:

  • Jeden read() Der Aufruf von N Bytes muss N Bytes vom Kernel in den Benutzerraum kopieren.

Andererseits werden die oben genannten Kosten größtenteils vermieden – Sie müssen nicht 25 Millionen 4K-Seiten im Benutzerbereich zuordnen.Normalerweise ist das möglich malloc einen einzelnen Puffer, einen kleinen Puffer im Benutzerbereich, und verwenden Sie diesen wiederholt für alle Ihre Zwecke read Anrufe.Auf der Kernel-Seite gibt es fast kein Problem mit 4K-Seiten oder TLB-Fehlern, da der gesamte RAM normalerweise linear mithilfe einiger sehr großer Seiten (z. B. 1-GB-Seiten auf x86) zugeordnet wird, sodass die zugrunde liegenden Seiten im Seitencache abgedeckt sind sehr effizient im Kernelraum.

Im Grunde haben Sie also den folgenden Vergleich, um festzustellen, was für einen einzelnen Lesevorgang einer großen Datei schneller ist:

Ist die zusätzliche Arbeit pro Seite damit verbunden? mmap Dieser Ansatz ist kostspieliger als die Arbeit pro Byte, die durch die Verwendung des Kopierens von Dateiinhalten vom Kernel in den Benutzerbereich entsteht read()?

Auf vielen Systemen sind sie tatsächlich annähernd ausgeglichen.Beachten Sie, dass jeder mit völlig unterschiedlichen Attributen der Hardware und des Betriebssystem-Stacks skaliert.

Insbesondere die mmap Der Ansatz wird relativ schneller, wenn:

  • Das Betriebssystem verfügt über eine schnelle Handhabung kleinerer Fehler und insbesondere Optimierungen für das Bulking kleinerer Fehler wie z. B. Fault-Around.
  • Das Betriebssystem hat eine gute MAP_POPULATE Implementierung, die große Karten effizient verarbeiten kann, wenn beispielsweise die zugrunde liegenden Seiten im physischen Speicher zusammenhängend sind.
  • Die Hardware verfügt über eine starke Seitenübersetzungsleistung, wie z. B. große TLBs, schnelle TLBs der zweiten Ebene, schnelle und parallele Seitenwanderer, gute Prefetch-Interaktion mit der Übersetzung usw.

...während read() Der Ansatz wird relativ schneller, wenn:

  • Der read() syscall hat eine gute Kopierleistung.Z.B. gut copy_to_user Leistung auf der Kernel-Seite.
  • Der Kernel verfügt über eine effiziente (im Vergleich zum Userland) Möglichkeit, Speicher abzubilden, z. B. indem er nur wenige große Seiten mit Hardwareunterstützung verwendet.
  • Der Kernel verfügt über schnelle Systemaufrufe und eine Möglichkeit, Kernel-TLB-Einträge über alle Systemaufrufe hinweg beizubehalten.

Die oben genannten Hardwarefaktoren variieren wild über verschiedene Plattformen hinweg, sogar innerhalb derselben Familie (z. B. innerhalb x86-Generationen und insbesondere in Marktsegmenten) und definitiv über Architekturen hinweg (z. B. ARM vs. x86 vs. PPC).

Auch die Betriebssystemfaktoren ändern sich ständig, wobei verschiedene Verbesserungen auf beiden Seiten zu einem großen Anstieg der relativen Geschwindigkeit bei dem einen oder anderen Ansatz führen.Eine aktuelle Liste enthält:

  • Hinzufügung der oben beschriebenen Fehlerumgehung, die wirklich hilft mmap Hülle ohne MAP_POPULATE.
  • Hinzufügung von Fast-Path copy_to_user Methoden in arch/x86/lib/copy_user_64.S, z. B. mit REP MOVQ wenn es schnell ist, hilft das wirklich read() Fall.

Update nach Spectre und Meltdown

Die Abhilfemaßnahmen für die Spectre- und Meltdown-Schwachstellen erhöhten die Kosten eines Systemaufrufs erheblich.Auf den Systemen, die ich gemessen habe, beliefen sich die Kosten für einen „Nichts tun“-Systemaufruf (der eine Schätzung des reinen Overheads des Systemaufrufs ist, abgesehen von der tatsächlich durch den Aufruf geleisteten Arbeit) auf durchschnittlich etwa 100 ns modernes Linux-System auf ca. 700 ns.Darüber hinaus ist je nach System die Seitentabellen-Isolation Der Fix speziell für Meltdown kann neben den direkten Systemaufrufkosten zusätzliche nachgelagerte Auswirkungen haben, da TLB-Einträge neu geladen werden müssen.

All dies ist ein relativer Nachteil für read() basierte Methoden im Vergleich zu mmap basierte Methoden, da read() Methoden müssen einen Systemaufruf für jede Datenmenge „Puffergröße“ durchführen.Sie können die Puffergröße nicht beliebig erhöhen, um diese Kosten zu amortisieren, da die Verwendung großer Puffer in der Regel eine schlechtere Leistung erbringt, da Sie die L1-Größe überschreiten und es daher ständig zu Cache-Fehlern kommt.

Andererseits mit mmap, Mit können Sie einen großen Speicherbereich zuordnen MAP_POPULATE und den effizienten Zugriff darauf, zum Preis von nur einem einzigen Systemaufruf.


1 Dies gilt mehr oder weniger auch für den Fall, dass die Datei zunächst nicht vollständig zwischengespeichert wurde, das OS-Read-Ahead jedoch gut genug ist, um den Eindruck zu erwecken, dass dies der Fall ist (d. h. die Seite wird normalerweise zum gewünschten Zeitpunkt zwischengespeichert). Es).Dies ist jedoch ein subtiles Problem, da die Art und Weise, wie das Vorauslesen funktioniert, oft sehr unterschiedlich ist mmap Und read Anrufe und können durch „Beratung“-Anrufe weiter angepasst werden, wie in beschrieben 2.

2 ...denn wenn die Datei ist nicht zwischengespeichert, wird Ihr Verhalten vollständig von IO-Bedenken dominiert, einschließlich der Frage, wie sympathisch Ihr Zugriffsmuster mit der zugrunde liegenden Hardware ist – und alle Ihre Bemühungen sollten darin bestehen, sicherzustellen, dass dieser Zugriff so sympathisch wie möglich ist, z. B.durch die Verwendung von madvise oder fadvise Aufrufe (und alle Änderungen auf Anwendungsebene, die Sie vornehmen können, um Zugriffsmuster zu verbessern).

3 Das könnte man beispielsweise umgehen, indem man sequentiell vorgeht mmaping in Fenstern kleinerer Größe, sagen wir 100 MB.

4 Tatsächlich stellt sich heraus, dass MAP_POPULATE Der Ansatz ist (mindestens eine Hardware/Betriebssystem-Kombination) nur geringfügig schneller, als ihn nicht zu verwenden, wahrscheinlich weil der Kernel ihn verwendet Fehlerumgehung - die tatsächliche Anzahl kleinerer Fehler reduziert sich also etwa um den Faktor 16.

Es tut mir leid, dass Ben Collins seinen Sliding Windows MMAP-Quellcode verloren hat.Das wäre schön, wenn man es in Boost hätte.

Ja, das Zuordnen der Datei ist viel schneller.Sie verwenden im Wesentlichen das virtuelle Speichersubsystem des Betriebssystems, um Speicher mit Festplatte zu verknüpfen und umgekehrt.Stellen Sie sich das so vor:Wenn die Entwickler des Betriebssystemkernels es schneller machen könnten, würden sie es tun.Denn dadurch geht fast alles schneller:Datenbanken, Bootzeiten, Programmladezeiten usw.

Der Schiebefenster-Ansatz ist wirklich nicht so schwierig, da mehrere zusammenhängende Seiten gleichzeitig zugeordnet werden können.Die Größe des Datensatzes spielt also keine Rolle, solange der größte Datensatz in den Speicher passt.Wichtig ist die Führung der Buchhaltung.

Wenn ein Datensatz nicht an einer getpagesize()-Grenze beginnt, muss Ihre Zuordnung auf der vorherigen Seite beginnen.Die Länge des zugeordneten Bereichs reicht vom ersten Byte des Datensatzes (ggf. abgerundet auf das nächste Vielfache von getpagesize()) bis zum letzten Byte des Datensatzes (aufgerundet auf das nächste Vielfache von getpagesize()).Wenn Sie mit der Verarbeitung eines Datensatzes fertig sind, können Sie die Zuordnung aufheben() und mit dem nächsten fortfahren.

Das alles funktioniert auch unter Windows einwandfrei mit CreateFileMapping() und MapViewOfFile() (und GetSystemInfo(), um SYSTEM_INFO.dwAllocationGranularity zu erhalten – nicht SYSTEM_INFO.dwPageSize).

mmap sollte schneller sein, aber ich weiß nicht wie viel.Es hängt sehr stark von Ihrem Code ab.Wenn Sie mmap verwenden, ist es am besten, die gesamte Datei auf einmal zu mmapen, das wird Ihnen das Leben viel einfacher machen.Ein potenzielles Problem besteht darin, dass Sie eine 64-Bit-Architektur benötigen, wenn Ihre Datei größer als 4 GB ist (oder in der Praxis die Grenze niedriger ist, oft 2 GB).Wenn Sie also eine 32-Umgebung verwenden, möchten Sie diese wahrscheinlich nicht verwenden.

Allerdings gibt es möglicherweise einen besseren Weg zur Leistungsverbesserung.Du sagtest Die Eingabedatei wird viele Male gescannt, wenn Sie es in einem Durchgang auslesen und dann damit fertig sind, könnte das möglicherweise viel schneller gehen.

Ich stimme zu, dass die Datei-E/A mit mmap schneller sein wird, aber während Sie den Code vergleichen, sollte das Gegenbeispiel nicht so sein etwas optimiert?

Ben Collins schrieb:

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

Ich würde vorschlagen, auch Folgendes zu versuchen:

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

Darüber hinaus können Sie auch versuchen, die Puffergröße auf die Größe einer Seite des virtuellen Speichers einzustellen, falls 0x1000 nicht der Größe einer Seite des virtuellen Speichers auf Ihrem Computer entspricht ...Meiner Meinung nach gewinnt die mmap-Datei-E/A immer noch, aber das sollte die Sache näher bringen.

Vielleicht sollten Sie die Dateien vorverarbeiten, damit sich jeder Datensatz in einer separaten Datei befindet (oder zumindest jede Datei eine mmap-fähige Größe hat).

Könnten Sie auch alle Verarbeitungsschritte für jeden Datensatz durchführen, bevor Sie mit dem nächsten fortfahren?Vielleicht würde das einen Teil des E/A-Overheads vermeiden?

Meiner Meinung nach entlastet die Verwendung von mmap() den Entwickler „nur“ davon, seinen eigenen Caching-Code schreiben zu müssen.In einem einfachen Fall des „genauen Durchlesens der Datei“ wird dies nicht schwer sein (obwohl Sie, wie mlbrock betont, die Speicherkopie immer noch im Prozessraum speichern), aber wenn Sie in der Datei hin und her gehen oder Ich glaube, dass die Kernel-Entwickler das Überspringen von Bits usw. getan haben wahrscheinlich Ich habe das Caching besser implementiert, als ich kann ...

Ich erinnere mich, dass ich vor Jahren eine riesige Datei mit einer Baumstruktur im Speicher abgelegt habe.Ich war erstaunt über die Geschwindigkeit im Vergleich zur normalen Deserialisierung, die viel Arbeit im Speicher erfordert, wie das Zuweisen von Baumknoten und das Setzen von Zeigern.Tatsächlich habe ich also einen einzelnen Aufruf mit MMAP (oder seinem Gegenstück unter Windows) mit vielen (vielen) Aufrufen an News- und Konstruktor -Anrufe mit Bediener vergleicht.Für solche Aufgaben ist mmap im Vergleich zur Deserialisierung unschlagbar.Natürlich sollte man sich hierfür den verschiebbaren Zeiger von Boosts ansehen.

Das klingt nach einem guten Anwendungsfall für Multithreading ...Ich denke, Sie könnten ganz einfach einen Thread so einrichten, dass er Daten liest, während die anderen sie verarbeiten.Das könnte eine Möglichkeit sein, die wahrgenommene Leistung dramatisch zu steigern.Nur ein Gedanke.

Ich denke, das Beste an mmap ist das Potenzial für asynchrones Lesen mit:

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

Das Problem ist, dass ich nicht die richtigen MAP_FLAGS finden kann, um einen Hinweis zu geben, dass dieser Speicher so schnell wie möglich aus der Datei synchronisiert werden sollte.Ich hoffe, dass MAP_POPULATE den richtigen Hinweis für mmap gibt (d. h.Es wird nicht versucht, alle Inhalte vor der Rückkehr vom Anruf zu laden, sondern dies erfolgt asynchron.mit feed_data).Zumindest liefert es mit diesem Flag bessere Ergebnisse, auch wenn im Handbuch angegeben ist, dass es seit 2.6.23 nichts mehr ohne MAP_PRIVATE macht.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top