上週,用戶Masse在Haskell的一個目錄中詢問question about recursively listing files。我的第一個想法是嘗試使用來自List
package的monadic列表,以避免在打印開始之前在內存中構建整個列表。我實現這個如下:爲什麼我的代碼使用List包中的monadic列表太慢?
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))
這工作很好,它立即開始打印,並使用很少的內存。不幸的是,它也比類似的FilePath -> IO [FilePath]
版本慢了幾十倍。
我在做什麼錯?我從來沒有使用過像這樣的玩具例子中的包ListT
,所以我不知道期望的性能是什麼樣的,但是30秒(而不是幾分之一秒)處理具有〜40,000個文件的目錄似乎太慢了。
我們可以使用'Data.ByteString.getDirectoryContents :: ByteString - > [ByteString]'。如果考慮40,000個文件,每個文件包含10個以上的字符,那就是400,000個字符。 Haskell中的[Char]需要什麼? Ballpark 12字節(x86)或24(x86-64)。所以這400,000個字符在整個鏈表中都是8MB或更多。現在我已經說了所有這些,希望你會回答「我已經對getDirectoryContents進行了基準測試,這不是問題」。 – 2010-10-12 17:43:02
@TomMD:我主要對使用'getDirectoryContents'的FilePath - > IO [FilePath]'版本進行比較的方式完全相同(例如,Masse的原始實現)。所以我不認爲這是問題,但我會看看。 – 2010-10-12 22:28:54