Performance Haskell算法建议和备选解决方案建议

Performance Haskell算法建议和备选解决方案建议,performance,algorithm,haskell,Performance,Algorithm,Haskell,我将在下面留下对问题的描述,但我正在编辑所有不相关的部分 1) 多亏了dfeuer,我才能够从这个程序中节省几乎整整一秒钟的时间。我从10.1秒降到了9.2秒 2) 多亏了对what SO user的修改,我能够将时间缩短到7.0秒,使IO变得非常高效 3) 多亏了groupBy Find的另一个版本,我现在只有6.2秒了 4) 生成器输出的重新实现,现在为5.8秒 import qualified Data.ByteString.Builder as BB import qualif

我将在下面留下对问题的描述,但我正在编辑所有不相关的部分

1) 多亏了dfeuer,我才能够从这个程序中节省几乎整整一秒钟的时间。我从10.1秒降到了9.2秒

2) 多亏了对what SO user的修改,我能够将时间缩短到7.0秒,使IO变得非常高效

3) 多亏了groupBy Find的另一个版本,我现在只有6.2秒了

4) 生成器输出的重新实现,现在为5.8秒

import qualified Data.ByteString.Builder    as BB  
import qualified Data.ByteString.Lazy.Char8 as DB
import qualified Data.Function              as DF

import Control.Applicative
import Data.Monoid
import System.IO

groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy rel []     = []
groupBy rel (x:xs) = (x:ys) : groupBy rel zs
    where (ys,zs) = groupByAux x xs
          groupByAux x0 (x:xs) 
              | rel x0 x = (x:ys, zs)
              where (ys,zs) = groupByAux x xs
          groupByAux y xs = ([], xs)

filterLengthBy :: (Int -> Bool) -> [[a]] -> [[a]]
filterLengthBy fn = filter (flb fn)
    where flb fn xs = fn $ length xs  

buildOutput x y =  BB.intDec x <> BB.char7 ' ' <> BB.intDec y <> BB.char7 '\n'

tensAndHundreds :: [[[Int]]] -> [BB.Builder]
tensAndHundreds [] = []
tensAndHundreds (x:xs) 
    | length x /= 10 = tens x ++ tensAndHundreds xs
    | otherwise      = buildOutput (head $ head x) 100 : tensAndHundreds xs

tens :: [[Int]] -> [BB.Builder]
tens = foldr (\x acc -> buildOutput (head x) 10 : acc) []

dbIntVals :: DB.ByteString -> [Int]
dbIntVals xs = 
    case (DB.readInt xs) of
         Just (x', xs') -> x' : dbIntVals (DB.tail xs')
         Nothing        -> if xs == DB.empty
                           then []
                           else dbIntVals (DB.tail xs)

generateResults :: [Int] -> [BB.Builder]
generateResults xs = tensAndHundreds 
                   $ groupBy ((==) `DF.on` (`quot` 100) . head) 
                   $ filterLengthBy (== 10)
                   $ groupBy ((==) `DF.on` (`quot` 10)) xs

displayResults :: [BB.Builder] -> IO ()
displayResults xs = BB.hPutBuilder stdout $ mconcat xs

main :: IO ()
main = DB.getContents >>= 
       displayResults . generateResults . dbIntVals
否则,对于从0(0-9、10-19、15430-15439等)开始的相同范围内的每一组连续的10,打印该范围的磁头,后跟10:

30 10
70 10
145620 10 
145650 10
一个小测试集的示例输出如下所示:

0 10
19000 100
20320 10
54540 100
我的主测试文件有56653371个INT,其中有290197组10个INT和4组100个INT。测试机器有一个AMD1090T处理器和8GB内存,在64位Linux上运行GHC7.8.3。使用的唯一编译器标志是“-O2-fflvm”

有一件重要的事情需要注意:我有意避免使用编程中经常使用的计数器。例如,有跟踪10和100的计数器:“如果计数器1==100,那么…”之类的东西。我希望避免跟踪这些计数器的状态,而只使用可用的函数/HOF

以下是我迄今为止最好的尝试。它在10.1秒内完成任务(目前为5.8秒)。从这个角度来看,我前面提到的线程的C版本在9.4秒内完成。还有另一个Haskell版本,我没有写,它使用计数器完成8.1秒(目前为4.7)。这就是我的灵感所在(感谢Xyne的这一点,以及在案例陈述中融入自己的教学)

**编辑**

我忘了说它是干什么的。它使用整数除法将输入数据拆分为10个潜在范围,并将它们放入列表列表中(编辑以澄清luqui询问的拆分问题)。因此:

filterLengthBy然后删除长度不是10的所有内容,因为我们不需要它。根据头部的整数分割,将生成的列表拆分为列表列表列表。因此:

[[0,1,2,3,4,5,6,7,8,9],[10,11,12,13,14,15,16,17,18,19],[100,101,102,103,104,105,106,107,108,109]]
变成:

[[[0,1,2,3,4,5,6,7,8,9],[10,11,12,13,14,15,16,17,18,19]],[[100,101,102,103,104,105,106,107,108,109]]]
如果列表的第二级长度为10,则匹配范围为100,并输出到解决方案集。否则,该列表的列表范围为10,应单独添加到解决方案集中

代码的旧版本

import qualified Data.ByteString.Lazy.Char8  as DB
import qualified Data.Function               as DF
import qualified Data.List                   as DL
import Control.Applicative

groupByEqOn :: Eq b => (a -> b) -> [a] -> [[a]]
groupByEqOn fn = DL.groupBy ((==) `DF.on` fn)

filterLengthBy :: (Int -> Bool) -> [[a]] -> [[a]]
filterLengthBy fn = filter (flb fn)
    where flb fn xs = fn $ length xs   

tensAndHundreds :: [[[Int]]] -> [(Int, Int)]
tensAndHundreds [] = []
tensAndHundreds (x:xs) 
    | length x /= 10 = tens x ++ tensAndHundreds xs
    | otherwise      = (head $ head x, 100) : tensAndHundreds xs

tens :: [[Int]] -> [(Int, Int)]
tens = map (\x -> (head x, 10))

dbEmpty :: DB.ByteString
dbEmpty = DB.empty

dbIntVals :: [DB.ByteString] -> [Int]
dbIntVals [] = []
dbIntVals (x:xs) =
    case (DB.readInt x) of
         (Just (x', dbEmpty)) -> x' : dbIntVals xs
         _                    -> error "*** Error: Non-integer input ***"

generateResults :: [Int] -> [(Int, Int)]
generateResults xs = tensAndHundreds 
                   $ groupByEqOn ((`quot` 100) . head) 
                   $ filterLengthBy (== 10) 
                   $ groupByEqOn (`quot` 10) xs

displayResults :: [(Int, Int)] -> IO ()
displayResults = mapM_ (\(a, b) -> putStrLn (show a ++ " " ++ show b)) 

main :: IO ()
main = dbIntVals <$> DB.lines <$> DB.getContents >>= 
       displayResults . generateResults
将限定数据.ByteString.Lazy.Char8导入为DB
导入符合条件的数据。功能为DF
导入符合条件的数据。作为DL列出
导入控制
groupByEqOn::Eq b=>(a->b)->[a]->[a]]
groupByEqOn fn=DL.groupBy((==)`DF.on`fn)
过滤器长度::(Int->Bool)->[[a]]->[[a]]
过滤器长度fn=过滤器(flb fn)
其中,flb fn xs=fn$length xs
时态与百分位::[[[Int]]->[(Int,Int)]
张量与百分之[]=[]
张量和百分位(x:xs)
|长度x/=10=十个x++十个x++十个x
|否则=(head$head x,100):TENSAND x
十位数::[[Int]]->[(Int,Int)]
tens=地图(\x->(标题x,10))
dbEmpty::DB.ByteString
dbEmpty=DB.empty
dbIntVals::[DB.ByteString]->[Int]
dbIntVals[]=[]
dbIntVals(x:xs)=
案例(DB.readInt x)的
(仅(x',dbEmpty))->x':dbIntVals xs
_->错误“***错误:非整数输入***”
GenerateResult::[Int]->[(Int,Int)]
generateResults xs=张量和百分之一百
$groupByEqOn('quot`100).head)
$filterLengthBy(=10)
$groupByEqOn(`quot`10)xs
显示结果::[(Int,Int)]->IO()
displayResults=mapM(a,b)->putStrLn(show a++++++++show b))
main::IO()
main=dbIntVals DB.lines DB.getContents>>=
显示结果。生成结果

在分析器中,唯一让我感到惊讶的两件事是:第一,filterLengthBy的速度有多快。对于所有参与的人来说,这是一个巨大的荣誉,因为他们的过滤速度如此之快。考虑到我投入了多少数据,这确实是一件了不起的事情。就这一点而言,值得注意的是,我写的东西和现在一样快。另一个惊喜是dbIntVals有多慢。我确实实现了正确的错误检查,这使事情变得有点慢,但我仍然感到惊讶。除此之外,它的读取方式似乎与我的代码完全相同。

我已经基本上完成了这个线程。我来这里是想看看SO Haskell社区能提供什么,我得到了一些有益的回应。对此我很感激

我发布的最新版本的代码表明,如果代码好,GHC可以只使用函数式习惯用法生成快速、准确的程序。这看起来似乎很明显,但这是我从零开始编写的第一个Haskell程序(显然是groupBy的复制和粘贴)

我学到了什么

从dfeuer开始,我被提醒要小心,当仅仅依靠内置函数(如map)不一定是最好的方法时。有时将逻辑结合起来是有帮助的。在这种情况下,我得到了非常显著的性能提升

我从applicative学习了高效输出。性能的提高是惊人的

如果任何Haskell/GHC开发人员正在阅读以下内容:

在我看来,groupBy应该包含在标准库中,而不是被推到黑客的位置。为什么?它在保持准确性的同时速度很快。对于在数百万个元素上进行完全相同的操作并产生完全相同的结果,性能提高了约8.2%。我知道它做的事情并不完全一样;我仍然认为值得一看

真的有意义吗?我看到,与上述相同的数据集相比,性能至少提高了10%,精确度达到100%。我理解“rem”和
[[[0,1,2,3,4,5,6,7,8,9],[10,11,12,13,14,15,16,17,18,19]],[[100,101,102,103,104,105,106,107,108,109]]]
import qualified Data.ByteString.Lazy.Char8  as DB
import qualified Data.Function               as DF
import qualified Data.List                   as DL
import Control.Applicative

groupByEqOn :: Eq b => (a -> b) -> [a] -> [[a]]
groupByEqOn fn = DL.groupBy ((==) `DF.on` fn)

filterLengthBy :: (Int -> Bool) -> [[a]] -> [[a]]
filterLengthBy fn = filter (flb fn)
    where flb fn xs = fn $ length xs   

tensAndHundreds :: [[[Int]]] -> [(Int, Int)]
tensAndHundreds [] = []
tensAndHundreds (x:xs) 
    | length x /= 10 = tens x ++ tensAndHundreds xs
    | otherwise      = (head $ head x, 100) : tensAndHundreds xs

tens :: [[Int]] -> [(Int, Int)]
tens = map (\x -> (head x, 10))

dbEmpty :: DB.ByteString
dbEmpty = DB.empty

dbIntVals :: [DB.ByteString] -> [Int]
dbIntVals [] = []
dbIntVals (x:xs) =
    case (DB.readInt x) of
         (Just (x', dbEmpty)) -> x' : dbIntVals xs
         _                    -> error "*** Error: Non-integer input ***"

generateResults :: [Int] -> [(Int, Int)]
generateResults xs = tensAndHundreds 
                   $ groupByEqOn ((`quot` 100) . head) 
                   $ filterLengthBy (== 10) 
                   $ groupByEqOn (`quot` 10) xs

displayResults :: [(Int, Int)] -> IO ()
displayResults = mapM_ (\(a, b) -> putStrLn (show a ++ " " ++ show b)) 

main :: IO ()
main = dbIntVals <$> DB.lines <$> DB.getContents >>= 
       displayResults . generateResults