Die Herausforderung besteht darin, eine große Datei schnell zu filtern.
- Eingabe: Jede Zeile hat drei durch Leerzeichen getrennte positive Ganzzahlen.
Ausgabe: Alle Eingabezeilen
A
B
,T
die eines der folgenden Kriterien erfüllen.- Es gibt eine weitere Eingangsleitung
C
,D
,U
woD = A
und0 <= T - U < 100
. - Es gibt eine weitere Eingangsleitung
C
,D
,U
woB = C
und0 <= U - T < 100
.
- Es gibt eine weitere Eingangsleitung
Verwenden Sie zum Erstellen einer Testdatei das folgende Python-Skript, das auch zum Testen verwendet wird. Es wird eine 1.3G-Datei erstellt. Sie können natürlich Nolines zum Testen reduzieren.
import random
nolines = 50000000 # 50 million
for i in xrange(nolines):
print random.randint(0,nolines-1), random.randint(0,nolines-1), random.randint(0,nolines-1)
Regeln. Der schnellste Code beim Testen einer Eingabedatei, die ich mit dem obigen Skript auf meinem Computer erstellt habe, gewinnt. Die Frist beträgt eine Woche ab dem Zeitpunkt der ersten korrekten Eingabe.
Meine Maschine Die Timings werden auf meiner Maschine ausgeführt. Dies ist eine Standard-Ubuntu-Installation mit 8 GB RAM auf einem AMD FX-8350 Eight-Core-Prozessor. Dies bedeutet auch, dass ich Ihren Code ausführen kann.
Einige relevante Timing-Informationen
Die Timings wurden aktualisiert, um vor jedem Test Folgendes auszuführen.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
time wc test.file
real 0m26.835s
user 0m18.363s
sys 0m0.495s
time sort -n largefile.file > /dev/null
real 1m32.344s
user 2m9.530s
sys 0m6.543s
Status der Einträge
Ich führe vor jedem Test die folgende Zeile aus.
sync && sudo bash -c 'echo 3 > /proc/sys/vm/drop_caches'
- Perl (Warten auf Fehlerbehebung.)
- Scala 1 Minuten 37 Sekunden von @James_pic. (Verwenden von scala -J-Xmx6g Filterer largefile.file output.txt)
- Java . 1 Minute 23 Sekunden von @Geobits. (Verwenden von Java -Xmx6g Filter_26643)
- C . 2 Minuten 21 Sekunden von @ScottLeadley.
- C . 28 Sekunden von @James_pic.
- Python + Pandas . Vielleicht gibt es eine einfache "Groupby" -Lösung?
- C . 28 Sekunden von @KeithRandall.
Die Gewinner sind Keith Randall und James_pic.
Ich konnte ihre Laufzeiten nicht auseinanderhalten und beide sind fast so schnell wie wc!
1 < n < 2147483647
?Antworten:
C, ~
74,1 SekundenRadix sortiert nach T und geht dann durch das Array, um nach Übereinstimmungen zu suchen.
Es ist schnell, weil es Cache-freundlich ist. Der Radix sortiert einigermaßen und der letzte Spaziergang sehr. Ich muss jede Zeile mit ungefähr 100 anderen vergleichen, aber sie sind alle nebeneinander im Cache.
Hinzugefügt: Ich muss nicht mehr jede Zeile mit einem Scan von 100 anderen Zeilen vergleichen. Eine kleine Tabelle mit Zählwerten von Bits niedriger Ordnung von b im Fenster reicht aus, um die meisten Instanzen dieses Scans zu eliminieren.
Jetzt ungefähr 1/2 Mal analysieren, 1/3 mal sortieren, 1/6 mal das eigentliche Matching durchführen.
quelle
filter.c
, um das Gleiche zu tun, bin auf die Frage gekommen und habe dies gefunden. +1Scala 2.10 - 0:41
Das Problem ist im Grunde:
Die meisten RDBMS würden feststellen, dass der Join von
x.a
bisy.b
die höchste Spezifität aufweist, und dies als Hash-Join planen.Das machen wir also. Wir erstellen eine Hashtabelle der Daten für
a
, verknüpfen sie mit derselben Tabelleb
und filtern nach dem Unterschied int
.Kompilieren mit:
Und laufe mit:
Auf meinem Computer läuft dies in 2 Minuten 27.
Trotzdem könnte es interessant sein, den Ansatz aus @ Lembiks Antwort zu versuchen, aber in einer schnelleren Sprache. Es entspricht so etwas wie einem Merge Join On
t
. Auf dem Papier sollte es langsamer sein, aber es hat eine bessere Cache-Lokalität, was es möglicherweise vorantreiben kann.Aktualisieren
Ich habe es geschafft, mit einer überraschend kleinen Änderung einen großen Teil meiner Freizeit zu sparen - einen besseren Hash-Mingler. Die Hash-Map reagiert sehr empfindlich auf Hash-Verklumpungen. Durch diese Änderung wird sie auf meinem Computer auf 1:45 reduziert.
Die meiste Zeit wird damit verbracht, die Daten in ein Array einzulesen.
Ich bin gespannt, warum mein Datenlesecode so viel langsamer ist als @Geobits. Es dauert 70 Sekunden, bis mein Code die Daten eingelesen hat - länger als das gesamte Programm von @Geobits, sobald der
Thread.start
Fehler behoben ist. Ich bin versucht, den @ Geobits-Ansatz zum Lesen von Daten zu stehlen, bin mir aber nicht sicher, wie sich die Stack Exchange-Götter dazu fühlen würden.Update 2
Ich habe weitere Verbesserungen vorgenommen, diesmal am Datenleser. Die Verwendung von Pattern Matching und Monad-Operationen innerhalb der Schleife beeinträchtigte die Leistung, daher habe ich sie vereinfacht. Ich denke, das
scala.io.Source
ist der nächste Engpass, der angegangen werden muss.Auf meinem Computer ist es jetzt 1:26.
Update 3
Losgeworden
probe
von OpenHashMultiMap. Der Code ist jetzt mehr Java-ish und läuft in 1:15.Update 4
Ich verwende jetzt einen FSM, um Eingaben zu analysieren. Die Laufzeit ist auf 0:41 gesunken
quelle
StringTokenizer
, aber wenn ich tun, ich analysieren Millionen von Strings.String.split
ist derzeit ein Engpass, aber momentanStringTokenizer
nicht viel besser - die Zuweisung in einer engen inneren Schleife schadet meinem bereits angespannten GC. Ich arbeite an einem FSM, derJava: 1m54s
(Auf meinem i7)
Da jedes Match innerhalb von 100
t
von seinem Partner sein wird, habe ich beschlossen, die Eingaben nach zu sortierent
. Für jeweils 100 gibt es einen Eimer. Um eine Zahl zu überprüfen, muss nur gegen +/- 1 Eimer geprüft werden.Im Durchschnitt enthält jeder Bucket nur 100 Einträge, sodass das Scannen einiger Buckets für jeden nicht lange dauert. Weit über die Hälfte der Zeit wird mit Lesen und Bucketing verbracht. Der Abgleich dauert nur etwa 40 Sekunden.
Hinweis: Abhängig von Ihrem JVM-Setup müssen Sie möglicherweise die Heap-Größe erhöhen. Dies setzt auch den Dateinamen von voraus
test.file
. Ändern Sie es einfach in Zeile 24, wenn dies nicht der Fall ist.quelle
Thread::run
nicht verwendetThread.start
, also läuft alles auf demmain
Thread. MitThread::start
sinkt die Laufzeit auf meinem Computer von 1:38 auf 0:46.sort
. Ich habe den Haufen auf 6G erhöht, genau wie meinen (Sie sagten, Sie hätten 8G, also schien es eine vernünftige Vermutung zu sein).C - 12 Sekunden
Ich beschloss, meine Scala-Antwort auf C zu portieren, um zu sehen, wie viel mehr Leistung ich erzielen konnte.
Es ist mehr oder weniger der gleiche Ansatz (eine offene Hash-Tabelle erstellen
a
), außer dass ich den Schritt überspringe, in dem ich das ursprüngliche Array erstelle, und direkt von der Hash-Tabelle iteriere (aus irgendeinem Grund konnte ich diesen Ansatz in Scala niemals ausführen - Ich vermute, JVM Inlining war schuld).Ich habe mich nicht um Fäden gekümmert, da es ein Problem ist, dies tragbar zu machen.
Der Code lautet:
Kompilieren mit:
Und laufe mit:
Der Speicherort der Testdatei ist fest als "test.file" codiert.
Das Lesen der Daten nimmt erneut die meiste Zeit in Anspruch (knapp 9 Sekunden). Das Matching dauert den Rest der Zeit.
Auch hier wird es interessant sein zu sehen, wie dies gegen Scott Leadleys Antwort verstößt, die dieselbe Sprache, aber eine andere Strategie verwendet. Scott tritt T bei, was im Prinzip bedeutet, dass er mehr zu tun hat, aber auch hier bietet die Teilnahme an T eine bessere Cache-Lokalität.
quelle
diff <(sort -n James_pic-c.out) <(sort -n James_pic-scala.out)
a
n
n >= BUFFER_SIZE + 2
Perl, 17m46s auf einem Core i7 mit 8 GB mem
Zunächst verwenden wir sort -n -k3, um das wichtigste Feld in Ordnung zu bringen, wobei wir die integrierte Parallelität moderner Versionen von sort (1) nutzen. Da Perl durch die Tatsache, dass ein einfacher Skalar die Größenordnung von jeweils 80 Bytes annimmt (50 Millionen * 3 * 80 sind zu viel - mindestens 12 GB), stark behindert wird, schlürfen wir die Ausgabe auf 50 Millionen * 12 Byte Array (12 Bytes pro Zeile, jede Zeile enthält 3 Ganzzahlen, die als 32-Bit-Ganzzahl dargestellt werden können). Dann feuern wir 8 Threads ab, die jeweils (ungefähr) 1/8 der Daten abdecken (+ einige Überlappungen).
Unsortierte Ausgabe:
Ich bin mir sicher, dass dies in C eine Größenordnung schneller sein würde, aber ich werde mir wahrscheinlich nicht die Zeit dafür nehmen.
quelle
A = D = 8455767
, aberU = 50175
,T = 50130
und soT - U = -45
C # - 30 Sekunden
Ein anderer Ansatz als die meisten, wenn ich richtig lese - ich verwende keine Hash-basierten Strukturen.
Ich neige dazu, keine Ergebnisse zu erhalten, bin mir nicht sicher, ob dies eine statistische Anomalie oder ein Fehler in meiner Argumentation ist.Behoben, Vergleich für binäre Sortierung war fehlerhaft.quelle
x.A
vonsortedA
undx.B
von kommtsortedB
, während tatsächlich beide von kommensortedB
und diesComparer
hervorbringen wird Unsinn Ergebnisse.A
undB
gibt es einen schnelleren Algorithmus als Iterieren aufA
und binäre Suche aufB
denenO(n log(n))
(und ist effektiv ein Arme-Leute-Hash - Tabelle). Sie können stattdessen die beiden Listen zusammenführen und verbindenO(n)
.B
in einem bestimmten Bereich gleichmäßig verteilt sind, besteht darin, die binäre Suche gegen die Interpolationssuche auszutauschen, wodurch die Suchzeit vonO(log(n))
bis verkürzt wirdO(log(log(n))
.C.
Brutale, brutale Gewalt, hässliches C. In deinem Gesicht würde ich bei einer Überarbeitung fast jede andere kompilierte Sprache wählen.
Kompilieren Sie mit "gcc -m64 -pthreads -O". Erwartet Eingaben auf stdin. Läuft standardmäßig mit mehreren Threads. Verwenden Sie die Option "-s", um nur einen Thread zu verwenden.
quelle
Endlich hatte ich die Möglichkeit, ein physisches Ubuntu 14.04-System ähnlich dem von Lembik zu bauen und meine Lösung für dieses Rätsel post mortem zu machen. In meiner Wahl der Wichtigkeit:
saugte,waren nicht performant:Anstatt Sie mit einem weiteren FSM-Parser zu langweilen, verwendet die folgende Lösung fgets () und einen lokalen Ersatz für strtol () [suchen Sie nach s2i ()].
Eine Referenzimplementierung in Ruby:
Es ist ein Hund, ~ 50x langsamer als eine C-Lösung, aber Perl ist genauso langsam und weniger prägnant.
Die C-Lösung:
Kompilieren Sie mit "gcc -O3 -std = c99 -Wall -m64".
quelle