我想在Haskell中重新实现我的一些ASCII解析器,因为我认为我可以获得一些速度。但是,即使是简单的“grep和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)
在带有17001895行的~600MB文件上同时使用time
运行显示 Python实现几乎比Haskell 快4倍(在带有PCIe的MacBook Pro Retina 2015上运行) 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ásKovács的实施(ByteString
),我得到它不到半秒!
> $ time ./FooCounter
1770
./EvtReader 0.47s user 0.48s system 97% cpu 0.964 total
答案 0 :(得分:11)
我对以下解决方案进行了基准测试:
{-# 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。
答案 1 :(得分:5)
您的Haskell版本使用类型String
来表示文件的文本。 String
是[Char]
的别名,它是一个链接的字符列表。这对于大字符串来说不是一个好的表示。
请尝试使用text包。它将字符串表示为数组(在Data.Text.*
模块中)或表示链接的数组列表(在Data.Text.Lazy.*
模块中)。要移植现有代码,您可能需要后者,因为我猜你不想一次将完整的600MB文件加载到内存中。请查看Data.Text.Lazy
和Data.Text.Lazy.IO
模块,了解您正在使用的readFile
,filter
,isPrefixOf
等功能的变体。
如果您确定只想支持ASCII,还可以考虑使用bytestring
包而不是text
包。