这是代码:
{-# 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)
ghc 7.6.2
我用and编译,-O2
运行了 1.7 秒。
我尝试了几个不同的版本f
:
f x = U.zipWith (+) x
f x = (U.zipWith (+) x) . id
f x y = U.zipWith (+) x y
版本 1 与原始版本相同,而版本 2 和 3 的运行时间不到 0.09 秒(并且INLINING
f
没有任何改变)。
我还注意到,如果我进行f
多态(使用上述三个签名中的任何一个),即使使用“快速”定义(即 2 或 3),它也会减慢……精确到 1.7 秒。这让我想知道最初的问题是否可能是由于(缺乏)类型推断,即使我明确地给出了 Vector 类型和元素类型的类型。
我也有兴趣添加整数模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
.
以下是我的问题:
- 减速来自哪里?
- 版本 2 和 3 中发生了什么
f
会以任何方式影响性能?对我来说,(相当于)编码风格会影响这样的性能似乎是一个错误。在 Vector 之外是否还有其他示例,其中部分应用函数或其他样式选择会影响性能? - 为什么多态性会使我减慢一个数量级,而与多态性的位置无关(即在向量类型中、在
Num
类型中、两者中或幻像类型中)?我知道多态性会使代码变慢,但这很荒谬。它周围有黑客吗?
编辑 1
我向 Vector library 页面提出了问题。我发现了一个与此问题相关的GHC问题。
编辑2
在从@kqr 的回答中获得一些见解后,我重写了这个问题。以下是原文供参考。
--------------原始问题--------
这是代码:
{-# 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)
ghc 7.6.2
我用and编译,-O2
运行了 1.7 秒。
我尝试了几个不同的版本f
:
f x = U.zipWith (+) x
f x = (U.zipWith (+) x) . U.force
f x = (U.zipWith (+) x) . Control.DeepSeq.force)
f x = (U.zipWith (+) x) . (\z -> z `seq` z)
f x = (U.zipWith (+) x) . id
f x y = U.zipWith (+) x y
版本 1 与原始版本相同,版本 2 运行时间为 0.111 秒,版本 3-6 运行时间不到 0.09 秒(并且INLINING
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 秒。这让我想知道最初的问题是否可能是由于(缺乏)类型推断,即使一切都应该很好地推断出来。
以下是我的问题:
- 减速来自哪里?
- 为什么使用
force
help 编写,但使用 strictiterate
不使用? - 为什么
U.force
比不上DeepSeq.force
?我不知道U.force
应该做什么,但听起来很像DeepSeq.force
,并且似乎有类似的效果。 - 为什么多态性会使我减慢一个数量级,而与多态性的位置无关(即在向量类型中,在
Num
类型中,或两者兼而有之)? - 为什么版本 5 和 6,它们都不应该有任何严格的含义,就像严格的函数一样快?
正如@kqr 指出的那样,问题似乎不在于严格性。因此,我编写函数的方式导致使用泛型zipWith
而不是未装箱的特定版本。这只是 GHC 和 Vector 库之间的侥幸,还是可以在这里说一些更笼统的东西?