提高 Haskell 代码性能(BangPatterns、LazyByteString)
Improving Haskell code performance (BangPatterns, LazyByteString)
我用过 BangPatterns、Lazy ByteString。不知道还能做什么来提高这段代码的性能。有什么想法和建议吗?它显然不是最快的版本,因为它超过了时间限制。
-- Find the sum of all the multiples of 3 or 5 below N
-- Input Format
-- First line contains T that denotes the number of test cases. This is followed by T lines, each containing an integer, N.
{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -O2 -optc-O2 #-}
import qualified Data.ByteString.Lazy as L
import Control.Monad (mapM_)
readInt :: L.ByteString -> Int
readInt !s = L.foldl' (\x c -> 10 * x + fromIntegral c - 48) 0 s
main :: IO ()
main = do
-- don't need the number of inputs, since it is read lazily.
-- split input by lines
(_:ls) <- L.split 10 `fmap` L.getContents
-- length ls <= 10^5
mapM_ (print . f . readInt) ls
-- n <= 10^9
f :: Int -> Int
f n = go 0 0
where
go !i !a | i == n = a
go !i !a | i `mod` 3 == 0
|| i `mod` 5 == 0 = go (i+1) (a+i)
go !i !a = go (i+1) a
你在行print
中的使用
mapM_ (print . f . readInt) ls
可能会引入一些开销,因为 print
依赖于 Int
的 Show
实例,这意味着将转换为低效的 String
s。
添加以下导入
import qualified Data.ByteString.Builder as BB
import qualified Data.Foldable as F
import Data.List.Split (chunksOf) -- from the "split" package
import System.IO -- for stdout
并尝试用类似
的方式更改该行
let resultList = map (f . readInt) ls
F.mapM_ (BB.hPutBuilder stdout . F.foldMap BB.intDec) (chunksOf 1000 resultList)
takes chunks of size 1000 from the list of Int
s and uses the efficient Builder
type and the specialized hPutBuilder
函数将它们写入标准输出。
(我添加了分块,否则我担心构建 Builder
会迫使整个输入列表进入内存。我们不希望这样,因为列表正在被延迟读取。)
不过我不确定这是否是主要瓶颈。
danidiaz 已经 输入和输出问题。
产生 3 或 5 的倍数的一种快速方法是使用主筛常用的那种“轮”。
multiples3or5 = go 0 $ cycle [3,2,1,3,1,2,3]
where
go n (x : xs) = n : go (n+x) xs
go n [] = error "impossible"
事实上,由于循环列表永远不会结束,所以使用不同的类型会更干净。并且由于您使用的是 Int
,它也可以专门针对性能进行解压缩。请注意,GHC 版本 7.8 或更高版本不需要此上下文中的 UNPACK
pragma。
data IntStream = {-# UNPACK #-} !Int :> IntStream
infixr 5 :>
wheel :: IntStream
wheel = 3 :> 2 :> 1 :> 3 :> 1 :> 2 :> 3 :> wheel
multiples3or5 = go 0 wheel
where
go !n (x :> xs) = n : go (n+x) xs
作为 fgv , this is in the nature of an anamorphism。你可以通过写
看到这个
multiples3or5 = unfoldr go (0, wheel) where
go (!n, (x :> xs)) = Just (n, (n+x, xs))
但请注意,unfoldr
在尚未正式发布的 base 4.8 之前还没有变得足够高效以用于任何用途。
打印结果的时候,系统要把很多东西除以10,不知道那些例程有没有特别优化,但是我知道GHC的native code generator做的 not 目前优化已知除数的除法,除非该除数是 2 的幂。因此您可能会发现可以通过使用 -fllvm
并小心使用 LLVM 的兼容版本来提高性能。
编辑
请参阅 以获得更好的方法。
如果您真的很关心效率,请重新考虑算法。您的主要瓶颈是您手动对 1 和 N 之间的一堆数字求和,无论您做什么,这在大 N 上都会表现不佳做。
相反,从数学角度思考。 N以内的所有3或5的倍数之和等于几乎所有3的倍数之和直至N(称之为S_3),加上5的所有倍数之和,直到N(称之为 S_5).我说 "almost" 是因为有些数字被重复计算;称它们的总和为 T。现在你想要的总和正好是S_3 + S_5 – T,每一项都有一个漂亮的封闭公式(它是什么?)。计算这三个数字比你现在做的要快得多。
这是没有那些"think about"导师答案的公式
sumMultiplesOf::Integral n=>n->n->n
sumMultiplesOf k n = d * (1 + d) `div` 2 * k where d = (n - 1) `div` k
sumMultiplesOf3or5::Integral n=>n->n
sumMultiplesOf3or5 n = sumMultiplesOf 3 n + sumMultiplesOf 5 n - sumMultiplesOf 15 n
我用过 BangPatterns、Lazy ByteString。不知道还能做什么来提高这段代码的性能。有什么想法和建议吗?它显然不是最快的版本,因为它超过了时间限制。
-- Find the sum of all the multiples of 3 or 5 below N
-- Input Format
-- First line contains T that denotes the number of test cases. This is followed by T lines, each containing an integer, N.
{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -O2 -optc-O2 #-}
import qualified Data.ByteString.Lazy as L
import Control.Monad (mapM_)
readInt :: L.ByteString -> Int
readInt !s = L.foldl' (\x c -> 10 * x + fromIntegral c - 48) 0 s
main :: IO ()
main = do
-- don't need the number of inputs, since it is read lazily.
-- split input by lines
(_:ls) <- L.split 10 `fmap` L.getContents
-- length ls <= 10^5
mapM_ (print . f . readInt) ls
-- n <= 10^9
f :: Int -> Int
f n = go 0 0
where
go !i !a | i == n = a
go !i !a | i `mod` 3 == 0
|| i `mod` 5 == 0 = go (i+1) (a+i)
go !i !a = go (i+1) a
你在行print
中的使用
mapM_ (print . f . readInt) ls
可能会引入一些开销,因为 print
依赖于 Int
的 Show
实例,这意味着将转换为低效的 String
s。
添加以下导入
import qualified Data.ByteString.Builder as BB
import qualified Data.Foldable as F
import Data.List.Split (chunksOf) -- from the "split" package
import System.IO -- for stdout
并尝试用类似
的方式更改该行let resultList = map (f . readInt) ls
F.mapM_ (BB.hPutBuilder stdout . F.foldMap BB.intDec) (chunksOf 1000 resultList)
takes chunks of size 1000 from the list of Int
s and uses the efficient Builder
type and the specialized hPutBuilder
函数将它们写入标准输出。
(我添加了分块,否则我担心构建 Builder
会迫使整个输入列表进入内存。我们不希望这样,因为列表正在被延迟读取。)
不过我不确定这是否是主要瓶颈。
danidiaz 已经
产生 3 或 5 的倍数的一种快速方法是使用主筛常用的那种“轮”。
multiples3or5 = go 0 $ cycle [3,2,1,3,1,2,3]
where
go n (x : xs) = n : go (n+x) xs
go n [] = error "impossible"
事实上,由于循环列表永远不会结束,所以使用不同的类型会更干净。并且由于您使用的是 Int
,它也可以专门针对性能进行解压缩。请注意,GHC 版本 7.8 或更高版本不需要此上下文中的 UNPACK
pragma。
data IntStream = {-# UNPACK #-} !Int :> IntStream
infixr 5 :>
wheel :: IntStream
wheel = 3 :> 2 :> 1 :> 3 :> 1 :> 2 :> 3 :> wheel
multiples3or5 = go 0 wheel
where
go !n (x :> xs) = n : go (n+x) xs
作为 fgv
multiples3or5 = unfoldr go (0, wheel) where
go (!n, (x :> xs)) = Just (n, (n+x, xs))
但请注意,unfoldr
在尚未正式发布的 base 4.8 之前还没有变得足够高效以用于任何用途。
打印结果的时候,系统要把很多东西除以10,不知道那些例程有没有特别优化,但是我知道GHC的native code generator做的 not 目前优化已知除数的除法,除非该除数是 2 的幂。因此您可能会发现可以通过使用 -fllvm
并小心使用 LLVM 的兼容版本来提高性能。
编辑
请参阅
如果您真的很关心效率,请重新考虑算法。您的主要瓶颈是您手动对 1 和 N 之间的一堆数字求和,无论您做什么,这在大 N 上都会表现不佳做。
相反,从数学角度思考。 N以内的所有3或5的倍数之和等于几乎所有3的倍数之和直至N(称之为S_3),加上5的所有倍数之和,直到N(称之为 S_5).我说 "almost" 是因为有些数字被重复计算;称它们的总和为 T。现在你想要的总和正好是S_3 + S_5 – T,每一项都有一个漂亮的封闭公式(它是什么?)。计算这三个数字比你现在做的要快得多。
这是没有那些"think about"导师答案的公式
sumMultiplesOf::Integral n=>n->n->n
sumMultiplesOf k n = d * (1 + d) `div` 2 * k where d = (n - 1) `div` k
sumMultiplesOf3or5::Integral n=>n->n
sumMultiplesOf3or5 n = sumMultiplesOf 3 n + sumMultiplesOf 5 n - sumMultiplesOf 15 n