使用向量的风格与性能
代码如下:
{-# 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 :
fx = U.zipWith (+) x fx = (U.zipWith (+) x) . id fxy = U.zipWith (+) xy 版本1与原始版本相同,而版本2和版本3在0.09秒内运行(并且INLINING f不会改变任何内容)。
我也注意到,如果我做f多态性(与以上三种签名),即使有一个“快”的定义(即2或3),它减缓回落...准确1.7秒。 这让我想知道原始问题是否可能归因于(缺少)类型推断,尽管我明确地给出了Vector类型和元素类型的类型。
我也有兴趣添加整数modulo 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会以什么方式影响性能? 这似乎是一个错误,(编码风格)会影响这样的性能。 除矢量之外,部分应用功能或其他风格选择是否会影响性能? Num类型,两者还是幻像类型)? 我知道多态使代码变慢,但这很荒谬。 它周围有黑客吗? 编辑1
我向Vector库页面提出了一个问题。 我发现与这个问题有关的GHC问题。
EDIT2
从@ 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 :
fx = U.zipWith (+) x fx = (U.zipWith (+) x) . U.force fx = (U.zipWith (+) x) . Control.DeepSeq.force) fx = (U.zipWith (+) x) . (z -> z `seq` z) fx = (U.zipWith (+) x) . id fxy = U.zipWith (+) xy 版本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合成帮助,但使用严格的iterate不? U.force比DeepSeq.force更糟? 我不知道U.force应该做什么,但听起来很像DeepSeq.force ,似乎也有类似的效果。 Num类型中还是在两者中)? 正如@kqr指出的那样,这个问题似乎并不严格。 所以我写这个函数的方式会导致使用通用的zipWith而不是zipWith特定的版本。 这仅仅是GHC和Vector库之间的一种偶然,还是可以在这里说一些更一般的东西?
虽然我没有你想要的明确答案,但有两件事可以帮助你。
首先, x `seq` x在语义和计算上都与x相同。 该wiki说seq :
关于seq的常见误解是seq x “评估” x 。 好吧,有点。 seq并不仅仅依靠源文件中存在的东西来评估任何东西,它所做的只是在另一个值上引入一个数值的仿真数据依赖性:当seq的结果被评估时,第一个参数也必须(有点;见下文)进行评估。
举个例子,假设x :: Integer ,然后seq xb行为基本上类似于if x == 0 then b else b - 无条件等于b ,但强制x沿途。 特别是, x `seq` x这个表达式是完全多余的,并且总是和写x一样具有完全相同的效果。
什么第一段说的是,写作seq ab并不意味着a会奇迹般地得到评估这一刻,就意味着a如将得到尽快评估b需要进行评估。 这可能会在程序的后面发生,也可能永远不会发生。 当你从这个角度来看时,显然seq xx是一个冗余,因为它所说的只是“一旦x需要被评估就评估x ”。 无论如何,如果你只写了x ,那当然会发生什么。
这对你有两个影响:
你的“严格的” iterate'函数并不比没有seq更严格。 事实上,我很难想象iterate函数如何变得比现在更严格。 你不能严格限制列表的尾部,因为它是无限的。 你可以做的主要事情是强制使用“累加器” fx ,但这样做并不会给我的系统带来任何显着的性能提升[1]。
抓住那个。 你的严格iterate'和我的爆炸模式版本完全一样。 查看评论。
写作(z -> z `seq` z)并没有给你一个严格的身份识别功能,我认为这就是你要做的。 事实上,普通的身份识别功能就像你会得到的那样严格 - 它会在需要时立即评估其结果。
然而,我偷看了GHC核心的核心
U.zipWith (+) y
和
U.zipWith (+) y . id
而且我的未经训练的眼睛只能看到一个很大的区别。 第一个使用的只是一个普通的Data.Vector.Generic.zipWith (这里是你的多态性巧合可能会发挥作用的地方 - 如果GHC选择一个通用的zipWith它当然会执行,就好像代码是多态的!),而后者爆炸了这个单个函数调用几乎90行的状态monad代码和解压缩的机器类型。
状态monad代码看起来几乎就像用命令式语言编写的循环和破坏性更新,所以我认为它适合运行的机器。 如果我不那么着急,我会花更长的眼光来看看它是如何工作的,以及为什么GHC突然决定需要一个紧密的循环。 我和其他任何想看看的人一样,为我自己附加了生成的核心。[2]
[1]:沿途强制累加器:(这是你已经做的事,我误解了代码!)
{-# LANGUAGE BangPatterns #-}
iterate' f !x = x : iterate f (f x)
[2]:什么核心U.zipWith (+) y . id U.zipWith (+) y . id被翻译成。
