2010-10-07 15 views
9

J'ai quelques situations où j'ai besoin de lister les fichiers de manière récursive, mais mes implémentations ont été lentes. J'ai une structure de répertoire avec 92784 fichiers. find répertorie les fichiers en moins de 0,5 seconde, mais mon implémentation Haskell est beaucoup plus lente.Comment lister les répertoires plus rapidement?

Ma première implémentation a duré un peu plus de 9 secondes, la prochaine version un peu plus de 5 secondes et je suis actuellement à un peu moins de deux secondes.

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 

Le test prend environ 100 Mo de mémoire (+ RTS -s), et le programme consacre environ 40% en GC.

Je pensais faire la liste dans une monade WriterT avec Sequence en tant que monoïde pour empêcher les concavités et la création de liste. Est-ce probable que cela aide? Que devrais-je faire d'autre?

Modifier: J'ai modifié la fonction pour utiliser readDirStream, et cela permet de réduire la mémoire. Il y a encore une certaine répartition, mais le taux de productivité est maintenant> 95% et il tourne en moins d'une seconde.

Ceci est la version actuelle:

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 

Répondre

5

Je pense que System.Directory.getDirectoryContents construit une liste complète et utilise donc beaucoup de mémoire. Que diriez-vous d'utiliser System.Posix.Directory? System.Posix.Directory.readDirStream renvoie une entrée une par une. En outre, FileManip library peut être utile bien que je ne l'ai jamais utilisé.

+0

J'ai fait une version en utilisant System.Posix.Directory et iteratees, ça n'a pas fait grand-chose sinon mieux. Une chose étrange que j'ai trouvé était que System.Posix.Directory ne semble pas fournir la fonctionnalité que je m'attendais."readdir" renvoie un pointeur vers un "struct dirent", mais il semble que la seule chose que vous pouvez obtenir d'un DirectoryStream est le nom de fichier - ce qui signifie que vous devez faire un autre appel (probablement à stat() via doesDirectoryExist) c'est un annuaire. Cela pourrait aussi être une partie du problème - find n'a pas besoin de faire un autre syscall pour découvrir si c'est un répertoire ou non. – mokus

+0

@mokus: Merci pour l'info. Dans les systèmes Posix, la lecture du répertoire par [readdir] (http://www.opengroup.org/onlinepubs/009695399/functions/readdir.html) ne retourne pas si l'entrée renvoyée est un répertoire ou non, et vous avez donc besoin d'un syscall (habituellement stat ou lstat) pour décider s'il s'agit d'un répertoire. Par conséquent, le comportement de System.Posix.Directory que vous avez décrit n'est pas impair. Certaines implémentations de la commande find utilisent l'astuce de comptage de liens physiques pour omettre les appels inutiles à stat, ce qui rend la traversée plus rapide. –

+1

Sur mon système (Mac OS), "struct dirent" a un champ "d_type", dont une valeur possible est "DT_DIR". Wikipédia laisse entendre que cela est facultatif dans la spécification POSIX, mais il serait certainement bon que DirectoryStream fournisse une opération 'isDir' ou 'fileType' qui utiliserait cette information si elle était disponible et appelerait stat autrement. Même si ce n'est pas une norme obligatoire, si sa plate-forme l'a, je serais choqué si find ne l'utilise pas. – mokus

1

Un problème est qu'il doit construire la liste complète du contenu du répertoire, avant que le programme ne peut rien faire avec eux. Les E/S paresseuses sont généralement désapprouvées, mais l'utilisation d'interfaces non sécurisées réduit considérablement l'utilisation de la mémoire.

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

Qui rasé environ 0,4 secondes et 20 mégaoctets. Donc, un peu mieux – Masse

3

Le profilage de votre code montre que la plus grande partie du temps processeur est comprise entre getDirectoryContents, doesDirectoryExist et </>. Cela signifie que seule la modification de la structure des données ne sera pas très utile. Si vous voulez faire correspondre les performances de find, vous devez utiliser des fonctions de niveau inférieur pour accéder au système de fichiers, probablement celles que Tsuyoshi a signalées.

1

Serait-ce une option d'utiliser une sorte de système de cache combinée avec la lecture? Je pensais à un service d'indexation asynchrone/thread qui maintenait ce cache à jour en arrière-plan, peut-être que vous pourriez faire le cache comme une simple base de données SQL qui vous donnerait de bonnes performances lors des requêtes?

Pouvez-vous élaborer quelque chose sur votre «projet/idée» afin que nous puissions trouver quelque chose d'autre? Je n'irais pas moi-même pour un "index complet" car je construis principalement des services basés sur le Web et "resposnetime" est critique pour moi, d'autre part - si c'est une façon initiale de démarrer un nouveau serveur, je suis sûr les clients ne seraient pas dérangés d'attendre cette première fois. Je voudrais simplement stocker le résultat dans la base de données pour les recherches ultérieures.

+0

Je suis toujours ouvert aux nouvelles idées. J'écris un emballage pour Hyper Estraier, un moteur de recherche de texte intégral, pour l'utilisation de bureau. Je suis un utilisateur de ligne de commande lourd , donc je pensais faire un chercheur natif et chercheur. Pour le moment, j'ai converti mon script bash en Haskell, mais il utilise toujours les commandes estcmd pour la collecte et la recherche, et les appels système sont moche. Et pour le rassembleur natif je dois analyser chaque fichier au moins avec le premier passage. Mais je ne peux pas penser à un moyen de liste seulement les fichiers qui sont ajoutés ou modifiés depuis la dernière fois. – Masse

+0

ok - pour quel genre d'OS construisez-vous? Par exemple. Windows a des "événements d'annuaire" pour les nouveaux fichiers, renommer, etc. Si vous avez une sorte de dossier "racine", vous pouvez mettre un "gestionnaire d'événement racine" avec un déclenchement récursif. Je l'ai essayé moi-même, mais je regarderais dans cette direction après avoir indexé le catalogue la première fois. – BerggreenDK

+0

Linux a un cache de fichiers global, vous n'avez donc pas à en écrire un et il est partagé entre les applications. Il a également des événements d'annuaire. –