Haskell 使用向量的风格与性能

Haskell 使用向量的风格与性能,haskell,lambda,pointfree,Haskell,Lambda,Pointfree,代码如下: {-# LANGUAGE FlexibleContexts #-} import Data.Int import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Generic as V {-# NOINLINE f #-} -- Note the 'NO' --f :: (Num r, V.Vector v r) => v r -> v r -> v r --f :: (V.Vec

代码如下:

{-# LANGUAGE FlexibleContexts #-}

import Data.Int
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Generic as V

{-# NOINLINE f #-} -- Note the 'NO'
--f :: (Num r, V.Vector v r) => v r -> v r -> v r
--f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
--f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
f = V.zipWith (+) -- or U.zipWith, it doesn't make a difference

main = do
    let iters = 100
        dim = 221184
        y = U.replicate dim 0 :: U.Vector Int64
    let ans = iterate ((f y)) y !! iters
    putStr $ (show $ U.sum ans)
{-# LANGUAGE FlexibleContexts #-}

import Control.DeepSeq
import Data.Int
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Generic as V

{-# NOINLINE f #-} -- Note the 'NO'
--f :: (Num r, V.Vector v r) => v r -> v r -> v r
--f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
--f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
f = V.zipWith (+)

main = do
    let iters = 100
        dim = 221184
        y = U.replicate dim 0 :: U.Vector Int64
    let ans = iterate ((f y)) y !! iters
    putStr $ (show $ U.sum ans)
我用
GHC7.6.2
-O2
编译,运行了1.7秒

我尝试了几种不同版本的
f

  • fx=U.zip带(+)x
  • fx=(U.zipWith(+)x)。id
  • f x y=U.zip带(+)x y
  • 版本1与原始版本相同,而版本2和版本3在0.09秒内运行(并且
    内联
    f
    不会改变任何内容)

    我还注意到,如果我使
    f
    多态(使用上面三个签名中的任何一个),即使使用“快速”定义(即2或3),它也会减慢…精确到1.7秒。这让我想知道最初的问题是否是由于(缺乏)类型推断,即使我明确给出了向量类型和元素类型的类型

    我还对以q为模添加整数感兴趣:

    newtype Zq q i = Zq {unZq :: i}
    
    与添加
    Int64
    s时一样,如果我使用指定的每种类型编写函数

    h :: U.Vector (Zq Q17 Int64) -> U.Vector (Zq Q17 Int64) -> U.Vector (Zq Q17 Int64)
    
    与保留任何多态性相比,我获得了一个数量级的性能

    h :: (Modulus q) => U.Vector (Zq q Int64) -> U.Vector (Zq q Int64) -> U.Vector (Zq q Int64)
    
    但我至少应该能够删除特定的幻影类型!它应该被编译出来,因为我正在处理一个
    newtype

    以下是我的问题:

  • 经济放缓从何而来
  • f
    的第2版和第3版中发生了什么以任何方式影响性能的情况?对我来说,(相当于)编码风格可能会影响这样的性能,这似乎是一个bug。在Vector之外,是否有其他部分应用功能或其他风格选择会影响性能的示例
  • 为什么多态性会使我的速度降低一个数量级,而与多态性的位置无关(即在向量类型中,在
    Num
    类型中,两者都是,或幻影类型)?我知道多态性会使代码变慢,但这很荒谬。这附近有黑客吗
  • 编辑1

    我在矢量库页面上提交了一个文件。我发现了一个与这个问题有关的问题

    编辑2

    在从@kqr的回答中获得一些见解之后,我重写了这个问题。 以下是原件供参考

    --------------原始问题--------------------

    代码如下:

    {-# LANGUAGE FlexibleContexts #-}
    
    import Data.Int
    import qualified Data.Vector.Unboxed as U
    import qualified Data.Vector.Generic as V
    
    {-# NOINLINE f #-} -- Note the 'NO'
    --f :: (Num r, V.Vector v r) => v r -> v r -> v r
    --f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
    --f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
    f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
    f = V.zipWith (+) -- or U.zipWith, it doesn't make a difference
    
    main = do
        let iters = 100
            dim = 221184
            y = U.replicate dim 0 :: U.Vector Int64
        let ans = iterate ((f y)) y !! iters
        putStr $ (show $ U.sum ans)
    
    {-# LANGUAGE FlexibleContexts #-}
    
    import Control.DeepSeq
    import Data.Int
    import qualified Data.Vector.Unboxed as U
    import qualified Data.Vector.Generic as V
    
    {-# NOINLINE f #-} -- Note the 'NO'
    --f :: (Num r, V.Vector v r) => v r -> v r -> v r
    --f :: (V.Vector v Int64) => v Int64 -> v Int64 -> v Int64
    --f :: (U.Unbox r, Num r) => U.Vector r -> U.Vector r -> U.Vector r
    f :: U.Vector Int64 -> U.Vector Int64 -> U.Vector Int64
    f = V.zipWith (+)
    
    main = do
        let iters = 100
            dim = 221184
            y = U.replicate dim 0 :: U.Vector Int64
        let ans = iterate ((f y)) y !! iters
        putStr $ (show $ U.sum ans)
    
    我用
    GHC7.6.2
    -O2
    编译,运行了1.7秒

    我尝试了几种不同版本的
    f

  • fx=U.zip带(+)x
  • fx=(U.zipWith(+)x)。美国部队
  • fx=(U.zipWith(+)x)。控制。深度顺序力)
  • fx=(U.zipWith(+)x)。(\z->z`seq`z)
  • fx=(U.zipWith(+)x)。id
  • f x y=U.zip带(+)x y
  • 版本1与原始版本相同,版本2在0.111秒内运行,版本3-6在0.09秒内运行(并且
    内联
    f
    不会改变任何内容)

    因此,数量级的减速似乎是由于懒惰,因为
    force
    起到了帮助作用,但我不确定懒惰是从哪里来的。未绑定的类型不允许懒惰,对吗

    我试图编写一个严格版本的
    iterate
    ,认为向量本身一定是惰性的:

    {-# INLINE iterate' #-}
    iterate' :: (NFData a) => (a -> a) -> a -> [a]
    iterate' f x =  x `seq` x : iterate' f (f x)
    
    但是对于无点版本的
    f
    ,这一点都没有帮助

    我还注意到了其他一些事情,这可能只是巧合和转移注意力: 如果我使
    f
    多态(使用上面三个签名中的任何一个),即使使用“快速”定义,它也会减慢速度…精确到1.7秒。这让我想知道,最初的问题是否是由于(缺乏)类型推断,即使一切都应该很好地推断出来

    以下是我的问题:

  • 经济放缓从何而来
  • 为什么使用
    组合会有帮助,而使用严格的
    迭代则没有帮助
    
  • 为什么
    U.force
    DeepSeq.force
    更糟糕?我不知道U.force
  • 应该做什么,但它听起来很像
    DeepSeq.force
    ,似乎也有类似的效果
  • 为什么多态性会使我的速度降低一个数量级,而与多态性的位置无关(即在向量类型中,在
    Num
    类型中,或者两者都有)
  • 为什么版本5和版本6(两者都不应该有任何严格含义)与严格函数一样快

  • 正如@kqr所指出的,问题似乎并不在于严格。因此,我编写函数的方式导致使用泛型
    zipWith
    ,而不是未绑定的特定版本。这仅仅是GHC和向量库之间的侥幸,还是这里有更一般的说法?

    虽然我没有你想要的确切答案,但有两件事可能会对你有所帮助

    第一件事是
    x`seq`x
    在语义和计算上与
    x
    是一样的。维基上说的是关于
    seq

    关于
    seq
    的一个常见误解是
    seq x
    “评估”
    x
    。嗯,有点
    seq
    不会仅仅凭借源文件中的存在来计算任何内容,它所做的只是引入一个值对另一个值的人工数据依赖关系:当计算
    seq
    的结果时,还必须计算第一个参数(某种程度上;参见下文)

    例如,假设
    x::Integer
    ,则
    seq x b
    的行为本质上类似于
    如果x==0,则b else b
    –无条件地等于
    b
    ,但一路上强制执行
    x
    。特别是,表达式
    x`seq`x
    是完全冗余的,并且始终具有与仅写入
    x
    完全相同的效果

    第一段说的是,写
    seqabb
    并不意味着
    a
    会变魔术
    {-# LANGUAGE BangPatterns #-}
    iterate' f !x = x : iterate f (f x)