List 为什么我的代码使用列表包中的一元列表速度如此之慢?

List 为什么我的代码使用列表包中的一元列表速度如此之慢?,list,haskell,monads,monad-transformers,List,Haskell,Monads,Monad Transformers,上周,用户Masse在Haskell的一个目录中询问了一个问题。我的第一个想法是尝试使用来自的一元列表,以避免在开始打印之前在内存中构建整个列表。我的实施如下: module Main where import Prelude hiding (filter) import Control.Applicative ((<$>)) import Control.Monad (join) import Control.Monad.IO.Class (liftIO) import Con

上周,用户Masse在Haskell的一个目录中询问了一个问题。我的第一个想法是尝试使用来自的一元列表,以避免在开始打印之前在内存中构建整个列表。我的实施如下:

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))
modulemain其中
导入前奏隐藏(过滤器)
导入控件。应用程序(())
导入控制.Monad(join)
导入控制.Monad.IO.Class(liftIO)
导入控制.Monad.ListT(ListT)
导入Data.List.Class(cons、execute、filter、fromList、mapL)
导入系统(getArgs)
导入System.Directory(getDirectoryContents,doesDirectoryExist)
导入System.FilePath(())
main=执行。mapL putStrLn。listFiles=ListIO文件路径
listFiles path=liftIO(doesDirectoryExist path)>>=listIfDir
哪里
有效“.”=错误
有效“.”=错误
有效的
listIfDir False=返回路径
listIfDir True
=cons路径
$join
$listFiles
(路径)

(filter valid=在一个大目录上运行它会发现内存泄漏。我怀疑这与getDirectoryContents的严格性有关,但可能会有更多的问题。简单的评测结果不多,我会添加一些额外的成本中心并从那里开始…

评测显示
加入
(连同
doesDirectoryExists
)占据了代码中的大部分时间。让我们看看它的定义是如何展开的:

  join x
=> (definition of join in Control.Monad)
  x >>= id
=> (definition of >>= in Control.Monad.ListT)
  foldrL' mappend mempty (fmap id x)
=> (fmap id = id)
  foldrL' mappend mempty x
如果在搜索的根目录中有
k
子目录,并且它们的内容已经在列表中计算:
d1,d2,…dk
,那么在应用
join
之后,您将得到(大致):
([]++d1)++d2).++dk)
。因为
x++y
需要时间
O(长度x)
整个过程需要时间
O(d1+(d1+d2)+…+(d1+…dk-1))
如果我们假设文件的数量是
n
并且它们均匀分布在
d1…dk
之间,那么计算
连接的时间将是
O(n*k)
这仅适用于第一级
列表文件


我认为,这是您的解决方案的主要性能问题。

我很好奇,为使用而编写的同一个程序对您的工作有多好?
LogicT
在语义上与
ListT
相同,但以连续传递的方式实现,因此它不应该出现与
concat
相关的问题遇到

import Prelude hiding (filter)
import Control.Applicative
import Control.Monad
import Control.Monad.Logic
import System (getArgs)
import System.Directory (getDirectoryContents, doesDirectoryExist)
import System.FilePath ((</>))

main = sequence_ =<< observeAllT . fmap putStrLn . listFiles =<< head <$> getArgs

cons :: MonadPlus m => a -> m a -> m a
cons x xs = return x `mplus` xs

fromList :: MonadPlus m => [a] -> m a
fromList = foldr cons mzero

filter :: MonadPlus m => (a -> Bool) -> m a -> m a
filter f xs = do
  x <- xs
  guard $ f x
  return x

listFiles :: FilePath -> LogicT 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))
导入前奏隐藏(过滤器)
导入控制
进口管制
导入控制.Monad.Logic
导入系统(getArgs)
导入System.Directory(getDirectoryContents,doesDirectoryExist)
导入System.FilePath(())
主=顺序\uuM=a->m a
cons x xs=返回x`mplus`xs
fromList::MonadPlus m=>[a]->MA
fromList=foldr cons mzero
过滤器::MonadPlus m=>(a->Bool)->ma->ma
过滤器f xs=do
x LogicT IO文件路径
listFiles path=liftIO(doesDirectoryExist path)>>=listIfDir
哪里
有效“.”=错误
有效“.”=错误
有效的
listIfDir False=返回路径
listIfDir True
=cons路径
$join
$listFiles
(路径)

(筛选器有效=我们可以使用<代码> DAT.ByTestStudi.GeDistRyTyF内容::ByTeStord-> [ByTeScord] < /Calp>。当您考虑40000个文件,每个字符为10个字符时,这是400000个字符。Haskell中的[char ]取什么?PARCARPE 12字节(x86)或24(x86—64)。因此,这40万个字符在一个链接列表中的分布是8MB或更多。现在我已经说过了所有这些,希望您能以“我已经对getDirectoryContents进行了基准测试,这不是问题所在”。@TomMD:我主要感兴趣的是与
FilePath->IO[FilePath]的比较
以完全相同的方式使用
getDirectoryContents
的版本(例如Masse的原始实现)。因此我认为这不是问题所在,但我会看一看。Masse原始实现中的
concat
(速度更快)似乎在做完全相同的事情,但我将更详细地研究这一点。Concat被融合规则转化为以下内容,但我不清楚它是否一定更快,而且我目前还不准备进行基准测试:`“Concat”forall xs.Concat xs=build(\c n->foldr(\x y->foldr c y x)n xs)`ListT定义了它自己的列表类型,因此在它的所有操作中都添加了一些间接操作。它的定义如下:data ListItem l a=Nil | Cons{headL::a,tailL::l a}