我最近开始使用Data.Vector。我的理解是它应该能够采用矢量操作链并通过融合有效地组合它们。
特别是,我希望这两个函数具有相似的性能特征(即searchTopDown融合到像searchTd这样的东西)。
import qualified Data.Vector.Unboxed as VU
import Data.Vector.Unboxed (fromList, (!))
--| Going down from i searches the vector for the element satisfying predicate
searchTopDown :: VU.Vector Int -> Int -> (Int -> Bool) -> (Int, Int)
searchTopDown vec i pred =
VU.head $ VU.dropWhile (not . pred . snd)
$ VU.reverse $ VU.take (1 + i)
$ VU.indexed vec
searchTd :: VU.Vector Int -> Int -> (Int -> Bool) -> (Int, Int)
searchTd vec i pred = let val = vec ! i
in
if pred val then (i, val) else searchTd vec (i-1) pred
但是,对此代码进行基准测试会显示数量级的性能差异。
testTd/150
lower bound estimate upper bound
OLS regression 4.51 μs 4.53 μs 4.56 μs
R² goodness-of-fit 1.000 1.000 1.000
Mean execution time 4.52 μs 4.53 μs 4.55 μs
Standard deviation 35.2 ns 50.0 ns 69.8 ns
topDown/150
lower bound estimate upper bound
OLS regression 75.4 μs 76.6 μs 77.9 μs
R² goodness-of-fit 0.997 0.998 0.999
Mean execution time 74.6 μs 75.2 μs 76.1 μs
Standard deviation 1.58 μs 2.42 μs 3.75 μs
问题:是什么阻止了searchTopDown被融合?我该怎么做才能帮助融合?什么时候我应该期待融合?
用于基准测试的代码:
import qualified Data.List as L
import Criterion.Main
vec :: VU.Vector Int
vec = fromList $ L.replicate 100 (-1) L.++ L.replicate 100 1
testTd :: Int -> Int
testTd i = fst $ searchTd vec i (<0)
testTopDown :: Int -> Int
testTopDown i = fst $ searchTopDown vec i (<0)
benchMain = defaultMain [
bgroup "testTd" [ bench "1" $ whnf testTd 1
, bench "90" $ whnf testTd 90
, bench "100" $ whnf testTd 100
, bench "150" $ whnf testTd 150
],
bgroup "topDown" [ bench "1" $ whnf testTopDown 1
, bench "90" $ whnf testTopDown 90
, bench "100" $ whnf testTopDown 100
, bench "150" $ whnf testTopDown 150
]
]
修改
遵循@Zeta建议(添加-O2
)我从这两个函数中获得了出色的性能。但是,我在一个更大的项目中遇到了这个问题,因此我将基准测试代码更改为类似于它。对于这种情况,searchTopDown
似乎是i
的线性。新的基准测试代码和结果(以微秒为单位):
import Data.Vector.Unboxed (fromList, (!))
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VM
import Control.Monad.ST
import qualified Data.List as L
datStream :: [(Int, Int)]
datStream = L.zip [0..] $ L.replicate 100 (-1) L.++ L.replicate 100 1
vecUpdate v (iChg, vChg) = runST $ do
mv <- VU.unsafeThaw v
VM.modify mv (+ vChg) iChg
VU.unsafeFreeze mv
vecs :: [VU.Vector Int]
vecs = let vecInit = VU.replicate 200 0 in L.drop 1 $ L.scanl' vecUpdate vecInit datStream
test f i = L.sum $ L.map (\x -> fst $ f x i (<0)) vecs
testTd = test searchTd
testTopDown = test searchTopDown