Was macht ByteString IO so schnell?

8

Ich habe versucht, Problem 1330 von acm zu lösen .timus.ru in Haskell. Grundsätzlich läuft es darauf hinaus: 1) lese von stdin ein Array A der Länge N (N & lt; 10 ^ 4) und M Paare von ganzen Zahlen (M & lt; 10 ^ 5) ab; 2) für jedes (von, bis) Paar, drucke die Summe von Unterfeld A [von..to] nach stdout.

Da SO mich nicht mehr als zwei URLs als Teil dieser Frage veröffentlichen lässt, werde ich auf Dateien in meinem Github-Repository unten.

Ich habe zwei Lösungen gefunden, die den größten Teil des Codes teilen. Die erste (1330_slow.hs) verwendet Prelude-Funktionen (getLine / read / words) und ist etwas langsam:

%Vor%

Die andere Lösung (1330.hs) verteilt diese Funktionen, ersetzt sie durch ihre Data.ByteString.Char8-Entsprechungen (B.getLine / B.readInt / B.words) und verhält sich gut:

%Vor%

Das Zeitlimit für dieses Problem liegt bei 500 ms, also sind 270 ms schnell genug (und vergleichbar mit meinen Lösungen in anderen Sprachen wie C ++ und Go), 2180 ms schneiden nicht ab. Warum ist meine erste Lösung so lächerlich langsam? Selbst wenn ich die Profiling-Tipps von Real World Haskell befolge, kann ich immer noch keinen Sinn daraus ziehen (alles was ich herausfinden konnte war, dass die meiste Zeit in der readIntPair-Funktion verbracht wurde, was nicht viel half).

Wenn Sie eigene Tests durchführen möchten, habe ich einen Python-Input-Generator (gen_test.py) und eine vorgenerierte Eingabedatei (input.txt) für den Fall, dass Sie Python nicht installiert haben. Und ein Unterschied (slow_fast_diff.txt) zwischen den beiden Lösungen.

    
Ivan Komarov 27.01.2013, 02:20
quelle

2 Antworten

8

Wie andere bereits gesagt haben, ist ByteString nicht schnell, sondern String ist sehr, sehr langsam.

A ByteString speichert ein Byte pro Zeichen, zuzüglich einiger Buchhaltungskosten. A String speichert etwa 12 Bytes pro Zeichen (abhängig davon, ob Sie im 32-Bit- oder 64-Bit-Modus arbeiten). Es speichert auch jedes Zeichen in einem nicht zusammenhängenden Speicher, so dass jedem Zeichen individuell zugewiesener Speicherplatz zugewiesen werden muss, der vom Speicherbereiniger einzeln gescannt und schließlich wieder einzeln freigegeben werden muss. Dies bedeutet eine schlechte Cache-Lokalität, viel Zuteilungszeit und viel Zeit für die Speicherbereinigung. Kurz gesagt, es ist höllisch ineffizient.

Grundsätzlich macht ByteString was C tut, was Java macht, was C ++ macht, was C # macht, was VB tut und was jede andere Programmiersprache mit Strings macht. Keine andere Sprache, die mir bekannt ist, hat einen Standard-String-Typ, der so ineffizient ist wie Haskell. (Auch Frege, ein Haskell-Dialekt, verwendet einen effizienteren Stringtyp.)

Ich sollte darauf hinweisen, dass ByteString.Char8 nur Latin-1-Zeichen verarbeitet. Es kommt überhaupt nicht mit zufälligen Unicode-Zeichen zurecht. Für eine Programmieraufgabe wie diese ist das wahrscheinlich kein Problem, aber für ein "echtes System" könnte es gut sein. ByteString befasst sich nicht wirklich mit exotischen Zeichen oder anderen Zeichenkodierungen oder irgendetwas; es nimmt nur an, dass Sie einfaches ASCII wollen. Das war früher eine sichere Annahme; heute, nicht so sehr.

    
MathematicalOrchid 27.01.2013, 09:49
quelle
8

Kontrast von ByteString und String (d. h. warum ist String langsam?)

Bytestring IO beinhaltet das Lesen von Daten in gepackte Puffer, genau wie Sie es in C. String s gewohnt sind, auf der anderen Seite sind verkettete Listen von Zeichen, die IO nicht nur komplizieren, sondern für die Verarbeitung bedeuten können höhere Nutzung von Speicher, Verarbeitung, Cache, möglicherweise Verzweigung und GC.

Warum ist ByteString schnell?

Eine andere Möglichkeit, um es auszudrücken: ByteString ist aus den gleichen Gründen schnell unsigned char *  ist schnell in C .

    
Thomas M. DuBuisson 27.01.2013 04:54
quelle

Tags und Links