使用向量的风格与性能
代码如下:
{-# 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
被翻译成。