Ich habe ein paar Situationen, in denen ich rekursiv Dateien auflisten muss, aber meine Implementierungen waren langsam. Ich habe eine Verzeichnisstruktur mit 92784 Dateien. find
listet die Dateien in weniger als 0,5 Sekunden auf, aber meine Haskell-Implementierung ist viel langsamer.Wie listet man Verzeichnisse schneller auf?
Meine erste Implementierung dauerte etwas mehr als 9 Sekunden, die nächste Version etwas mehr als 5 Sekunden und ich bin momentan auf etwas weniger als zwei Sekunden.
listFilesR :: FilePath -> IO [FilePath]
listFilesR path = let
isDODD "." = False
isDODD ".." = False
isDODD _ = True
in do
allfiles <- getDirectoryContents path
dirs <- forM allfiles $ \d ->
if isDODD d then
do let p = path </> d
isDir <- doesDirectoryExist p
if isDir then listFilesR p else return [d]
else return []
return $ concat dirs
Der Test dauert etwa 100 Megabyte Speicher (+ RTS-S), und das Programm verbringt etwa 40% im GC.
Ich dachte daran, die Auflistung in einer WriterT-Monade mit Sequenz als Monoid zu machen, um die Erstellung von Konkatas und Listen zu verhindern. Ist das wahrscheinlich hilfreich? Was soll ich sonst machen?
Bearbeiten: Ich habe die Funktion bearbeitet, um readDirStream zu verwenden, und es hilft, den Speicher zu halten. Es gibt immer noch eine Zuteilung, aber die Produktivitätsrate liegt jetzt bei> 95% und sie läuft in weniger als einer Sekunde.
Dies ist die aktuelle Version:
list path = do
de <- openDirStream path
readDirStream de >>= go de
closeDirStream de
where
go d [] = return()
go d "." = readDirStream d >>= go d
go d ".." = readDirStream d >>= go d
go d x = let newpath = path </> x
in do
e <- doesDirectoryExist newpath
if e
then
list newpath >> readDirStream d >>= go d
else putStrLn newpath >> readDirStream d >>= go d
Ich habe eine Version mit System.Posix.Directory und iterates gemacht, es hat nicht viel besser gemacht. Eine seltsame Sache, die ich fand, war, dass System.Posix.Directory nicht die Funktionalität bietet, die ich erwarten würde."readdir" gibt einen Zeiger auf eine "struct dirent" zurück, aber es scheint, dass das einzige, was du von einem DirectoryStream bekommen kannst, der Dateiname ist - was bedeutet, dass du einen weiteren Aufruf machen musst (vermutlich zu stat() über doesDirectoryExist) Es ist ein Verzeichnis. Das könnte auch ein Teil des Problems sein - find muss keinen weiteren Syscall machen, um herauszufinden, ob es ein Verzeichnis ist oder nicht. – mokus
@mokus: Danke für die Info. In Posix-Systemen gibt das Lesen des Verzeichnisses nach [readdir] (http://www.opengroup.org/onlinepubs/009695399/functions/readdir.html) nicht zurück, ob es sich bei dem zurückgegebenen Eintrag um ein Verzeichnis handelt oder nicht, und daher benötigen Sie ein separates Verzeichnis syscall (normalerweise stat oder lstat), um zu entscheiden, ob es ein Verzeichnis ist. Daher ist das Verhalten von System.Posix.Directory, das Sie beschrieben haben, nicht ungerade. Einige Implementierungen des find-Befehls verwenden den Hardlink-Zähltrick, um unnötige Aufrufe von stat wegzulassen, wodurch das Traversieren beschleunigt wird. –
Auf meinem System (Mac OS) hat "struct dirent" ein Feld "d_type", dessen möglicher Wert "DT_DIR" ist. Wikipedia weist darauf hin, dass dies in der POSIX-Spezifikation optional ist, aber es wäre sicherlich ein starker Fall für DirectoryStream, eine "isDir" - oder "fileType" -Operation zur Verfügung zu stellen, die diese Information verwenden würde, wenn sie verfügbar wäre. Selbst wenn es kein erforderlicher Standard ist, wenn seine Plattform es hätte, wäre ich schockiert, wenn Find es nicht benutzt. – mokus