Optimization 在Haskell中进行有效的数值计算

Optimization 在Haskell中进行有效的数值计算,optimization,haskell,numerics,Optimization,Haskell,Numerics,我受到了这篇名为“”的帖子的启发,来看看他在Haskell中提出的问题(从一个向量中求出几百万个数字的和),并与他的结果进行比较 我是一个Haskell新手,所以我真的不知道如何正确计时或如何有效地进行计时,我对这个问题的第一次尝试如下。请注意,我没有在向量中使用随机数,因为我不知道如何以一种好的方式使用。我也在打印资料,以确保全面评估 import System.TimeIt import Data.Vector as V vector :: IO (Vector Int) vector

我受到了这篇名为“”的帖子的启发,来看看他在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
import System.TimeIt
导入数据。向量为V
向量::IO(向量Int)
向量=do
设vec=V.10,复制3000000
打印$V.length向量
返回向量
sumit::IO()
sumit=do

vec尝试使用一个未绑定的向量,尽管我不确定它在这种情况下是否有明显的区别。还要注意,这种比较有点不公平,因为向量包应该完全优化向量(这种优化称为流融合)。

首先,要认识到GHCi是一个解释器,它的设计速度不是很快。为了获得更有用的结果,您应该编译启用优化的代码。这可以带来巨大的不同

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

我修改了您的代码以使用Criteria并删除了打印语句,这样我们就不会对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]
所以我得到的平均值刚刚超过9毫秒,标准偏差不到一毫秒。对于更大的测试用例,我得到大约100ms

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


通过使用
-fllvm
选项来试验新的基于LLVM的代码生成器也是值得的

您的原始文件,未编译,然后未经优化编译,然后使用简单优化标志编译:

$ 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.unbox作为V
而不是
import qualified Data.Vector作为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.unbox

如果使用足够大的向量,则向量unbox可能变得不切实际。对于我来说,如果向量大小>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
Edit:我使用criteria重复了基准测试,并使
sumit
pure。代码和结果如下所示:

代码:

结果:

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
产生了很大的不同,这是意料之中的

小心你测量的东西。目前,您正在测量向量的分配和求和。不要使用ghci进行性能测试。使用ghc--make-O2.
ique
,查看关于使用
向量
包的优秀教程:您是使用优化编译的吗?对于你的版本,我得到了相同的比率,4:60,即使是100倍的数字。是的,我用
ghc--make-O2
编译。
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