我在下面留下问题的描述,但是我在编辑所有不相关的内容时都会这样做。
1)感谢dfeuer,我已经能够完成这个项目几乎整整一秒的时间。我从10.1开始下降到9.2秒。
2)由于修改了SO用户applicative posted,我能够降低到7.0秒,使IO非常高效。
3)感谢发现here的不同版本的groupBy,我现在只需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
我在帖子底部的两个问题是:
鉴于我上面发布的方法,我错过了什么可以提高该程序的效率并因此提高该程序的性能?空间分配是最后的瓶颈吗?换句话说,groupBy方法是否正在运行?
我还可以通过哪些其他方式来看待能够提高效率的问题,同时仍然试图避免使用传统意义上的计数器?我还没有体验过Haskell的先进功能,但请随时提供指示。
这是原始问题。我离开了代码的第一个版本,但删除了所有的分析器内容
我开始学习Haskell(再次),并决定尝试一些我发现的随机问题。我在Arch Linux论坛上遇到的一个问题是这样的:
给定一组唯一的,上升的,但不一定是连续的整数,对于从0开始的每个可能的子范围100(0-99,100-199,1300-1399等),如果该子范围有所有100个元素,打印头值后跟100:
0 100
145600 100
否则,对于从0开始的相同范围内的每个连续10组(0-9,10-19,15430-15439等),打印该范围的头部,然后是10:
30 10
70 10
145620 10
145650 10
微小测试集的示例输出如下所示:
0 10
19000 100
20320 10
54540 100
我的主要测试文件有56,653,371个整数,其中有290,197个十组和四组100个。测试机器有一个AMD 1090T处理器和8 GB RAM,并且在64位Linux上运行GHC 7.8.3 。使用的唯一编译器标志是&#39; -O2 -fflvm&#39;。
有一点需要注意:我故意避免使用编程中经常使用的计数器。例如,拥有跟踪10和100的计数器:&#39;如果计数器一= = 100,那么......&#39;有点东西。我想避免跟踪这些计数器的状态,并仅使用可用的功能/ HOF&#39; s。
以下是迄今为止我最好的尝试。它在10.1秒内完成任务(当前为5.8秒)。从透视角度来看,我提到的线程中的C版本在9.4秒内完成。还有另一个Haskell版本,我没有编写它使用的计数器完成它8.1秒(目前为4.7 )。这就是我的灵感(感谢Xyne,并教导案例陈述自成一体)。
**编辑**
我忘了提到它的作用。它使用整数除法将输入数据分成十个可能的范围,并将它们放入列表列表中(编辑以澄清分裂的luqui问题)。所以:
1 4 7 9 -> [1,4,7,9]
1 2 3 8 -> [1,2,3,8]
0 1 2 3 4 5 6 7 8 9 -> [0,1,2,3,4,5,6,7,8,9]
7 8 9 10 11 12 13 14 15 16 17 18 19 20 25 27 -> [7,8,9] [10,11,12,13,14,15,16,17,18,19] [20,25,27]
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
分析器中我唯一感到惊讶的两件事就是filterLengthBy的速度。所有参与过滤器的人都非常荣幸。考虑到我投入了多少数据,这真是一件了不起的事情。就此而言,我写的内容和它一样快,这是非常了不起的。另一个惊喜是dbIntVals的速度有多慢。我确实实现了正确的错误检查,这使事情变得有点慢,但我仍然感到惊讶。除此之外,它似乎与我的代码完全一样。
答案 0 :(得分:0)
我已经完成了这个线程。我来到这里看看SO Haskell社区提供了什么,我收到了一些有用的回复。为此,我感激不尽。
我发布的最新版本的代码表明,鉴于良好的代码,GHC可以使用功能惯用语生成超快速,准确的程序。这看起来很明显,但这是我从头开始编写的第一个Haskell程序(有明显的groupBy复制和粘贴)。
我学到了什么?
来自dfeuer我被提醒要小心,只依赖于内置功能,如地图可能不一定是最好的方法。有时结合逻辑很有帮助。在这种情况下,我的性能提升非常显着。
从应用程序中我学到了有效的输出。性能的提升令人惊叹。
如果有任何Haskell / GHC开发人员正在阅读此内容:
在我看来,groupBy here应该包含在标准库中,而不是推送到Hackage。为什么?它在保持准确性的同时速度很快。对于数百万个元素的完全相同的操作并产生完全相同的结果,性能提高了约8.2%。我明白它并没有做同样的事情;我仍然认为值得一看。
this真的有意义吗?我发现与上述相同的数据集至少有10%的性能提升,准确率为100%。我明白了&#39; rem&#39;和&#39; mod&#39;做不同的事情,他们倾向于在不同的方向和#34;,但前者以巨大的利润执行后者。我可能不了解这个范围,但10%是很难忽视的。