Optimization 如何更快地列出目录?

Optimization 如何更快地列出目录?,optimization,haskell,file-io,io,Optimization,Haskell,File Io,Io,我有一些情况需要递归地列出文件,但我的实现速度很慢。我有一个目录结构,包含92784个文件查找在不到0.5秒的时间内列出文件,但我的Haskell实现要慢得多 我的第一个实现花费了9秒多一点的时间来完成,下一个版本花费了5秒多一点的时间,而我现在只花了不到2秒的时间 listFilesR :: FilePath -> IO [FilePath] listFilesR path = let isDODD "." = False isDODD ".." = False

我有一些情况需要递归地列出文件,但我的实现速度很慢。我有一个目录结构,包含92784个文件<代码>查找在不到0.5秒的时间内列出文件,但我的Haskell实现要慢得多

我的第一个实现花费了9秒多一点的时间来完成,下一个版本花费了5秒多一点的时间,而我现在只花了不到2秒的时间

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
listFilesR::FilePath->IO[FilePath]
listfilesrpath=let
ISD奇数“.”假
IsD奇数“.”=假
ISD奇数=真
在做

allfiles一个问题是,它必须先构造整个目录内容列表,然后程序才能处理它们。懒惰IO通常不受欢迎,但在这里使用unsafeInterleaveIO可以显著减少内存使用

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
listFilesR::FilePath->IO[FilePath]
listFilesR路径=
让
ISD奇数“.”假
IsD奇数“.”=假
ISD奇数=真
在未经授权的情况下,$do

所有文件我认为
System.Directory.getDirectoryContents
构建了一个完整的列表,因此占用了大量内存。使用如何
System.Posix.Directory.readDirStream
逐个返回一个条目


另外,虽然我从未使用过它,但它可能很有用。

分析代码表明,大部分CPU时间都花在
getDirectoryContents
doesDirectoryExist
上。这意味着仅更改数据结构不会有多大帮助。如果您想与
find
的性能相匹配,您应该使用较低级别的函数来访问文件系统,可能是Tsuyoshi指出的那些函数。

是否可以选择使用某种缓存系统与读取相结合?我在考虑一个异步索引服务/线程,它在后台保持这个缓存的最新状态,也许您可以将缓存作为一个简单的SQL-DB来执行,这样在对它进行查询时,它会给您带来一些好的性能

你能详细阐述一下你的“项目/想法”吗?这样我们就可以想出一些替代方案了


我自己不会选择“完整索引”,因为我主要构建基于网络的服务,“响应时间”对我来说至关重要,另一方面,如果这是启动新服务器的初始方式,我相信客户不会介意第一次等待。我只想将结果存储在数据库中,以便以后查找。

这节省了大约0.4秒和20兆字节。因此,稍微好一点的是,我使用System.Posix.Directory和iteratees制作了一个版本,它没有做得更好。我发现一件奇怪的事情是System.Posix.Directory似乎没有提供我期望的功能。“readdir”返回一个指向“struct dirent”的指针,但从DirectoryStream中似乎只能得到文件名——这意味着您必须进行另一次调用(可能是通过doesDirectoryExist调用stat()),以确定它是否为目录。这也可能是问题的一部分-find不需要再进行一次系统调用来发现它是否是目录。@mokus:谢谢你提供的信息。在Posix系统中,读取directory by不会返回返回的条目是否为目录,因此需要单独的系统调用(通常是stat或lstat)来确定它是否为目录。因此,您描述的System.Posix.Directory的行为并不奇怪。find命令的一些实现使用硬链接计数技巧来省略对stat的不必要调用,这使得遍历更快。在我的系统(Mac OS)上,“struct dirent”有一个字段“d_type”,其中一个可能的值是“DT_DIR”。Wikipedia暗示,这在POSIX规范中是可选的,但对于DirectoryStream来说,提供一个“isDir”或“fileType”操作肯定是一个很好的例子,该操作将使用该信息(如果可用)并调用stat。即使这不是一个必需的标准,如果他的平台有它,如果find没有使用它,我也会感到震惊。@mokus:哇。我不知道d_类型字段,但至少我也有它。这似乎是一个事实上的标准。@Tsuyoshi Ito没有统计,你如何获得硬链接计数?我总是乐于接受新想法。我正在为一个全文搜索引擎Hyper Estraier编写一个用于桌面的包装。我是一个大量的命令行用户,所以我想做一个本地的gatherer和searcher。目前,我已经将bash脚本转换为Haskell,但它仍然使用estcmd命令进行收集和搜索,并且系统进程调用非常糟糕。对于本地gatherer,我需要至少第一次解析每个文件。但是我想不出一种方法来只列出自上次以来添加或修改的文件?例如,Windows有用于新文件、重命名等的“目录事件”。如果您有某种“根”文件夹,您可能可以放置一个带有递归触发的“根事件处理程序”。我自己还没有尝试过,但在第一次索引目录之后,我会朝这个方向看。Linux有一个全局文件缓存,所以你不必编写一个,它在应用程序之间共享。它也有目录事件。
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