W zeszłym tygodniu użytkownik Masse zapytał o numer question about recursively listing files w katalogu w Haskell. Moją pierwszą myślą było spróbuj użyć monadycznych list od List
package, aby uniknąć budowania całej listy w pamięci przed rozpoczęciem drukowania. Zaimplementowałem to w następujący sposób:Dlaczego mój kod używa list monadycznych z pakietu List tak wolno?
module Main where
import Prelude hiding (filter)
import Control.Applicative ((<$>))
import Control.Monad (join)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.ListT (ListT)
import Data.List.Class (cons, execute, filter, fromList, mapL)
import System (getArgs)
import System.Directory (getDirectoryContents, doesDirectoryExist)
import System.FilePath ((</>))
main = execute . mapL putStrLn . listFiles =<< head <$> getArgs
listFiles :: FilePath -> ListT IO FilePath
listFiles path = liftIO (doesDirectoryExist path) >>= listIfDir
where
valid "." = False
valid ".." = False
valid _ = True
listIfDir False = return path
listIfDir True
= cons path
$ join
$ listFiles
<$> (path </>)
<$> (filter valid =<< fromList <$> liftIO (getDirectoryContents path))
Działa to pięknie, ponieważ natychmiast rozpoczyna drukowanie i zużywa bardzo mało pamięci. Niestety jest również kilkadziesiąt razy wolniejsza niż porównywalna wersja FilePath -> IO [FilePath]
.
Co robię źle? Nigdy nie używałem pakietu List
poza tymi przykładami zabawek, więc nie wiem, jakiego rodzaju wydajności można się spodziewać, ale wydaje mi się, że 30 sekund (w ułamku sekundy) do przetworzenia katalogu z ~ 40 000 plików o wiele za wolno.
Możemy użyć 'Data.ByteString.getDirectoryContents :: ByteString -> [ByteString]'. Kiedy weźmiesz pod uwagę 40 000 plików po 10 lub więcej znaków, to 400 000 znaków. A [Char] w Haskell bierze co? Ballpark 12 bajtów (x86) lub 24 (x86-64). Tak więc te 400 000 znaków ma 8 MB lub więcej, które znajdują się na połączonej liście. Teraz, gdy już to wszystko powiedziałem, mam nadzieję, że odpowiesz: "Sprawdziłem poziom zawartości GetDirectoryContents, a to nie jest problem". –
@TomMD: Interesuje mnie przede wszystkim porównanie z wersjami 'FilePath -> IO [FilePath]', które używają 'getDirectoryContents' w dokładnie taki sam sposób (na przykład oryginalna implementacja Masse'a). Więc nie sądzę, że to jest problem, ale przyjrzę się. –