该程序创建一个非常大的集合来查找散列函数冲突。有没有办法减少在GC中花费的时间? + RTS -s报告在GC中花费了40 +%的时间。
使用示例:
./program 0 1000000 +RTS -s
./program 145168473 10200000 +RTS -s
我可以使用更好的算法或数据结构吗?
{-# LANGUAGE OverloadedStrings #-}
import System.Environment
import Control.Monad
import Crypto.Hash.SHA256
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.Char
import Data.Int
import Data.Bits
import Data.Binary
import Data.Set as Set
import Data.List
import Numeric
str2int :: (Integral a) => B.ByteString -> a
str2int bs = B.foldl (\a w -> (a * 256)+(fromIntegral $ ord w)) 0 bs
t50 :: Int64 -> Int64
t50 i = let h = hash $ B.concat $ BL.toChunks $ encode i
in
(str2int $ B.drop 25 h) .&. 0x3ffffffffffff
sha256 :: Int64 -> B.ByteString
sha256 i = hash $ B.concat $ BL.toChunks $ encode i
-- firstCollision :: Ord b => (a -> b) -> [a] -> Maybe a
firstCollision f xs = go f Set.empty xs
where
-- go :: Ord b => (a -> b) -> Set b -> [a] -> Maybe a
go _ _ [] = Nothing
go f s (x:xs) = let y = f x
in
if y `Set.member` s
then Just x
else go f (Set.insert y s) xs
showHex2 i
| i < 16 = "0" ++ (showHex i "")
| otherwise = showHex i ""
prettyPrint :: B.ByteString -> String
prettyPrint = concat . (Data.List.map showHex2) . (Data.List.map ord) . B.unpack
showhash inp =
let h = sha256 inp
x = B.concat $ BL.toChunks $ encode inp
in do putStrLn $ " - input: " ++ (prettyPrint x) ++ " -- " ++ (show inp)
putStrLn $ " - hash: " ++ (prettyPrint h)
main = do
args <- getArgs
let a = (read $ args !! 0) :: Int64
b = (read $ args !! 1) :: Int64
c = firstCollision t [a..(a+b)]
t = t50
case c of
Nothing -> putStrLn "No collision found"
Just x -> do let h = t x
putStrLn $ "Found collision at " ++ (show x)
showhash x
let first = find (\x -> (t x) == h) [a..(a+b)]
in case first of
Nothing -> putStrLn "oops -- failed to find hash"
Just x0 -> do putStrLn $ "first instance at " ++ (show x0)
showhash x0
答案 0 :(得分:4)
正如您所注意到的那样,GC统计数据报告生产率低:
44,184,375,988 bytes allocated in the heap
1,244,120,552 bytes copied during GC
39,315,612 bytes maximum residency (42 sample(s))
545,688 bytes maximum slop
109 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 81400 colls, 0 par 2.47s 2.40s 0.0000s 0.0003s
Gen 1 42 colls, 0 par 1.06s 1.08s 0.0258s 0.1203s
INIT time 0.00s ( 0.00s elapsed)
MUT time 4.58s ( 4.63s elapsed)
GC time 3.53s ( 3.48s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 8.11s ( 8.11s elapsed)
%GC time 43.5% (42.9% elapsed)
Alloc rate 9,651,194,755 bytes per MUT second
Productivity 56.5% of total user, 56.4% of total elapsed
最明显的第一步是增加GC默认区域以尝试消除调整大小的需要。一招,例如is to increase the -A area( 您可以使用工具like GC tune为您的程序找到正确的设置。
$ ./A ... +RTS -s -A200M
Total time 7.89s ( 7.87s elapsed)
%GC time 26.1% (26.5% elapsed)
Alloc rate 7,581,233,460 bytes per MUT second
Productivity 73.9% of total user, 74.1% of total elapsed
所以我们减少了四分之一秒,但生产率提高到75%。 现在我们应该看一下堆配置文件:
显示集合及其Int值的线性增长。这是你的算法所指定的,所以只要你保留所有的结果,我就看不到你能做的很多。
答案 1 :(得分:2)
您正在做的一件事是通过使用ByteString
包来构建binary
(如果您想避免使用cereal
,可以使用Builder
/从懒惰的块)。如果你深入研究他们使用的binary
monad的内部,你可以看到它的默认初始大小约为32k。为了您的目的,考虑到您只需要8个字节,这可能会给垃圾收集器带来更多压力。
由于您实际上只是使用encodeInt64 :: Int64 -> B.ByteString
encodeInt64 x =
let
go :: Int -> Maybe (Word8, Int)
go i
| i < 0 = Nothing
| otherwise =
let
w :: Word8
w = fromIntegral (x `shiftR` i)
in Just (w, i-8)
in fst $ B.unfoldrN 8 go 56
进行编码,因此您可以使用以下内容自行完成:
Data.Set
我担心你甚至可能做得更好,或者把字节直接戳到缓冲区。
以上是一回事,另一个非GC相关的问题是,您使用的是标准Data.HashSet
实施,您可以通过unordered-containers
-A200M
找到稍微更好的效果
Don提到的最后一点是,您可以请求Data.HashSet
(或者约会)更大的分配区域。
通过上述所有修改(您自己的编码器,使用-A200M
和{{1}}),您的代码的运行时间从我的机器上的7.397s到3.474s,%GC时间为分别为52.9%和21.2%。
所以在你的方法的Big-O意义上你没有做错什么,但是有一些常数可以让你失望一点!
答案 2 :(得分:1)
我不确定。但是,这里有一些分析器输出,以防有人可以从中构建一个真正的答案:
这是堆配置文件(来自与+RTS -hT
一起运行)
由于对firstCollision
进行了非强制评估,我认为你在Set.insert
中正在积累thunk。但是,内存分配在绝对意义上是如此之小,以至于我不确定它是真正的罪魁祸首 - 见下文。
以下是探查器的输出(使用-prof -fprof-auto
编译,使用+RTS -p
运行):
COST CENTRE MODULE %time %alloc
firstCollision.go Main 49.4 2.2
t50.h Main 39.5 97.5
str2int Main 5.4 0.0
firstCollision.go.y Main 3.4 0.0
t50 Main 1.1 0.0
基本上所有内存分配都来自序列化/散列管道h
的本地等效sha256
,其中似乎有很多中间数据结构构建正在进行中。
任何经验丰富的人都可以更准确地找出问题吗?