{-# 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
和-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秒(对f
进行内联也没有改变任何内容)。
我还注意到,如果我使f
成为多态函数(使用上述三种签名之一),即使使用“快速”定义(即2或3),它的运行时间也会变慢...恰好为1.7秒。这让我想知道是否由于(缺乏)类型推断而导致了原始问题,尽管我明确给出了Vector类型和元素类型的类型。
我还有兴趣添加模q
的整数:
newtype Zq q i = Zq {unZq :: i}
就像添加Int64
一样,如果我编写一个指定每个类型的函数:
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
我在Vector库页面上提交了问题。我找到了一个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
和 -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
第一种版本与原始版本相同,第二个版本运行时间为0.111秒,版本3-6在不到0.09秒内运行(并且对 f
进行 INLINING
没有任何改变)。
因此,数量级的减速似乎是由于惰性引起的,因为 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
组合有帮助,而使用严格的iterate
却没有帮助? - 为什么
U.force
比DeepSeq.force
更差?我不知道U.force
应该做什么,但它听起来很像DeepSeq.force
,并且似乎具有类似的效果。 - 为什么泛型化会使我变慢一个数量级,而不受泛型化的影响(即在向量类型、
Num
类型或两者中的任何一个中)? - 为什么版本5和6,它们都不应该具有任何严格性影响,与严格函数一样快?
正如 @kqr 指出的那样,问题似乎并不是严格性。因此,我编写函数的方式导致使用通用的 zipWith
而不是特定于 Unboxed 的版本。这只是 GHC 和 Vector 库之间的偶然事件,还是有更普遍的东西可以在这里说吗?
Vector.force
的内容(http://hackage.haskell.org/package/vector-0.10.9.1/docs/Data-Vector-Unboxed.html),它与`DeepSeq.force`不同。 - ollantaO2
在那里时会做某些事情,但也在id
存在时做同样的事情。我的意思是第三个问题有误导性,因为Vector.force
和DeepSeq.force
是两个无关的函数,除了当应用于向量时它们的名称和类型签名相同。 - ollantascc-pragma
),或者稍微调整代码以获得所需的详细信息。在这里查看核心和正在触发的规则可能是最有用的。祝好运! - jberrymanINLINE
命令的主要功能之一就是导出接口文件中的展开代码,因为这对于内联是必需的。当然,在一些情况下函数无法被内联,但模块边界并不是障碍。 - John L