【问题标题】:Parsing ASCII file efficiently in Haskell在 Haskell 中有效地解析 ASCII 文件
【发布时间】:2015-10-05 13:17:14
【问题描述】:

我想在 Haskell 中重新实现我的一些 ASCII 解析器,因为我认为我可以获得一些速度。但是,即使是简单的“grep and count”也比草率的 Python 实现要慢得多。

谁能解释一下为什么以及如何正确地做到这一点?

所以任务是,计算以字符串“foo”开头的行数。

我非常基本的 Python 实现:

with open("foo.txt", 'r') as f:
    print len([line for line in f.readlines() if line.startswith('foo')])

还有 Haskell 版本:

import System.IO 
import Data.List

countFoos :: String -> Int
countFoos str = length $ filter (isPrefixOf "foo") (lines str)

main = do
    contents <- readFile "foo.txt"
    putStr (show $ countFoos contents)

使用time 在大约 600MB 的文件上运行 17001895 行表明,Python 实现几乎比 Haskell 快 4 倍(在我的 MacBook Pro Retina 2015 和 PCIe SSD 上运行) :

> $ time ./FooCounter                                                           
1770./FooCounter  20.92s user 0.62s system 98% cpu 21.858 total

> $ time python foo_counter.py                                                   
1770
python foo_counter.py  5.19s user 1.01s system 97% cpu 6.332 total

与 unix 命令行工具相比:

> $ time grep -c foo foo.txt                                                     
1770
grep -c foo foo.txt   4.87s user 0.10s system 99% cpu 4.972 total

> $ time fgrep -c foo foo.txt                                                     
1770
fgrep -c foo foo.txt  6.21s user 0.10s system 99% cpu 6.319 total

> $ time egrep -c foo foo.txt                                                     
1770
egrep -c foo foo.txt  6.21s user 0.11s system 99% cpu 6.317 total

有什么想法吗?

更新:

使用 András Kovács 的实现 (ByteString),我不到半秒就搞定了!

> $ time ./FooCounter                                                                                                               
1770
./EvtReader  0.47s user 0.48s system 97% cpu 0.964 total

【问题讨论】:

  • 如果没有,请用-O2编译。
  • 不要使用String。使用ByteString 或(更有可能)TextString 类型非常灵活,但对几乎所有事情都非常低效。
  • @AndrásKovács 我忘了提,我确实用 -O2 编译过它。实际上这并没有什么区别 :-\ 无论如何:köszi
  • @MathematicalOrchid 但readFilereadFile :: FilePath -&gt; IO String。我应该如何强制使用ByteStringText
  • @septi 看看Data.Text.IO。您会发现另一个 readFile 函数返回 Text

标签: haskell file-io text-parsing


【解决方案1】:

我对以下解决方案进行了基准测试:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.ByteString.Char8 as B

main =
  print . length . filter (B.isPrefixOf "foo") . B.lines =<< B.readFile "test.txt"

text.txt 是一个 170 MB 的文件,有 800 万行,其中一半的行以“foo”开头。我使用 GHC 7.10 和 -O2 -fllvm 编译。

ByteString 版本在 0.27 秒内运行,而原始版本在 5.16 秒内运行。

但是,严格的ByteString 版本使用 170 MB 内存来加载完整文件。将导入更改为 Data.ByteString.Lazy.Char8 我得到 0.39 秒的运行时间和 1 MB 的内存使用量。

【讨论】:

  • 哇,这令人印象深刻。不到半秒!
  • 这会将整个文件读入内存吗?由于我还需要解析大于 10GB 的文件……
  • 是的,它会加载所有内容。查看答案的编辑。
【解决方案2】:

您的 Haskell 版本使用类型 String 来表示文件的文本。 String[Char] 的别名,[Char] 是一个字符链表。这不是大字符串的良好表示。

尝试改用text 包。它将字符串表示为数组(在Data.Text.* 模块中)或数组的链接列表(在Data.Text.Lazy.* 模块中)。要移植现有代码,您可能需要后者,因为我猜您不想一次将完整的 600MB 文件加载到内存中。在 Data.Text.LazyData.Text.Lazy.IO 模块中查找您正在使用的 readFilefilterisPrefixOf 等函数的变体。

如果您确定只想支持 ASCII,也可以考虑使用 bytestring 包而不是 text 包。

【讨论】:

    猜你喜欢
    • 2011-04-04
    • 1970-01-01
    • 2012-07-04
    • 2013-06-27
    • 1970-01-01
    • 2014-09-20
    • 2011-06-18
    • 2022-12-22
    • 2018-02-03
    相关资源
    最近更新 更多