Haskell的勾股三元组表现出令人难以置信的惊人性能

时间:2019-02-07 16:06:43

标签: performance haskell

假设我们有一个简单的Haskell函数,可以生成勾股三元组:

pytha :: [(Int, Int, Int)]
pytha = [(x, y, z)
        | z <- [0..]
        , x <- [1..z]
        , y <- [x..z]
        , x * x + y * y == z * z
        ]

,我们希望确定生产前100个三元组所需的时间。因此(使用criterion库并假设import Criterion.Main),我们有以下基准:

main :: IO ()
main = do
  countStr <- readFile "count.txt"
  defaultMain [ bgroup "pytha" [ bench countStr $ nf (`take` pytha) (read countStr) ] ]

我们甚至从文件中读取count,以确保ghc在编译期间不会尝试评估pytha

进行echo 100 > count.txt,用-O2编译基准并在我的机器(4.0 GHz Sandy Bridge CPU)上运行时显示了一些有趣的数字:

time                 967.4 ns   (957.6 ns .. 979.3 ns)
                     0.999 R²   (0.998 R² .. 0.999 R²)
mean                 979.6 ns   (967.9 ns .. 995.6 ns)
std dev              45.34 ns   (33.96 ns .. 60.29 ns)

稍微修改该程序以显示总体上考虑了多少个三元组(方法是先生成所有三元组,然后用[0..]压缩列表,然后过滤掉所有非毕达哥拉斯三元组,然后查看生成的三元组的索引)表明已考虑了将近900000个三元组。

所有这些自然地引发​​了一个问题:上面的代码如何在漂亮的标准CPU的单核上实现1000个triple / ns?还是仅仅是我的基准测试错了?

1 个答案:

答案 0 :(得分:2)

您需要使用将要记忆的函数而不是值。

pytha :: Int -> [(Int, Int, Int)]
pytha z_max =
    [ (x, y, z)
    | z <- [0..z_max]
    , x <- [1..z]
    , y <- [x..z]
    , x * x + y * y == z * z
    ]

GHC不会变得足够聪明,无法从常量列表中将其纳入takeWhile,因此它应该提供有意义的基准。只要确保Criterion负责传递z_max,您就可以合理地将其设置为maxBound :: Int或类似的东西。

顺便说一句:通过使用浮点运算为y计算更严格的界限,可以使实现的速度降低很多。