Bad Linux Memory Mapped Datei Leistung mit zufälligem Zugriff C ++ & Python

Beim Versuch, Speicher-abgebildete Dateien zu verwenden, um eine Multi-Gigabyte-Datei (ca. 13 GB) zu erstellen, lief ich in das, was ein Problem mit mmap () zu sein scheint. Die erste Implementierung wurde in c ++ auf Windows mit boost :: iostreams :: mapped_file_sink durchgeführt und alles war gut. Der Code wurde dann auf Linux ausgeführt und was war Minuten auf Windows wurde Stunden auf Linux.

Die beiden Maschinen sind Klone der gleichen Hardware: Dell R510 2.4GHz 8M Cache 16GB RAM 1TB Disk PERC H200 Controller.

Das Linux ist Oracle Enterprise Linux 6.5 mit dem 3.8 Kernel und g ++ 4.83.

Es gab einige Bedenken, dass es ein Problem mit der Boost-Bibliothek geben kann, also wurden Implementierungen mit boost :: interprocess :: file_mapping und wieder mit native mmap () durchgeführt. Alle drei zeigen das gleiche Verhalten. Die Windows- und Linux-Leistung steht auf einem bestimmten Punkt, wenn die Linux-Performance schlecht abfällt.

Voller Quellcode und Leistungsnummern sind unten verlinkt.

// C++ code using boost::iostreams void IostreamsMapping(size_t rowCount) { std::string outputFileName = "IoStreamsMapping.out"; boost::iostreams::mapped_file_params params(outputFileName); params.new_file_size = static_cast<boost::iostreams::stream_offset>(sizeof(uint64_t) * rowCount); boost::iostreams::mapped_file_sink fileSink(params); // NOTE: using this form of the constructor will take care of creating and sizing the file. uint64_t* dest = reinterpret_cast<uint64_t*>(fileSink.data()); DoMapping(dest, rowCount); } void DoMapping(uint64_t* dest, size_t rowCount) { inputStream->seekg(0, std::ios::beg); uint32_t index, value; for (size_t i = 0; i<rowCount; ++i) { inputStream->read(reinterpret_cast<char*>(&index), static_cast<std::streamsize>(sizeof(uint32_t))); inputStream->read(reinterpret_cast<char*>(&value), static_cast<std::streamsize>(sizeof(uint32_t))); dest[index] = value; } } 

Ein letzter Test wurde in Python durchgeführt, um dies in einer anderen Sprache zu reproduzieren. Der Absturz ist an der gleichen Stelle passiert, sieht also wie das gleiche Problem aus.

 # Python code using numpy import numpy as np fpr = np.memmap(inputFile, dtype='uint32', mode='r', shape=(count*2)) out = np.memmap(outputFile, dtype='uint64', mode='w+', shape=(count)) print("writing output") out[fpr[::2]]=fpr[::2] 

Für die c ++ – Tests haben Windows und Linux ähnliche Leistung bis zu rund 300 Millionen Int64s (mit Linux etwas schneller). Es sieht aus wie die Leistung fällt auf Linux um 3Gb (400 Millionen * 8 Bytes pro int64 = 3.2Gb) für C ++ und Python.

Ich weiß auf 32-Bit-Linux, dass 3Gb ist eine magische Grenze, aber bin nicht bewusst von ähnlichen Verhalten für 64-Bit-Linux.

Der Kern der Ergebnisse ist 1,4 Minuten für Windows 1.7 Stunden auf Linux bei 400 Millionen Int64s. Ich versuche tatsächlich, nahezu 1,3 Milliarden Int64s zuzuordnen.

Kann jemand erklären, warum es eine solche Trennung in der Leistung zwischen Windows und Linux gibt?

Jede Hilfe oder Anregungen wäre sehr dankbar!

LoadTest.cpp

Makefile

LoadTest.vcxproj

Aktualisiert mmap_test.py

Original mmap_test.py

Aktualisierte Ergebnisse Mit aktualisiertem Python-Code … Python-Geschwindigkeit jetzt vergleichbar mit C ++

Ursprüngliche Ergebnisse HINWEIS: Die Python-Ergebnisse sind veraltet

One Solution collect form web for “Bad Linux Memory Mapped Datei Leistung mit zufälligem Zugriff C ++ & Python”

Bearbeiten: Upgrade auf "richtige Antwort". Das Problem ist mit der Art und Weise, dass "schmutzige Seiten" von Linux behandelt werden. Ich möchte immer noch, dass mein System immer wieder schmutzige Seiten spült, also habe ich es nicht zulassen, dass es viele hervorragende Seiten hat. Aber zur gleichen Zeit kann ich zeigen, dass das ist, was los ist.

Ich habe das getan (mit "sudo -i"):

 # echo 80 > /proc/sys/vm/dirty_ratio # echo 60 > /proc/sys/vm/dirty_background_ratio 

Was gibt diese Einstellungen VM schmutzige Einstellungen:

 grep ^ /proc/sys/vm/dirty* /proc/sys/vm/dirty_background_bytes:0 /proc/sys/vm/dirty_background_ratio:60 /proc/sys/vm/dirty_bytes:0 /proc/sys/vm/dirty_expire_centisecs:3000 /proc/sys/vm/dirty_ratio:80 /proc/sys/vm/dirty_writeback_centisecs:500 

Das macht meine Benchmark so:

 $ ./a.out m64 200000000 Setup Duration 33.1042 seconds Linux: mmap64 size=1525 MB Mapping Duration 30.6785 seconds Overall Duration 91.7038 seconds 

Vergleiche mit "vor":

 $ ./a.out m64 200000000 Setup Duration 33.7436 seconds Linux: mmap64 size=1525 Mapping Duration 1467.49 seconds Overall Duration 1501.89 seconds 

Die diese VM-Dirty-Einstellungen hatten:

 grep ^ /proc/sys/vm/dirty* /proc/sys/vm/dirty_background_bytes:0 /proc/sys/vm/dirty_background_ratio:10 /proc/sys/vm/dirty_bytes:0 /proc/sys/vm/dirty_expire_centisecs:3000 /proc/sys/vm/dirty_ratio:20 /proc/sys/vm/dirty_writeback_centisecs:500 

Ich bin mir nicht sicher genau, welche Einstellungen ich verwenden sollte, um IDEAL-Leistung zu bekommen, während ich immer noch nicht alle schmutzigen Seiten sitze, die im Gedächtnis für immer sitzen (was bedeutet, dass, wenn das System abstürzt, dauert es viel länger, auf die Festplatte zu schreiben).

Für die Geschichte: Hier ist was ich ursprünglich als "Nicht-Antwort" geschrieben habe – einige Kommentare hier noch …

Nicht wirklich eine Antwort, aber ich finde es ziemlich interessant, dass wenn ich den Code ändern, um zuerst das gesamte Array zu lesen, und das schreib es aus, es ist zweifellos schneller, als in beiden Schleifen zu spielen. Ich schätze, dass dies völlig nutzlos ist, wenn man mit wirklich riesigen Datensätzen umgehen muss (größer als Speicher). Mit dem ursprünglichen Code wie gepostet, ist die Zeit für 100M uint64 Werte 134s. Wenn ich das Lesen und den Schreibzyklus spalte, ist es 43s.

Dies ist die DoMapping Funktion [nur Code habe ich geändert] nach der Änderung:

 struct VI { uint32_t value; uint32_t index; }; void DoMapping(uint64_t* dest, size_t rowCount) { inputStream->seekg(0, std::ios::beg); std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); uint32_t index, value; std::vector<VI> data; for(size_t i = 0; i < rowCount; i++) { inputStream->read(reinterpret_cast<char*>(&index), static_cast<std::streamsize>(sizeof(uint32_t))); inputStream->read(reinterpret_cast<char*>(&value), static_cast<std::streamsize>(sizeof(uint32_t))); VI d = {index, value}; data.push_back(d); } for (size_t i = 0; i<rowCount; ++i) { value = data[i].value; index = data[i].index; dest[index] = value; } std::chrono::duration<double> mappingTime = std::chrono::system_clock::now() - startTime; std::cout << "Mapping Duration " << mappingTime.count() << " seconds" << std::endl; inputStream.reset(); } 

Ich laufe derzeit einen Test mit 200M Datensätzen, die auf meiner Maschine eine beträchtliche Zeit in Anspruch nimmt (2000+ Sekunden ohne Code-Änderungen). Es ist sehr klar, dass die Zeit von Disk-I / O genommen ist, und ich sehe IO-Raten von 50-70MB / s, was ziemlich gut ist, da ich nicht wirklich erwarten, dass mein ziemlich anspruchsvolles Setup viel zu liefern mehr als das. Die Verbesserung ist nicht so gut mit der größeren Größe, aber immer noch eine anständige Verbesserung: 1502s Gesamtzeit, vs 2021s für die "lesen und schreiben in der gleichen Schleife".

Auch möchte ich darauf hinweisen, dass dies ein ziemlich schrecklicher Test für jedes System ist – die Tatsache, dass Linux ist deutlich schlechter als Windows ist neben dem Punkt – Sie wollen nicht wirklich eine große Datei abbilden und schreiben 8 Bytes [Bedeutung Die 4KB-Seite muss eingelesen werden] auf jede Seite zufällig. Wenn dies Ihre echte Anwendung widerspiegelt, dann sollten Sie ernsthaft Ihren Ansatz in irgendeiner Weise überdenken. Es wird gut laufen, wenn man genügend freien Speicher hat, dass die ganze Speicher-abgebildete Region in RAM passt.

Es gibt viel RAM in meinem System, also glaube ich, dass das Problem ist, dass Linux nicht zu viele abgebildete Seiten mag, die "schmutzig" sind.

Ich habe das Gefühl, dass dies etwas damit zu tun haben kann: https://serverfault.com/questions/126413/limit-linux-background-flush-dirty-pages Weitere Erläuterung: http://www.westnet.com/ ~ Gsmith / content / linux-pdflush.htm

Leider bin ich auch sehr müde und muss schlafen Ich werde sehen, ob ich morgen mit diesen experimentieren kann – aber halte deinen Atem nicht. Wie ich schon sagte, das ist nicht wirklich eine Antwort, sondern ein langer Kommentar, der nicht wirklich in einen Kommentar passt (und enthält Code, der komplett Müll ist, um einen Kommentar zu lesen)

  • Größenänderung von numpy.memmap Arrays
  • Python ist die beste Programmiersprache der Welt.