ÜBERARBEITETE ZUSAMMENFASSUNG
Okay, es sieht so aus, als ob die Systemaufrufe sicherlich mit GC zusammenhängen, und das zugrunde liegende Problem ist nur, dass GC zu oft passiert. Dies scheint mit der Verwendung von "splitWhen" und "pack" zu tun zu haben, was ich am besten durch Profiling feststellen kann.
splitWhen's Implementierung konvertiert jeden Chunk von "Lazy" in "Strict" und verkettet sie alle, indem er einen Puffer aus Chunks aufbaut. Das ist eine Menge zuzuteilen.
pack, da es von einem Typ in einen anderen konvertiert, muss zugewiesen werden, und das ist in meiner inneren Schleife, so dass auch Sinn macht.
ORIGINALE AUSGABE
Ich bin auf eine überraschende Syscall-Aktivität in Haskell-Enumerator-basierten IO gestoßen. Hoffe, dass jemand etwas Licht darauf werfen kann.
Ich habe mit einer Haskell-Version eines schnellen Perl-Skripts gespielt, das ich einmal für ein paar Monate geschrieben habe, jetzt und danach. Das Skript liest aus jeder Zeile ein json ein und druckt dann ein bestimmtes Feld aus, falls es existiert.
Hier ist die Perl-Version und wie ich sie ausführe.
%Vor%Hier ist die Haskell-Version (sie wird ähnlich wie die Perl-Version aufgerufen).
%Vor%Die Überraschung ist, dass der Trace der Haskell-Version in etwa so aussieht (der eigentliche JSON wird unterdrückt, weil es Daten von der Arbeit sind), während die Perl-Version das tut, was ich erwarten würde; eine Reihe von Reads gefolgt von einem Schreiben, wiederholt.
%Vor%Sind Sie besorgt über die Zuweisungen oder die (Overhead von?) Aufrufe an sigprocmask?
Wenn es ersteres ist und Sie das enumerator
-Paket verwenden möchten, hilft diese kleine Änderung einem 4k-Testsatz um etwa 50%: 8MB Zuweisungen auf 4MB und gen0 GCs von 15 auf 6 reduziert.
Vorher (stats von +RTS -sstderr -RTS
):
Nachher:
%Vor%Was eine ziemlich vernünftige Verbesserung ist, aber definitiv etwas zu wünschen übrig lässt. Anstatt den Enumerator zu sehr in den Wahnsinn zu treiben, habe ich einen Stich gemacht, um es in Conduit-0.4.1 nur für Tritte neu zu schreiben. Es sollte gleichwertig sein ...
%Vor%... aber aus irgendeinem Grund reserviert und speichert weniger Speicher:
%Vor%Dies wird auf oberster Ebene von Kommentaren unterstützt:
FWIW, ich gehe durch die Laufzeit (wir diskutieren das auch im IRC) und es gibt nur zwei Anwendungen von sigprocmask: GC und der tty-Treiber. Letzteres ist unwahrscheinlich, ich habe Profiling empfohlen, um zu verifizieren, dass es viel GC macht und zu versuchen, herauszufinden, warum.
Und es stellt sich heraus (von IRC), dass es 90MB Zuteilung für 0.5MB Daten macht, und der Müllsammler wird in der Tat ziemlich viel ausgelöst. Jetzt liegt es also daran, warum der Enumerator so viel zusätzliche Zuweisung vornimmt.
Mehr als ein Kommentar und weniger als eine Antwort: Wenn Sie durch die GHC-Quelle grepsen, sehen Sie posix/TTY.c
(TERMIOS-Code) und sm/GC.c
(via {,un}blockUserSignals
) die wahrscheinlichsten Kandidaten. Sie könnten GHC mit Debugging-Symbolen kompilieren oder einfach einige (einmalige) Dummy-Systemaufrufe einwerfen, um sicherzustellen, dass Sie die beiden Systemaufrufprofile unterscheiden können, um das herauszufinden. Ein weiterer günstiger Test wäre, alle terminalen Interaktionen zu entfernen, und wenn das Maskierungsverhalten verschwindet, wäre dies ein leichter Beweis, der den GC unterstützt (keine Antwort).
EDIT: Ich sollte anerkennen, dass ein bibliothekscode sigprocmask
auch aufrufen kann, ignorierte ich das als eine weniger wahrscheinliche Quelle, aber es könnte tatsächlich das Problem sein!
Tags und Links haskell ghc performance