为了好玩,我写了一个快速的Racket命令行脚本来解析旧的Unix fortune文件。财富文件只是一个巨大的文本文件,在一行分隔条目的空行上有一个%
。
就像快速的第一次黑客攻击一样,我写下了以下的球拍代码:
(define fortunes
(with-input-from-file "fortunes.txt"
(λ ()
(regexp-split #rx"%" (port->string)))))
我认为它几乎会立即运行。相反,它需要很长时间才能运行几分钟的顺序。相比之下,我认为是等效的Python:
with open('fortunes.txt') as f:
fortunes = f.read().split('%')
立即执行,与Racket代码的结果相同。
我在这里做错了什么?是的,有一些显而易见的低调成果,比如我确信如果我没有用port->string
将整个文件粘贴到RAM中,情况会更好,但是这种行为在病态上是如此糟糕我觉得好像我必须在比这更高的水平做一些愚蠢的事情。
是否有类似Racket的方式来实现这一目标,同样具有更好的性能?某些操作的Racket I / O真的很差吗?是否有一些方法可以比DrRacket中的naïve分析器稍微更深入地分析我的代码,这样我就可以弄清楚某个给定行的关于会导致问题?
编辑:我正在使用的命运文件是http://fortunes.cat-v.org/freebsd/上的FreeBSD,重量约为2 MB。 OS X Lion上Racket 5.1.3 x64的最佳运行时是:
real 1m1.479s
user 0m57.400s
sys 0m0.691s
对于Python 2.7.1 x64,它是:
real 0m0.057s
user 0m0.029s
sys 0m0.015s
Eli是正确的,时间几乎全部花在regexp-split
上(尽管似乎在port->string
花费了一整秒),但我不清楚有一个首选但同样简单的方法
答案 0 :(得分:5)
看起来大部分费用是由于在字符串上运行regexp-split
。我找到的最快的替代方法是拆分字节串,然后将结果转换为字符串:
(map bytes->string/utf-8
(call-with-input-file "db"
(λ (i) (regexp-split #rx#"%" (port->bytes i)))))
使用大约2MB的随机算命数据库,你的代码需要大约35秒,而这个版本需要33毫秒。
(我不确定为什么它在字符串上需要这么长时间,但是它肯定太慢了。)
编辑:我们将其跟踪为效率错误。粗略描述:当Racket对字符串执行regexp-match
时,它会将字符串的大部分转换为字节字符串(UTF-8)以进行搜索。此函数是在C中实现的核心函数。regexp-split
重复使用它来查找所有匹配项,因此不断重新执行此转换。我正在寻找一种更好的方法,但在修复之前,请使用上述解决方法。
答案 1 :(得分:4)
现在已在最新的Git HEAD版本的Racket中修复,请参阅:github.com/plt/racket/commit/8eefaba。您的示例现在在0.1秒内运行。