Performance Haskell向量性能与Scala的比较
我在Haskell和Scala中有一段非常简单的代码。此代码旨在以非常紧密的循环运行,因此性能至关重要。问题是Haskell比Scala慢10倍左右。这里是Haskell代码Performance Haskell向量性能与Scala的比较,performance,scala,haskell,Performance,Scala,Haskell,我在Haskell和Scala中有一段非常简单的代码。此代码旨在以非常紧密的循环运行,因此性能至关重要。问题是Haskell比Scala慢10倍左右。这里是Haskell代码 {-# LANGUAGE BangPatterns #-} import qualified Data.Vector.Unboxed as VU newtype AffineTransform = AffineTransform {get :: (VU.Vector Double)} deriving (Show) {
{-# LANGUAGE BangPatterns #-}
import qualified Data.Vector.Unboxed as VU
newtype AffineTransform = AffineTransform {get :: (VU.Vector Double)} deriving (Show)
{-# INLINE runAffineTransform #-}
runAffineTransform :: AffineTransform -> (Double, Double) -> (Double, Double)
runAffineTransform affTr (!x, !y) = (get affTr `VU.unsafeIndex` 0 * x + get affTr `VU.unsafeIndex` 1 * y + get affTr `VU.unsafeIndex` 2,
get affTr `VU.unsafeIndex` 3 * x + get affTr `VU.unsafeIndex` 4 * y + get affTr `VU.unsafeIndex` 5)
testAffineTransformSpeed :: AffineTransform -> Int -> (Double, Double)
testAffineTransformSpeed affTr count = go count (0.5, 0.5)
where go :: Int -> (Double, Double) -> (Double, Double)
go 0 res = res
go !n !res = go (n-1) (runAffineTransform affTr res)
还可以做些什么来改进此代码?我定义了以下严格/非固定对类型:
import System.Random.MWC -- for later
import Control.DeepSeq
data SP = SP {
one :: {-# UNPACK #-} !Double
, two :: {-# UNPACK #-} !Double
} deriving Show
instance NFData SP where
rnf p = rnf (one p) `seq` rnf (two p) `seq` ()
并在runAffineTransform
函数中替换它:
runAffineTransform2 :: AffineTransform -> SP -> SP
runAffineTransform2 affTr !(SP x y) =
SP ( get affTr `U.unsafeIndex` 0 * x
+ get affTr `U.unsafeIndex` 1 * y
+ get affTr `U.unsafeIndex` 2 )
( get affTr `U.unsafeIndex` 3 * x
+ get affTr `U.unsafeIndex` 4 * y
+ get affTr `U.unsafeIndex` 5 )
{-# INLINE runAffineTransform2 #-}
然后运行这个基准测试套件:
main :: IO ()
main = do
g <- create
zs <- fmap (AffineTransform . U.fromList)
(replicateM 100000 (uniformR (0 :: Double, 1) g))
let myConfig = defaultConfig { cfgPerformGC = ljust True }
defaultMainWith myConfig (return ()) [
bench "yours" $ nf (testAffineTransformSpeed zs) 10
, bench "mine" $ nf (testAffineTransformSpeed2 zs) 10
]
完整的代码在要点中
编辑
我还发布了Criteria的输出报告。主要问题是
runAffineTransform affTr (!x, !y) = (get affTr `VU.unsafeIndex` 0 * x
+ get affTr `VU.unsafeIndex` 1 * y
+ get affTr `VU.unsafeIndex` 2,
get affTr `VU.unsafeIndex` 3 * x
+ get affTr `VU.unsafeIndex` 4 * y
+ get affTr `VU.unsafeIndex` 5)
产生一对重击。调用runAffineTransform
时,不会对组件进行评估,直到某些消费者要求对组件进行评估时,组件才会停止运行
testAffineTransformSpeed affTr count = go count (0.5, 0.5)
where go :: Int -> (Double, Double) -> (Double, Double)
go 0 res = res
go !n !res = go (n-1) (runAffineTransform affTr res)
不是那个消费者,bang-onres
只对最外层的构造函数求值,(,)
,然后得到
runAffineTransform affTr (runAffineTrasform affTr (runAffineTransform affTr (...)))
只有在最后需要范式时才对其求值
如果强制立即计算结果的组件
runAffineTransform affTr (!x, !y) = case
( get affTr `U.unsafeIndex` 0 * x
+ get affTr `U.unsafeIndex` 1 * y
+ get affTr `U.unsafeIndex` 2
, get affTr `U.unsafeIndex` 3 * x
+ get affTr `U.unsafeIndex` 4 * y
+ get affTr `U.unsafeIndex` 5
) of (!a,!b) -> (a,b)
让它内联,使用自定义严格的unboxDouble
s对的版本的主要区别在于,对于testAffinetTransformsSpeed
中的循环,使用boxDouble
s作为参数得到一个初始迭代,最后,结果的组件被装箱,这增加了一点恒定的开销(我的盒子上每个循环大约5纳秒)。在这两种情况下,循环的主要部分都使用一个Int 35;
和两个Double#
参数,循环体是相同的,除了到达n=0
时的装箱
当然,使用未绑定的严格对类型强制立即计算组件更好。Nice,现在在我的计算机上,它的性能比Scala(Java)稍好一些。我们应该吸取什么教训?严格注释不够(它们不会自动取消装箱?)?有时您必须手动创建未打包(未打包)的数据结构?@user2705843幻灯片(4)和(9)在这里是相关的:
NFData
的SP
实例完全是浪费工作。事实上,很多都是这样。它隐式创建Double
构造函数,指向SP
中的未打包值,强制它们,然后丢弃它们rnf a=seq a()
将更加高效和正确。@Carl谢谢。不过,在这里进行更改不会产生任何(时间)差异。@Carl你低估了GHC,case p\u aIf of{AffTran.SP\uu->GHC.Tuple。}
是它为NFData
实例生成的。它知道组件已解除绑定,因此没有理由再次对其进行评估。您是如何编译的,使用哪种编译器?它是使用ghc 7.6.3编译的。选项是“-O2-Wall-funbox严格字段-线程化-rtsopts”。我原以为-funbox严格字段就足够了,但事实并非如此。嗯,我是一个完全的新手,所以我的期望值可能有点低。为什么需要Vector
s来表示仿射变换?为它制作一个ADT并处理坐标数组似乎更合理。您可以将仿射变换ADT作为固定向量的一个实例,并获得向量运算的所有功能。你说的“独立坐标”是什么意思?最后,您的应用程序中是否有更多的坐标或变换?您的代码要复杂得多。对固定大小的结构使用数组在任何语言中都是不自然的,从C
到Haskell
!(!x,!y)
有用吗?在中让或在哪里绑定。但在这里,它只是从中删除了太少的一个字符的一个残余!res@(!x,!y)
。谢谢你的注意。
runAffineTransform affTr (!x, !y) = case
( get affTr `U.unsafeIndex` 0 * x
+ get affTr `U.unsafeIndex` 1 * y
+ get affTr `U.unsafeIndex` 2
, get affTr `U.unsafeIndex` 3 * x
+ get affTr `U.unsafeIndex` 4 * y
+ get affTr `U.unsafeIndex` 5
) of (!a,!b) -> (a,b)