11

我受到这篇名为“只有快速语言才有趣”的帖子的启发,以查看他在 Haskell 中提出的问题(从向量中求和数百万个数字)并与他的结果进行比较。

我是 Haskell 新手,所以我真的不知道如何正确计时或如何有效地做到这一点,我对这个问题的第一次尝试如下。请注意,我没有在向量中使用随机数,因为我不确定如何以一种好的方式去做。我也在打印东西以确保全面评估。

import System.TimeIt

import Data.Vector as V

vector :: IO (Vector Int)
vector = do
  let vec = V.replicate 3000000 10
  print $ V.length vec
  return vec

sumit :: IO ()
sumit = do
  vec <- vector
  print $ V.sum vec

time = timeIt sumit

在 GHCI 中加载并运行time告诉我,运行 300 万个号码大约需要 0.22 秒,运行 3000 万个号码需要 2.69 秒。

与博客作者在 Lush 中 0.02 秒和 0.18 秒的结果相比,这要差得多,这让我相信这可以以更好的方式完成。

注意:以上代码需要 TimeIt 包才能运行。cabal install timeit会给你的。

4

4 回答 4

23

首先,要意识到 GHCi 是一个解释器,它的设计并不是很快。要获得更有用的结果,您应该在启用优化的情况下编译代码。这可以产生巨大的影响。

此外,对于 Haskell 代码的任何严肃基准测试,我建议使用标准。它使用各种统计技术来确保您获得可靠的测量结果。

我修改了您的代码以使用标准并删除了打印语句,这样我们就不会为 I/O 计时。

import Criterion.Main
import Data.Vector as V

vector :: IO (Vector Int)
vector = do
  let vec = V.replicate 3000000 10
  return vec

sumit :: IO Int
sumit = do
  vec <- vector
  return $ V.sum vec

main = defaultMain [bench "sumit" $ whnfIO sumit]

用 编译这个-O2,我在一个相当慢的上网本上得到这个结果:

$ ghc --make -O2 Sum.hs
$ ./Sum 
warming up
estimating clock resolution...
mean is 56.55146 us (10001 iterations)
found 1136 outliers among 9999 samples (11.4%)
  235 (2.4%) high mild
  901 (9.0%) high severe
estimating cost of a clock call...
mean is 2.493841 us (38 iterations)
found 4 outliers among 38 samples (10.5%)
  2 (5.3%) high mild
  2 (5.3%) high severe

benchmarking sumit
collecting 100 samples, 8 iterations each, in estimated 6.180620 s
mean: 9.329556 ms, lb 9.222860 ms, ub 9.473564 ms, ci 0.950
std dev: 628.0294 us, lb 439.1394 us, ub 1.045119 ms, ci 0.950

所以我得到的平均时间刚刚超过 9 毫秒,标准偏差不到一毫秒。对于更大的测试用例,我得到大约 100 毫秒。

在使用包时启用优化尤其重要vector,因为它大量使用流融合,在这种情况下能够完全消除数据结构,将您的程序变成一个高效、紧密的循环。

-fllvm通过使用选项来试验新的基于 LLVM 的代码生成器也可能是值得的。它显然非常适合数字代码

于 2011-12-01T10:32:35.203 回答
14

您的原始文件,未编译,然后在没有优化的情况下编译,然后使用简单的优化标志编译:

$ runhaskell boxed.hs  
3000000
30000000
CPU time:   0.35s

$ ghc --make boxed.hs -o unoptimized 
$ ./unoptimized
3000000
30000000
CPU time:   0.34s



$ ghc --make -O2 boxed.hs 
$ ./boxed
3000000
30000000
CPU time:   0.09s

您的文件使用import qualified Data.Vector.Unboxed as V而不是import qualified Data.Vector as V(Int是不可装箱的类型) - 首先没有优化,然后使用:

$ ghc --make unboxed.hs -o unoptimized
$ ./unoptimized
3000000
30000000
CPU time:   0.27s


$ ghc --make -O2 unboxed.hs 
$ ./unboxed
3000000
30000000
CPU time:   0.04s

所以,编译,优化......并在可能的情况下使用Data.Vector.Unboxed

于 2011-12-01T11:26:44.993 回答
3

如果您使用足够大的向量,Vector Unboxed 可能会变得不切实际。对我来说,纯(惰性)列表更快,如果向量大小 > 50000000:

import System.TimeIt

sumit :: IO ()
sumit = print . sum $ replicate 50000000 10

main :: IO ()
main = timeIt sumit

我得到这些时间:

Unboxed Vectors
CPU time:   1.00s

List:
CPU time:   0.70s

编辑:我已经使用 Criterion 重复了基准测试并将其设为sumit纯。代码和结果如下:

代码:

import Criterion.Main

sumit :: Int -> Int
sumit m = sum $ replicate m 10

main :: IO ()
main = defaultMain [bench "sumit" $ nf sumit 50000000]

结果:

warming up
estimating clock resolution...
mean is 7.248078 us (80001 iterations)
found 24509 outliers among 79999 samples (30.6%)
  6044 (7.6%) low severe
  18465 (23.1%) high severe
estimating cost of a clock call...
mean is 68.15917 ns (65 iterations)
found 7 outliers among 65 samples (10.8%)
  3 (4.6%) high mild
  4 (6.2%) high severe

benchmarking sumit
collecting 100 samples, 1 iterations each, in estimated 46.07401 s
mean: 451.0233 ms, lb 450.6641 ms, ub 451.5295 ms, ci 0.950
std dev: 2.172022 ms, lb 1.674497 ms, ub 2.841110 ms, ci 0.950

print正如预期的那样,它看起来有很大的不同!

于 2011-12-01T13:36:31.890 回答
3

尝试使用未装箱的向量,尽管我不确定在这种情况下它是否会产生显着差异。另请注意,比较有点不公平,因为向量包应该完全优化向量(这种优化称为流融合)。

于 2011-12-01T10:22:34.840 回答