【问题标题】:Why is this Haskell code so slow?为什么这个 Haskell 代码这么慢?
【发布时间】:2017-01-10 00:25:48
【问题描述】:

我对 Haskell 有点陌生,并尝试制作一个拼字游戏求解器。它接收您当前拥有的字母,找到它们的所有排列并过滤掉那些字典单词。代码很简单:

import Data.List

main = do
    dict    <- readFile "words"
    letters <- getLine
    let dictWords = words dict
    let perms = permutations letters
    print [x | x <- perms, x `elem` dictWords]

但是,与我使用 Python 实现的非常相似的实现相比,它的速度非常慢。有什么基本的我做错了吗?

*edit:这是我的 Python 代码:

from itertools import permutations

letters = raw_input("please enter your letters (without spaces): ")

d = open('words')
dictionary = [line.rstrip('\n') for line in d.readlines()]
d.close()

perms = ["".join(p) for p in permutations(letters)]

validWords = []

for p in perms:
    if p in dictionary: validWords.append(p)


for validWord in validWords:
    print validWord

我没有精确计时,但大致感觉 Python 实现的速度大约是 Haskell 实现的 2 倍。也许我不应该说 Haskell 代码相比之下“非常慢”,但由于 Haskell 是静态类型的,我想我只是认为它应该快得多,而且根本不比 Python 慢。

【问题讨论】:

  • 你能发布 Python 代码和一些基准测试吗?
  • words dict 只是一个列表,elem 正在对列表执行顺序搜索。
  • 字符串是 Haskell 中的链表。使用文本类型。
  • 我不确定为什么这会被如此强烈地反对。对于初学者来说,这是一个合理的问题。这里没有真的足够的信息来给出有意义的答案,因为很大程度上取决于您如何运行此代码。但是您可以进行一些高级别的改进,例如使用TextSet。为什么这与等效的 Python 解决方案具有不同的性能特征的问题非常有趣,如果您发布可以帮助我们解决问题的 Python 代码。
  • 答案当然是“因为你使用了错误的数据结构”。

标签: python haskell optimization language-comparisons


【解决方案1】:

检查x 是否是dictWords 的元素可能会很慢。我会假设您类似的 python 实现将dictWords 存储在一个集合或排序向量中(在后一种情况下使用二进制搜索)?看起来你可能想在这里做同样的事情。

使用this word list和下面的代码,Python版本运行时间约为30秒,Haskell版本运行时间为1.5分钟。所以 Haskell 比较慢(可能是因为它使用了一个链表,在所有条件相同的情况下,它的迭代速度较慢),但与 Python 相比,我不会称它为“非常慢”。在任一版本中切换使用一组可将时间缩短到 1 秒以下。

from itertools import permutations
f = open('twl06.txt')
words = f.read().split()

print [''.join(p) for p in permutations('apricot') if ''.join(p) in words]

这是基于集合的 Haskell 代码:

import Data.Set
import Data.List

main = do
    dict    <- readFile "twl06.txt"
    let letters = "apricot"
    let dictWords = Data.Set.fromList $ words dict
    let perms = permutations letters
    print [x | x <- perms, member x dictWords]

【讨论】:

  • python 代码将字典存储为字符串列表,就像 Haskell 实现一样。在 python 中,为了检查成员资格,我使用“in”函数
  • 嗯,我不知道你的问题的明确答案,但是将 dictWords 存储为一个集合似乎仍然可以解决你的运行时问题
  • 我想我希望 Haskell 比 Python 快,因为它是静态类型的,所以当它慢 3 倍时,我称之为“非常慢”。我的措辞很糟糕,我应该更清楚地了解情况。其他人也建议使用 Set ,它确实提高了运行时间。但是,我仍然很好奇为什么 Haskell 中的列表比 Python 中的列表慢。对于 Python 实现,要查找一个单词,它仍然必须遍历一个列表。 Python 的列表是否比 Haskell 的列表更优化?迭代速度怎么可能这么快?
  • @nilcit 请注意,python lists 是内置的,这意味着它们在 C 中直接实现为“可调整大小的数组”。这意味着对element in sequence 的单个调用将花费单个方法调用的解释开销,然后list.__contains__ 的实现将启动并在底层数组上执行C 循环并从C 调用相等运算符。所以最后 CPython 的 in 与编译语言相比并没有那么多开销,因为大部分工作都是在编译代码中完成的,唯一的开销是泛型比较。
【解决方案2】:

我是 Haskell 的新手,并尝试制作一个拼字游戏求解器。

您可以通过使用更好的算法来显着改善事情。

而不是测试输入字母的每个排列,如果你 首先对它们进行排序,您只能进行一个字典查找并获得 所有可能的单词(字谜)可以由 它们(全部使用)。

下面是将该字典创建为 Data.Map 的代码。 创建地图需要启动成本,但之后 第一次查询后续查找速度非常快。

import Data.List
import qualified Data.Map.Strict as Map
import Control.Monad
import System.IO

main = do
  contents <- readFile "words"
  let pairs = [ (sort w, [w]) | w <- words contents ]
      dict = foldl' (\m (k,v) -> Map.insertWith (++) k v m) Map.empty pairs
      -- dict = foldr (\(k,v) m -> Map.insertWith (++) k v m) Map.empty pairs
  forever $ do
    putStr "Enter letters: " >> hFlush stdout
    letters <- getLine
    case Map.lookup (sort letters) dict of
      Nothing -> putStrLn "No words."
      Just ws -> putStrLn $ "Words: " ++ show ws

236K 字 (2.5 MB) 的 word 文件的地图创建时间约为 4-5 秒。使用 ByteStrings 或 Text 代替 Strings 可能会获得更好的性能。

尝试一些好的字母组合:

steer rat tuna lapse groan neat

注意:使用 GHC 7.10.2 我发现此代码在不使用 -O2 编译的情况下执行得最好。

【讨论】:

  • 非常感谢您的回复!实际上,我确实尝试了一种与您提供的解决方案非常相似的解决方案——对输入和字典中的单词进行排序,并以这种方式检查字谜。我使用了 Set 结构并使用 Set.member 函数检查了成员资格。该实现实际上并没有真正显着改善我的运行时间。但是,在初始化之后,您的实现速度非常快!我一定会在地图上学习。再次感谢您的意见 - 作为该语言的新手,我非常感谢您的帮助!
  • 作为后续 - 当我在我的代码中包含一个永久行(我对输入和字典单词进行排序的那个)时,第一个之后的查询是即时的。我猜这是因为懒惰的评估?在代码中,直到第一个查询才真正创建字典,当它真正需要它时,但在它已经存在于后续查询之后?
  • 没错。但是,您必须小心 forever 以及编译器版本和选项 - 有时会为每次迭代重新计算映射。如果不重新计算地图,则第二次和后续查找是即时的。
  • 虽然它对于这项工作可能足够快,但如果您使用字符串(任何格式)作为键,Data.Map 是一个非常糟糕的数据结构。 HashMap 可能会更好,但更像 trie 的东西似乎最好。
猜你喜欢
  • 2015-02-26
  • 2011-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-28
  • 2011-04-24
相关资源
最近更新 更多