3

このプログラムは、ハッシュ関数の衝突を見つけるために非常に大きなセットを作成します。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
4

3 に答える 3

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 のデフォルト領域を増やして、サイズ変更の必要性をなくすことです。たとえば、1 つのトリックは -A 領域を大きくすることです ( 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

そのため、4 分の 1 秒短縮しましたが、生産性は 75% に向上しました。次に、ヒープ プロファイルを見てみましょう。

ここに画像の説明を入力

これは、セットとその Int 値の線形成長を示しています。ただし、これはアルゴリズムが指定するものであるため、すべての結果を保持している限り、できることはあまりありません。

于 2012-05-24T12:17:19.037 に答える
2

多くのことを行っていることの 1 つは、パッケージをByteString使用して s を構築することです(遅延チャンクとの間でそれを回避したい場合は、ちなみに使用できます)。彼らが使用するモナドの内部を掘り下げると、デフォルトの初期サイズが約 32k であることがわかります。あなたの目的のために、これはおそらくガベージコレクターに必要以上の圧力をかけています.8バイトしか必要としないことを考えると.binarycerealBuilder

実際binaryにはエンコーディングに使用しているだけなので、次のような方法で自分で行うことができます:

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

おそらく、バイトを直接バッファに突っ込むほうがうまくいくのではないかと思います。

上記は1つのことであり、GCに関連しないもう1つのポイントは、標準実装を使用していることです。これは、 fromData.Setでわずかに優れたパフォーマンスを見つけることができます。Data.HashSetunordered-containers

ドンによっても言及された最後のポイントは、-A200M(またはそのあたり)でより大きな割り当て領域を要求できることです。

Data.HashSet上記のすべての変更 (およびを使用した独自のエンコーダー-A200M) により、コードの実行時間は私のマシンで 7.397 秒から 3.474 秒になり、%GC 時間はそれぞれ 52.9% と 21.2% になりました。

したがって、アプローチのビッグオーの意味で間違っていることは何もありませんが、少し下げることができる定数がいくつかあります!

于 2012-05-24T17:59:23.637 に答える
1

わからない。ただし、誰かがそこから実際の回答を作成できる場合に備えて、プロファイラーの出力を次に示します。

これがヒーププロファイルです(で実行した場合+RTS -hT

ヒープ プロファイル

firstCollisionの強制されていない評価のために、サンクを構築していると思いますSet.insert。しかし、メモリ割り当ては絶対的に非常に小さいため、それが本当の原因であるかどうかはわかりません-以下を参照してください。

プロファイラーからの出力は次のとおりです ( でコンパイルし-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であり、多くの中間データ構造の構築が行われているようです。

経験豊富な人は、問題をより正確に特定できますか?

于 2012-05-24T06:18:32.473 に答える