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