2009-06-27 8 views
9

Ich versuche, die Handhabung großer Datensätze mit mmap zu optimieren. Ein Dataset liegt im Gigabyte-Bereich. Die Idee bestand darin, die gesamte Datei in den Speicher zu kopieren, sodass mehrere Prozesse gleichzeitig an der Datenmenge arbeiten konnten (schreibgeschützt). Es funktioniert jedoch nicht wie erwartet.Linux/Perl mmap Leistung

Als einfachen Test mmap ich einfach die Datei (mit perl Sys :: Mmap-Modul, mit der "mmap" Sub, die ich glaube direkt auf die zugrunde liegende C-Funktion) und den Prozess schlafen. Der Code verbringt dabei mehr als eine Minute, bevor er vom mmap-Aufruf zurückkehrt, obwohl dieser Test nichts - nicht einmal einen Lesevorgang - aus der mmap-Datei ausführt.

Ich denke, vielleicht Linux benötigt die ganze Datei gelesen werden, wenn zuerst mmap'ed, so nachdem die Datei im ersten Prozess zugeordnet wurde (während es im Schlaf war), rief ich einen einfachen Test in einem anderen Prozess versuchte, die ersten paar Megabyte der Datei zu lesen.

Überraschenderweise scheint der zweite Prozess auch eine Menge Zeit zu benötigen, bevor er vom mmap-Aufruf zurückkehrt, etwa zur gleichen Zeit, zu der die Datei das erste Mal erstellt wird.

Ich habe sichergestellt, dass MAP_SHARED verwendet wird und dass der Prozess, der die Datei das erste Mal zugeordnet wurde, noch aktiv ist (dass es nicht beendet wurde und dass die mmap nicht zugeordnet wurde).

Ich erwartete eine mmapped Datei würde mir erlauben, mehrere Worker-Prozesse effektiven wahlfreien Zugriff auf die große Datei geben, aber wenn jeder mmap-Aufruf das Lesen der gesamten Datei zuerst erfordert, ist es ein bisschen schwieriger. Ich habe nicht mit lang andauernden Prozessen getestet, um zu sehen, ob der Zugriff nach der ersten Verzögerung schnell ist, aber ich habe erwartet, MAP_SHARED zu verwenden, und ein anderer separater Prozess wäre ausreichend.

Meine Theorie war, dass mmap mehr oder weniger sofort zurückkehren würde, und dass Linux die Blöcke mehr oder weniger auf Abruf laden würde, aber das Verhalten, das ich sehe, ist das Gegenteil, zeigt an, dass es das Lesen der gesamten Datei auf jedem erfordert Anruf nach mmap.

Irgendeine Idee, was ich falsch mache, oder wenn ich vollständig missverstanden habe, wie mmap funktionieren soll?

Antwort

15

Ok, habe das Problem gefunden. Wie vermutet, waren weder Linux noch Perl schuld. Zum Öffnen und die Datei zugreifen ich etwas wie folgt aus:

#!/usr/bin/perl 
# Create 1 GB file if you do not have one: 
# dd if=/dev/urandom of=test.bin bs=1048576 count=1000 
use strict; use warnings; 
use Sys::Mmap; 

open (my $fh, "<test.bin") 
    || die "open: $!"; 

my $t = time; 
print STDERR "mmapping.. "; 
mmap (my $mh, 0, PROT_READ, MAP_SHARED, $fh) 
    || die "mmap: $!"; 
my $str = unpack ("A1024", substr ($mh, 0, 1024)); 
print STDERR " ", time-$t, " seconds\nsleeping.."; 

sleep (60*60); 

Wenn Sie diesen Code testen, gibt es keine Verzögerungen, wie ich sie in meiner ursprünglichen Code gefunden und nach der minimalen Proben zu schaffen (immer das tun, rechts !) Der Grund wurde plötzlich offensichtlich.

Der Fehler war, dass ich in meinem Code den $mh Skalar als Griff behandelte, etwas, das leicht ist und leicht bewegt werden kann (lesen: Wert übergeben). Es stellt sich heraus, dass es sich tatsächlich um eine GB-lange Zeichenfolge handelt, definitiv nicht etwas, das Sie verschieben möchten, ohne einen expliziten Verweis (perl lingua für einen "Zeiger"/Handle-Wert) zu erstellen.Wenn Sie also in einem Hash oder ähnlichem speichern müssen, stellen Sie sicher, dass Sie \$mh speichern und es aufheben, wenn Sie es wie ${$hash->{mh}} verwenden müssen, typischerweise als erster Parameter in einem substr oder ähnlich.

+3

+1 für die Nachverfolgung mit einer detaillierten Erklärung. – RichieHindle

+3

Verwenden Sie 3 Arg-Form von open(). –

0

Das klingt überraschend. Warum nicht eine reine C-Version ausprobieren?

Oder versuchen Sie Ihren Code auf einer anderen OS/Perl-Version.

+0

Ich habe die perl OS-Schnittstelle angeschaut, und es ruft die C-Version mehr oder weniger direkt, aber wenn ich es nicht herausfinden werde ich wahrscheinlich auch eine C-Version testen. Wie für OS/Perl-Version habe ich auf zwei System getestet, beide x86_64. Eines ist Ubuntu 8.04.2 (Linux 2.6.24-22, Perl 5.8.8) und das andere Ubuntu 9.04 (Linux 2.6.28-13, Perl 5.10.0). Gleiches Verhalten.Das zweite System war ein Laptop, und ich kann definitiv bestätigen, dass eine ernsthafte Festplatte beteiligt ist, wenn mmap aus meinen Tests aufgerufen wird. –

8

Wenn Sie eine relativ neue Version von Perl haben, sollten Sie Sys :: Mmap nicht verwenden. Sie sollten PerlIOs mmap Schicht verwenden.

Können Sie den Code posten, den Sie verwenden?

+0

Stimmen Sie zu, die PerlIO-mmap-Ebene ist wahrscheinlich vorzuziehen, da sie es auch ermöglicht, dass derselbe Code mit/ohne mmap'ing ausgeführt wird, indem einfach das mmap-Attribut hinzugefügt/entfernt wird. Egal, ich habe das Problem gefunden, den Code gepostet, Problem gelöst. –

+0

Machen Sie dieses Problem bis zu 2GB gelöst. Für größere Dateien hat Perl immer noch Probleme, siehe meine andere Antwort dazu. –

+0

Funktioniert PerlIOs mmap-Ebene für den Zugriff auf einen Teil von/dev/mem read/write? – donaldh

3

Auf 32-Bit-Systemen ist der Adressraum für mmap() s ziemlich begrenzt (und variiert von Betriebssystem zu Betriebssystem). Seien Sie sich dessen bewusst, wenn Sie Dateien mit mehreren Gigabyte verwenden und nur auf einem 64-Bit-System testen. (ich würde dies in einem Kommentar zu schreiben habe es vorgezogen, aber ich habe noch nicht genug Rufpunkte)

+0

+1. Sieht nach einer gültigen Antwort aus, die die gestellte Frage an mich adressiert, also danke, dass du sie nicht als Kommentar gepostet hast. –

+0

Wie ich in meiner anderen Antwort geschrieben habe, gibt es auch auf 64-Bit-Systemen immer noch Probleme mit größeren Dateien (> 2 GB). Ihre Antwort ist jedoch korrekt. Ich bin schon 64 Bit auf all meinen Maschinen, sogar auf dem Laptop, also ist das kein Problem für mich. –

0

Siehe Wide Finder für Perl-Performance mit mmap. Aber es gibt eine große Falle. Wenn sich Ihr Datensatz auf klassischem HD befindet und Sie aus mehreren Prozessen lesen, können Sie leicht in wahlfreien Zugriff fallen und Ihr E/A wird auf inakzeptable Werte (20-40 mal) fallen.

+0

Was ich versuche zu tun ist zufälliger Zugriff durch Design von mehreren Prozessen, um sicherzustellen, dass nur die Teile der Datei, auf die am häufigsten zugegriffen wird, immer im Speicher vorhanden sind. Welches Muster würden Sie vorschlagen, wenn ein wahlfreier Zugriff von mehreren Prozessen und eine große Datei benötigt wird? –

+0

Wenn Sie * wirklich * einen zufälligen Zugriff auf eine große Datei benötigen, gibt es keine bessere Lösung. –

+0

Eine bessere Lösung besteht darin, eine Reihe von Leseanforderungen in Warteschlangen einzureihen, die durch ein kurzes Timeout begrenzt sind, und dann die Blöcke von der Festplatte in der optimalen Reihenfolge zu lesen, um die Suchzeiten zu minimieren. Ich bin nicht sicher, ob irgendwelche Dateisysteme das schon tun, ich glaube nicht, dass ZFS das tut. Es würde am besten mit vielen gleichzeitig laufenden Prozessen funktionieren, z. ein Webserver oder mit einer anderen IO-API. –

1

eine Sache, die Leistung helfen kann, ist die Verwendung von 'Madvise (2)'. wahrscheinlich am einfachsten über Inline :: C getan. Mit 'madvise' können Sie dem Kernel mitteilen, wie Ihr Zugriffsmuster aussehen wird (z. B. sequenziell, zufällig usw.).

0

Ok, hier ist ein weiteres Update. Die Verwendung von Sys :: Mmap oder PerlIOs ": mmap" -Attribut funktioniert in Perl, aber nur bis zu 2 GB (die magische 32-Bit-Grenze). Sobald die Datei mehr als 2 GB groß ist, werden folgende Probleme angezeigt:

Mit Sys :: Mmap und substr für den Zugriff auf die Datei scheint subtr nur ein 32-Bit-int für den Positionsparameter akzeptiert, auch auf Systemen, in denen Perl unterstützt 64 bit. Es gibt mindestens einen Fehler über sie geschrieben:

#62646: Maximum string length with substr

open(my $fh, "<:mmap", "bigfile.bin") verwenden, sobald die Datei größer als 2 GB ist, so scheint es, Perl wird entweder hängen/oder darauf bestehen, auf die gesamte Datei auf der ersten Lese lesen (nicht sicher, was, ich habe es nie lange genug ausgeführt, um zu sehen, ob es abgeschlossen ist), was zu einer tot langsamen Leistung führt.

Ich habe keine Abhilfe zu einer dieser beiden gefunden, und ich bin derzeit mit langsamen Datei (nicht mmaped) Operationen für die Arbeit an diesen Dateien fest. Wenn ich keine Problemumgehung finde, muss ich möglicherweise die Verarbeitung in C oder einer anderen höheren Programmiersprache implementieren, die die Unterstützung von großen Dateien unterstützt.

+1

versuchen, mmap von Sys :: Mmap direkt zu verwenden, um ein gleitendes Fenster im Skalar zu erstellen. –

+0

Danke, das ist sicherlich ein Workaround. Es würde notwendig sein, den Zeiger in der Datei zu verfolgen und bei Bedarf zu mappen/zu entmappen, was sich wahrscheinlich auf die Leistung auswirkt. Aber es ist wahrscheinlich immer noch schneller als eine direkte Datei-IO. –

+0

Hat ein Benchmarking durchgeführt, das bestätigt, dass dynamische Zuordnung/Unmapping mit einer Segmentgröße von 2 GB und Annahme von Segmentwechsel recht selten ist, ist die Geschwindigkeit 30-40% schneller bei Verwendung von mmap mit Unmap/Mapping als bei geraden Dateien mit 3 GB Datei. Bei einer 2-GB-Datei sind die Unterschiede geringer, aber ich vermute, dass dies daran liegt, dass mein Laptop den größten Teil der Datei während der zufälligen Zugriffe zwischenspeichert. Zumindest habe ich eine Lösung, die funktioniert, wenn auch nicht so sauber, wie ich es mir erhofft hätte. Keine Notwendigkeit für weitere Optimierung in diesem Stadium. –

0

Wenn ich mein eigenes Modul anschließen kann: Ich rate mit File::Map anstelle von Sys::Mmap. Es ist viel einfacher zu verwenden und ist weniger anfällig für Abstürze als Sys :: Mmap.

+0

Hier ist ein Vorschlag für ein neues, sehr nützliches Feature, basierend auf meiner Beobachtung von Perl in diesem Thread beschrieben (Memory-Mapped-Dateien arbeiten nur bis zu 2 GB); Wenn der Benutzer eine Datei größer als 2 GB abbildet, verwenden Sie einen segmentierten Ansatz mit einer "benutzerdefinierten" Lesefunktion, die bei Bedarf automatisch die Zuordnung ausbildet. Zumindest bis der 2 GB Perl "Bug" behoben ist .. –

0

Ihr Zugriff auf diese Datei sollte besser zufällig sein, um eine volle mmap zu rechtfertigen. Wenn Ihre Verwendung nicht gleichmäßig verteilt ist, sind Sie wahrscheinlich besser dran mit einem Suchvorgang, lesen Sie in einem frisch veräußerten Bereich und verarbeiten Sie diesen, kostenlos, spülen und wiederholen. Und arbeite mit Stücken von Vielfachen von 4k, sagen wir 64k.

Ich einmal Benchmark viele String-Pattern-Matching-Algorithmen. Die gesamte Datei zu mappen war langsam und sinnlos. Das Lesen in einen statischen 32k-Puffer war besser, aber immer noch nicht besonders gut. Das Lesen in frisch gemolkenen Chunks, das Verarbeiten und anschließende Loslassen ermöglicht es dem Kernel, unter der Haube Wunder zu wirken. Der Unterschied in der Geschwindigkeit war enorm, aber dann wieder Muster-Matching ist sehr schnell komplexitywise und mehr Nachdruck muss auf die Handhabung Effizienz gelegt werden, als vielleicht in der Regel benötigt wird.