Hier gibt es bereits viele gute Antworten, die viele der wichtigsten Punkte abdecken. Daher möchte ich nur einige Punkte hinzufügen, die ich nicht direkt oben angesprochen habe. Das heißt, diese Antwort sollte nicht als umfassend für die Vor- und Nachteile angesehen werden, sondern als Ergänzung zu anderen Antworten hier.
mmap scheint magisch
Wenn Sie den Fall, in dem die Datei bereits vollständig zwischengespeichert ist 1 als Basis 2 haben , mmap
als magisch erscheinen :
mmap
Es ist nur ein Systemaufruf erforderlich, um (möglicherweise) die gesamte Datei zuzuordnen. Danach sind keine weiteren Systemaufrufe erforderlich.
mmap
erfordert keine Kopie der Dateidaten vom Kernel in den User-Space.
mmap
Ermöglicht den Zugriff auf die Datei "als Speicher", einschließlich der Verarbeitung mit allen erweiterten Tricks, die Sie gegen den Speicher ausführen können, z. B. automatische 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 unmöglich zu sein: Sie greifen einfach direkt auf den Kernel-Seiten-Cache als Speicher zu und es kann nicht schneller werden.
Nun, das kann es.
mmap ist eigentlich keine Magie, weil ...
mmap arbeitet immer noch pro Seite
Ein primärer versteckter Preis von mmap
vs read(2)
(was eigentlich der vergleichbare Systemaufruf auf Betriebssystemebene zum Lesen von Blöcken ist ) besteht darin, dass mmap
Sie für jede 4K-Seite im Benutzerbereich "etwas Arbeit" erledigen müssen, auch wenn sie möglicherweise von der Seitenfehlermechanismus.
Zum Beispiel muss eine typische Implementierung, die nur mmap
die gesamte Datei enthält, einen Fehler verursachen, sodass 100 GB / 4K = 25 Millionen Fehler zum Lesen einer 100-GB-Datei vorliegen. Nun, dies werden kleinere Fehler sein , aber 25 Milliarden Seitenfehler werden immer noch nicht superschnell sein. Die Kosten für einen kleinen Fehler liegen wahrscheinlich im besten Fall bei 100 Nanos.
mmap hängt stark von der TLB-Leistung ab
Jetzt können Sie an übergeben MAP_POPULATE
, mmap
um anzuweisen, dass alle Seitentabellen eingerichtet werden sollen, bevor Sie zurückkehren, damit beim Zugriff keine Seitenfehler auftreten. Dies hat das kleine Problem, dass es auch die gesamte Datei in den Arbeitsspeicher liest, was explodieren wird, wenn Sie versuchen, eine 100-GB-Datei zuzuordnen - aber lassen Sie uns dies vorerst ignorieren 3 . Der Kernel muss pro Seite arbeiten , um diese Seitentabellen einzurichten (wird als Kernelzeit angezeigt). Dies ist ein erheblicher Kostenfaktor für den mmap
Ansatz und proportional zur Dateigröße (dh er wird mit zunehmender Dateigröße nicht weniger wichtig) 4 .
Selbst im Benutzerbereich ist der Zugriff auf eine solche Zuordnung nicht gerade kostenlos (im Vergleich zu großen Speicherpuffern, die nicht aus einer dateibasierten Zuordnung stammen mmap
). Selbst wenn die Seitentabellen eingerichtet sind, wird jeder Zugriff auf eine neue Seite ausgeführt. konzeptionell entsteht ein TLB-Fehler. Da das mmap
Erstellen einer Datei die Verwendung des Seitencaches und seiner 4K-Seiten bedeutet, fallen für eine 100-GB-Datei erneut 25 Millionen Mal Kosten an.
Nun hängen die tatsächlichen Kosten dieser TLB-Fehler stark von mindestens den folgenden Aspekten Ihrer Hardware ab: (a) wie viele 4K-TLB-Enties Sie haben und wie der Rest des Übersetzungs-Caching funktioniert (b) wie gut Hardware-Prefetch funktioniert mit dem TLB - kann zB Prefetch einen Seitenlauf auslösen? (c) wie schnell und wie parallel die Page-Walking-Hardware ist. Auf modernen High-End-x86-Intel-Prozessoren ist die Page-Walk-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. Daher ist die Auswirkung des TLB auf eine Streaming- Leselast relativ gering - und eine solche Last wird unabhängig von der Seitengröße häufig ähnlich ausgeführt. Andere Hardware ist jedoch normalerweise viel schlechter!
read () vermeidet diese Fallstricke
Der read()
Syscall, der im Allgemeinen den "Block Read" -Aufrufen zugrunde liegt, die z. B. in C, C ++ und anderen Sprachen angeboten werden, hat einen Hauptnachteil, den jeder kennt:
- Jeder
read()
Aufruf von N Bytes muss N Bytes vom Kernel in den Benutzerbereich kopieren.
Auf der anderen Seite werden die meisten der oben genannten Kosten vermieden - Sie müssen nicht 25 Millionen 4K-Seiten in den Benutzerbereich abbilden. Normalerweise können Sie malloc
einen einzelnen Puffer, einen kleinen Puffer im Benutzerbereich, verwenden und diesen wiederholt für alle Ihre read
Anrufe wiederverwenden . Auf der Kernelseite gibt es fast kein Problem mit 4K-Seiten oder TLB-Fehlern, da der gesamte RAM normalerweise linear mit einigen sehr großen Seiten (z. B. 1 GB Seiten auf x86) zugeordnet wird, sodass die zugrunde liegenden Seiten im Seitencache abgedeckt sind sehr effizient im Kernelraum.
Grundsätzlich haben Sie also den folgenden Vergleich, um festzustellen, welche für einen einzelnen Lesevorgang einer großen Datei schneller ist:
Ist die zusätzliche Arbeit pro Seite, die durch den mmap
Ansatz impliziert wird, teurer als die Arbeit pro Byte beim Kopieren von Dateiinhalten vom Kernel in den Benutzerbereich, die durch die Verwendung impliziert wird read()
?
Auf vielen Systemen sind sie tatsächlich ungefähr ausgeglichen. Beachten Sie, dass jeder mit völlig unterschiedlichen Attributen der Hardware und des Betriebssystemstapels skaliert.
Insbesondere wird der mmap
Ansatz relativ schneller, wenn:
- Das Betriebssystem verfügt über eine schnelle Behandlung kleinerer Fehler und insbesondere über Bulk-Optimierungen kleiner Fehler wie Fehlerumgehung.
- Das Betriebssystem verfügt über eine gute
MAP_POPULATE
Implementierung, mit der große Karten effizient verarbeitet werden können, wenn beispielsweise die zugrunde liegenden Seiten im physischen Speicher zusammenhängend sind.
- Die Hardware bietet eine starke Leistung bei der Seitenübersetzung, z. B. große TLBs, schnelle TLBs der zweiten Ebene, schnelle und parallele Page-Walker, eine gute Prefetch-Interaktion mit der Übersetzung usw.
... während der read()
Ansatz relativ schneller wird, wenn:
- Der
read()
Syscall hat eine gute Kopierleistung. ZB gute copy_to_user
Leistung auf der Kernelseite.
- Der Kernel verfügt über eine effiziente (im Verhältnis zum Benutzerland) Möglichkeit, Speicher zuzuordnen, z. B. indem nur wenige große Seiten mit Hardwareunterstützung verwendet werden.
- Der Kernel verfügt über schnelle Systemaufrufe und eine Möglichkeit, Kernel-TLB-Einträge über Systemaufrufe hinweg zu speichern.
Die Hardware - Faktoren , die oben variieren wild über verschiedene Plattformen hinweg, sogar innerhalb der gleichen Familie (zB innerhalb x86 Generationen und vor allem Marktsegmente) und auf jeden Fall über Architekturen (zB ARM vs x86 vs PPC).
Auch die OS-Faktoren ändern sich ständig, wobei verschiedene Verbesserungen auf beiden Seiten bei dem einen oder anderen Ansatz zu einem starken Anstieg der Relativgeschwindigkeit führen. Eine aktuelle Liste enthält:
- Hinzufügen der oben beschriebenen Fehlerbehebung, die den
mmap
Fall ohne wirklich hilft MAP_POPULATE
.
- Hinzufügen von Fast-Path-
copy_to_user
Methoden arch/x86/lib/copy_user_64.S
, z. B. REP MOVQ
wenn es schnell ist, was dem read()
Fall wirklich hilft .
Update nach Spectre und Meltdown
Die Abschwächung der Schwachstellen Spectre und Meltdown erhöhte die Kosten eines Systemaufrufs erheblich. Auf den Systemen, die ich gemessen habe, gingen die Kosten für einen Systemaufruf "nichts tun" (der eine Schätzung des reinen Overheads des Systemaufrufs darstellt, abgesehen von der tatsächlichen Arbeit, die durch den Aufruf ausgeführt wurde) von ungefähr 100 ns auf einen typischen Wert modernes Linux-System bis ca. 700 ns. Abhängig von Ihrem System kann der speziell für Meltdown festgelegte Seitentabellen-Isolations- Fix neben den direkten Systemaufrufkosten zusätzliche Downstream-Effekte 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
basierten Methoden, da read()
Methoden für jede Datenmenge mit "Puffergröße" einen Systemaufruf ausführen müssen. Sie können die Puffergröße nicht willkürlich erhöhen, um diese Kosten zu amortisieren, da die Verwendung großer Puffer normalerweise schlechter abschneidet, da Sie die L1-Größe überschreiten und daher ständig unter Cache-Fehlern leiden.
Auf der anderen Seite können Sie mit mmap
eine große Speicherregion mit MAP_POPULATE
und nur effizientem Zugriff auf Kosten eines einzigen Systemaufrufs abbilden.
1 Dies schließt mehr oder weniger auch den Fall ein, in dem die Datei zunächst nicht vollständig zwischengespeichert war, das Vorauslesen des Betriebssystems jedoch gut genug ist, um es so erscheinen zu lassen (dh die Seite wird normalerweise zu dem Zeitpunkt zwischengespeichert, zu dem Sie sich befinden will es). Dies ist jedoch ein subtiles Problem, da die Art und Weise, wie das Vorauslesen funktioniert, zwischen mmap
und read
Anrufen häufig sehr unterschiedlich ist und durch "Beratung" -Anrufe weiter angepasst werden kann, wie in 2 beschrieben .
2 ... denn wenn die Datei nicht zwischengespeichert wird, wird Ihr Verhalten vollständig von E / A-Bedenken dominiert, einschließlich der Sympathie Ihres Zugriffsmusters für die zugrunde liegende Hardware - und Sie sollten sich alle Mühe geben, um sicherzustellen, dass ein solcher Zugriff so sympathisch ist wie möglich, z. B. durch Verwendung von madvise
oder fadvise
Aufrufe (und welche Änderungen auf Anwendungsebene Sie vornehmen können, um die Zugriffsmuster zu verbessern).
3 Sie können dies umgehen, indem Sie beispielsweise mmap
Fenster kleinerer Größe, z. B. 100 MB , nacheinander eingeben.
4 Tatsächlich stellt sich heraus, dass der MAP_POPULATE
Ansatz (mindestens eine Kombination aus Hardware und Betriebssystem) nur geringfügig schneller ist als die Nichtverwendung , wahrscheinlich weil der Kernel eine Fehlerbehebung verwendet. Daher wird die tatsächliche Anzahl kleinerer Fehler um den Faktor 16 reduziert oder so.
mmap()
ist es 2-6 mal schneller als mit Syscalls, zread()
.