2010-10-07 9 views
9

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 

Antwort

5

Ich denke, dass System.Directory.getDirectoryContents eine ganze Liste konstruiert und verwendet daher viel Speicher. Wie wäre es mit System.Posix.Directory? System.Posix.Directory.readDirStream gibt einen Eintrag nacheinander zurück.

Auch FileManip library könnte nützlich sein, obwohl ich es nie benutzt habe.

+0

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

+0

@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. –

+1

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

1

Ein Problem ist, dass es die gesamte Liste der Verzeichnisinhalt zu konstruieren hat, bevor das Programm kann mit ihnen nichts tun. Lazy IO ist in der Regel verpönt, aber mit unsafeInterleaveIO hier Schnitt Speicherverbrauch erheblich.

listFilesR :: FilePath -> IO [FilePath] 
listFilesR path = 
    let 
    isDODD "." = False 
    isDODD ".." = False 
    isDODD _ = True 
    in unsafeInterleaveIO $ 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 
+0

Das etwa 0,4 Sekunden und 20 Megabyte abrasiert. Also ein bisschen besser – Masse

3

Das Profiling Ihres Codes zeigt, dass die meiste CPU-Zeit in getDirectoryContents, doesDirectoryExist und </> geht. Dies bedeutet, dass nur die Änderung der Datenstruktur nicht sehr hilfreich ist. Wenn Sie die Leistung von find abgleichen möchten, sollten Sie Funktionen auf niedrigerer Ebene für den Zugriff auf das Dateisystem verwenden, wahrscheinlich diejenigen, auf die Tsuyoshi hingewiesen hat.

1

Wäre es eine Option, eine Art Cache-System in Kombination mit dem Lesen zu verwenden? Ich dachte an einen asynchronen Indizierungsdienst/Thread, der diesen Cache im Hintergrund auf dem neuesten Stand hielt, vielleicht könnten Sie den Cache als eine einfache SQL-DB machen, die Ihnen dann eine gute Leistung geben würde, wenn Sie dagegen Anfragen stellen würden?

Können Sie etwas zu Ihrem "Projekt/Idee" ausarbeiten, damit wir uns etwas anderes einfallen lassen können?

Ich würde mich nicht für einen "vollen Index" selbst gehen, da ich hauptsächlich webbasierte Dienste baue und "Respossetime" kritisiert, auf der anderen Seite - wenn es ein erster Weg ist, einen neuen Server zu starten, bin ich mir sicher die Kunden würden es nicht abwarten, das erste Mal zu warten. Ich würde das Ergebnis einfach in der Datenbank für spätere Nachschlagezwecke speichern.

+0

Ich bin immer offen für neue Ideen. Ich schreibe einen Wrapper für Hyper Estraier, eine Volltext-Suchmaschine, für den Desktop-Einsatz. Ich bin ein schwerer Befehlszeilenbenutzer, also dachte ich daran, einen einheimischen Sammler und Sucher zu tun. Im Moment habe ich mein Bash-Skript in Haskell umgewandelt, aber es immer noch verwendet die estcmd Befehle zum Sammeln und Suchen, und das System Prozessaufrufe sind hässlich. Und für den einheimischen Sammler muss ich mindestens jede Datei mit dem ersten Durchlauf parsen. Aber ich kann mir keinen Weg vorstellen, zu nur Dateien aufzulisten, die seit dem letzten Mal hinzugefügt oder geändert wurden. – Masse

+0

10 ok - für was für ein Betriebssystem bauen Sie? Z.B. Windows hat "Verzeichnisereignisse" für neue Dateien, Umbenennungen usw. Wenn Sie einen "root" -Ordner haben, können Sie möglicherweise einen "root event handler" mit rekursivem Triggering setzen. habe es nicht selbst versucht, aber ich würde in diese Richtung schauen, nachdem ich den Katalog zum ersten Mal indexiert habe. – BerggreenDK

+0

Linux verfügt über einen globalen Dateicache, sodass Sie keinen Cache schreiben müssen, der von Anwendungen gemeinsam genutzt wird. Es hat auch Verzeichnisereignisse. –

Verwandte Themen