动态编程 - 斐波纳契 - 列表的性能优于数组

时间:2018-03-12 00:47:01

标签: arrays list performance haskell optimization

我正在玩Haskell和动态编程。

我已经实现了很多问题,但在Fibonacci的情况下,我得到了一些PC家属的结果,我想确认一下。

假设以下实施:

1) - 列表:

insertIndex

2) - 数组:

memoized_fib_list n = fibTab !! n
   where fibTab = map fibm [0 ..]
         fibm 0 = 0
         fibm 1 = 1
         fibm n = fibTab !! (n-2) + fibTab !! (n-1)

结果(带标准):

  1. N = 15.000:
    清单实施:171.5μs
    数组实现:8.782 ms

  2. N = 100.000:
    清单实施:2.289毫秒
    数组实现:195.7 ms

  3. N = 130.000:
    清单实施:3.708 ms
    数组实现:410.4 ms

  4. 测试是在配备Core i7 Skylake,8gb DDR4和SSD(Ubuntu)的笔记本电脑上进行的。

    我期待数组实现更好,这是列表实现更好的唯一问题。

    可能是因为顺序访问?在某些规格较低的硬件上,列表实现的性能较差。

    注意:我正在使用GHC的最后一个版本(编辑:最新版本)。

    感谢。

    编辑:

    memoized_fib_array n = fibTab ! n
      where fibTab = listArray (0, n) [mfib x | x <- [0..n]]
            mfib 0 = 0
            mfib 1 = 1
            mfib x = fibTab ! (x - 1) + fibTab ! (x - 2)
    

    Edit2:我在Windows 10 PC上安装了Haskell Platform 8.2.2,结果非常相似。

    Intel i5 6600K,16GB DDR4,SSD。

    benchmark n = defaultMain [
        bgroup "fibonacci" [ 
                       bench "memoized_fib_list" $ whnf (memoized_fib_list) n
                     , bench "memoized_fib_array" $ whnf (memoized_fib_array) n
                     ]
                    ]
    
    main = do
    {
        putStrLn "--------------EJECUTANDO BENCHMARK N=40------------------";
        benchmark 40;
        putStrLn "--------------EJECUTANDO BENCHMARK N=15000---------------";
        benchmark 15000;
        putStrLn "--------------EJECUTANDO BENCHMARK N=50000---------------";
        benchmark 50000;
        putStrLn "--------------EJECUTANDO BENCHMARK N=100000--------------";
        benchmark 100000;
        putStrLn "--------------EJECUTANDO BENCHMARK N=130000--------------";
        benchmark 130000;
    }
    

    Edit3:使用线性回归运行标准后的一些其他信息。所有值对应于N = 130000的执行。

    - 垃圾收集量:

    列表实施:

    -------------------EJECUTANDO BENCHMARK N=130000------------------------
    benchmarking best algo/memoized_fib_list
    time                 1.818 ms   (1.774 ms .. 1.855 ms)
                         0.993 R²   (0.985 R² .. 0.998 R²)
    mean                 1.853 ms   (1.826 ms .. 1.904 ms)
    std dev              119.2 μs   (84.15 μs .. 191.3 μs)
    variance introduced by outliers: 48% (moderately inflated)
    
    benchmarking best algo/memoized_fib_array
    time                 139.8 ms   (63.05 ms .. 221.8 ms)
                         0.884 R²   (0.623 R² .. 1.000 R²)
    mean                 287.0 ms   (221.4 ms .. 353.0 ms)
    std dev              83.83 ms   (64.91 ms .. 101.6 ms)
    variance introduced by outliers: 78% (severely inflated)
    

    阵列实施:

    numGcs:              NaN R²     (NaN R² .. NaN R²)
      iters              0.000      (0.000 .. 0.000)
      y                  0.000      (0.000 .. 0.000)
    

    -Bytes已分配:

    列表实施:

    numGcs:              1.000 R²   (1.000 R² .. 1.000 R²)
      iters              739.000    (739.000 .. 739.000)
      y                  2.040e-12  (-3.841e-12 .. 2.130e-12)
    

    阵列实施:

    allocated:           0.001 R²   (0.000 R² .. 0.089 R²)
      iters              1.285      (-9.751 .. 13.730)
      y                  2344.014   (1748.809 .. 2995.439)
    

    -CPU周期:

    列表实施:

    allocated:           1.000 R²   (1.000 R² .. 1.000 R²)
      iters              7.586e8    (7.586e8 .. 7.586e8)
      y                  1648.000   (1648.000 .. NaN)
    

    阵列实施:

    cycles:              0.992 R²   (0.984 R² .. 0.997 R²)
      iters              6759303.406 (6579945.392 .. 6962148.091)
      y                  -141047.582 (-4701325.840 .. 4674847.149)
    

1 个答案:

答案 0 :(得分:1)

这里发生的事情非常简单:使用-O2,GHC决定在memoized_fib_list全球范围内制作记忆列表。

$ ghc -fforce-recomp wtmpf-file4545.hs -O2 -ddump-prep

...

Main.memoizedFib_list :: GHC.Types.Int -> GHC.Integer.Type.Integer
[GblId, Arity=1, Str=<S(S),1*U(U)>, Unf=OtherCon []]
Main.memoizedFib_list
  = \ (n_sc61 [Occ=Once] :: GHC.Types.Int) ->
      GHC.List.!! @ GHC.Integer.Type.Integer Main.main_fibTab n_sc61

...

Main.main_fibTab :: [GHC.Integer.Type.Integer]
[GblId]
Main.main_fibTab
  = case Main.$wgo 0# of
    { (# ww1_sc5M [Occ=Once], ww2_sc5N [Occ=Once] #) ->
    GHC.Types.: @ GHC.Integer.Type.Integer ww1_sc5M ww2_sc5N
    }

...

这意味着,您的标准基准测试实际上并不重复评估斐波纳契函数 - 它只是在同一个全局列表中执行重复查找。并且在许多评估中取平均值,这给出了非常好的分数,但这并不代表计算的速度。

GHC在列表实现中执行此优化,因为您不需要不同长度的列表 - 它始终是所有 Fibonacci数的无限列表。这在阵列实现中是不可能的,所以这不能跟上来。

防止这种全球化的简单方法是让fibm显式依赖于n,只需将其修剪为所需的有限长度,就像阵列一样。

memoizedFib_list :: Int -> Integer
memoizedFib_list n = fibTab !! n
   where fibTab = map fibm [0 ..]
         fibm 0 = 0
         fibm 1 = 1
         fibm n = fibTab !! (n-2) + fibTab !! (n-1)

有了这个,列表实现变得比数组慢得多,正如人们所期望的那样,列表的备忘录是 O n ):

$ ghc -fforce-recomp wtmpf-file4545.hs -O2 && ./wtmpf-file4545 
[1 of 1] Compiling Main             ( wtmpf-file4545.hs, wtmpf-file4545.o )
Linking wtmpf-file4545 ...
--------------EJECUTANDO BENCHMARK N=40------------------
benchmarking fibonacci/memoizedFib_list
time                 10.47 μs   (10.42 μs .. 10.51 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 10.40 μs   (10.35 μs .. 10.44 μs)
std dev              163.3 ns   (122.2 ns .. 225.8 ns)
variance introduced by outliers: 13% (moderately inflated)

benchmarking fibonacci/memoizedFib_array
time                 1.618 μs   (1.617 μs .. 1.620 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 1.620 μs   (1.618 μs .. 1.623 μs)
std dev              7.521 ns   (4.079 ns .. 12.48 ns)

benchmarking fibonacci/memoizedFib_vector
time                 1.573 μs   (1.572 μs .. 1.574 μs)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 1.572 μs   (1.571 μs .. 1.573 μs)
std dev              2.351 ns   (1.417 ns .. 4.040 ns)

--------------EJECUTANDO BENCHMARK N=1500----------------
benchmarking fibonacci/memoizedFib_list
time                 18.52 ms   (18.41 ms .. 18.68 ms)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 18.65 ms   (18.53 ms .. 18.84 ms)
std dev              355.1 μs   (204.8 μs .. 592.1 μs)

benchmarking fibonacci/memoizedFib_array
time                 135.2 μs   (131.2 μs .. 140.1 μs)
                     0.996 R²   (0.991 R² .. 1.000 R²)
mean                 132.7 μs   (131.9 μs .. 135.0 μs)
std dev              4.463 μs   (2.024 μs .. 8.327 μs)
variance introduced by outliers: 32% (moderately inflated)

benchmarking fibonacci/memoizedFib_vector
time                 131.8 μs   (130.6 μs .. 133.2 μs)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 132.5 μs   (131.4 μs .. 134.1 μs)
std dev              4.383 μs   (3.463 μs .. 5.952 μs)
variance introduced by outliers: 31% (moderately inflated)
我在这里测试过的

Vector表现得更快,但并不是很重要。我认为只要你使用带有 O (1)查找的容器,性能就会受到相当大数量的增加的支配,所以你真的要对GMP进行基准测试,而不是Haskell必须做的任何事情。用。

import qualified Data.Vector as V

memoizedFib_vector :: Int -> Integer
memoizedFib_vector n = fibTab V.! n
  where fibTab = V.generate (n+1) mfib
        mfib 0 = 0
        mfib 1 = 1
        mfib x = fibTab V.! (x - 1) + fibTab V.! (x - 2)