Haskell 计算性能
Haskell computation performance
我对同一功能有几种不同的实现方式。不同之处在于刘海图案的使用。问题是,为什么 perimeterNaiveFast 与 perimeterStrictFast 的工作方式相同?
基准测试结果:
函数实现:
> data Point = Point { x :: Int, y :: Int }
>
> distance :: Point -> Point -> Double
> distance (Point x1 y1) (Point x2 y2) =
> sqrt $ fromIntegral $ (x1 - x2) ^ (2 :: Int) + (y1 - y2) ^ (2 :: Int)
>
> perimeterNaive :: [Point] -> Double
> perimeterNaive [] = 0.0
> perimeterNaive points = foldPoints points 0.0
> where
> firstPoint = head points
> foldPoints [] _ = 0.0
> foldPoints [lst] acc = acc + distance firstPoint lst
> foldPoints (prev:next:rst) acc = foldPoints (next:rst) (acc + distance prev next)
>
> perimeterStrict :: [Point] -> Double perimeterStrict [] = 0.0
> perimeterStrict points = foldPoints points 0.0
> where
> firstPoint = head points
> foldPoints [] _ = 0.0
> foldPoints [lst] acc = acc + distance firstPoint lst
> foldPoints (prev:next:rst) !acc = foldPoints (next:rst) (acc + distance prev next)
>
> perimeterNaiveFast :: [Point] -> Double perimeterNaiveFast [] = 0.0
> perimeterNaiveFast (first:rest) = foldPoints rest first 0.0
> where
> foldPoints [] lst acc = acc + distance first lst
> foldPoints (next:rst) prev acc = foldPoints rst next (acc + distance next prev)
>
> perimeterStrictFast :: [Point] -> Double perimeterStrictFast [] = 0.0
> perimeterStrictFast (first:rest) = foldPoints rest first 0.0
> where
> foldPoints [] lst acc = acc + distance first lst
> foldPoints (next:rst) prev !acc = foldPoints rst next (acc + distance next prev)
>
> main :: IO ()
> main = defaultMain [ perimeterBench $ 10 ^ (6 :: Int) ]
>
> perimeterBench :: Int -> Benchmark perimeterBench n = bgroup "Perimiter"
> [ bench "Naive" $ nf perimeterNaive points
> , bench "Strict" $ nf perimeterStrict points
> , bench "Naive fast" $ nf perimeterNaiveFast points
> , bench "Strict fast" $ nf perimeterStrictFast points
> ]
> where
> points = map (\i -> Point i i) [1..n]
GHC 的 strictness analysis pass 已经注意到 Float
累加器不会(也不能)延迟使用,并代表您将您的原始版本转换为您的严格版本。
它没有为您的另一对 naive/fast 也这样做的原因是这个条款:
foldPoints [] _ = 0.0
在这里你忽略了累加器,因此编译器认为在某些情况下,在你进行时不强制计算可能会更好。将其更改为
foldPoints [] acc = acc
足以让 GHC 使您的其他 naive/strict 对在我的机器上具有相同的性能。
我对同一功能有几种不同的实现方式。不同之处在于刘海图案的使用。问题是,为什么 perimeterNaiveFast 与 perimeterStrictFast 的工作方式相同?
基准测试结果:
函数实现:
> data Point = Point { x :: Int, y :: Int }
>
> distance :: Point -> Point -> Double
> distance (Point x1 y1) (Point x2 y2) =
> sqrt $ fromIntegral $ (x1 - x2) ^ (2 :: Int) + (y1 - y2) ^ (2 :: Int)
>
> perimeterNaive :: [Point] -> Double
> perimeterNaive [] = 0.0
> perimeterNaive points = foldPoints points 0.0
> where
> firstPoint = head points
> foldPoints [] _ = 0.0
> foldPoints [lst] acc = acc + distance firstPoint lst
> foldPoints (prev:next:rst) acc = foldPoints (next:rst) (acc + distance prev next)
>
> perimeterStrict :: [Point] -> Double perimeterStrict [] = 0.0
> perimeterStrict points = foldPoints points 0.0
> where
> firstPoint = head points
> foldPoints [] _ = 0.0
> foldPoints [lst] acc = acc + distance firstPoint lst
> foldPoints (prev:next:rst) !acc = foldPoints (next:rst) (acc + distance prev next)
>
> perimeterNaiveFast :: [Point] -> Double perimeterNaiveFast [] = 0.0
> perimeterNaiveFast (first:rest) = foldPoints rest first 0.0
> where
> foldPoints [] lst acc = acc + distance first lst
> foldPoints (next:rst) prev acc = foldPoints rst next (acc + distance next prev)
>
> perimeterStrictFast :: [Point] -> Double perimeterStrictFast [] = 0.0
> perimeterStrictFast (first:rest) = foldPoints rest first 0.0
> where
> foldPoints [] lst acc = acc + distance first lst
> foldPoints (next:rst) prev !acc = foldPoints rst next (acc + distance next prev)
>
> main :: IO ()
> main = defaultMain [ perimeterBench $ 10 ^ (6 :: Int) ]
>
> perimeterBench :: Int -> Benchmark perimeterBench n = bgroup "Perimiter"
> [ bench "Naive" $ nf perimeterNaive points
> , bench "Strict" $ nf perimeterStrict points
> , bench "Naive fast" $ nf perimeterNaiveFast points
> , bench "Strict fast" $ nf perimeterStrictFast points
> ]
> where
> points = map (\i -> Point i i) [1..n]
GHC 的 strictness analysis pass 已经注意到 Float
累加器不会(也不能)延迟使用,并代表您将您的原始版本转换为您的严格版本。
它没有为您的另一对 naive/fast 也这样做的原因是这个条款:
foldPoints [] _ = 0.0
在这里你忽略了累加器,因此编译器认为在某些情况下,在你进行时不强制计算可能会更好。将其更改为
foldPoints [] acc = acc
足以让 GHC 使您的其他 naive/strict 对在我的机器上具有相同的性能。