2015-06-24 26 views
3

考慮以下玩具程序,通過將字符替換應用於字典單詞來強制密碼散列。字典按順序或並行遍歷,在編譯時由PARMAP符號觸發。爲什麼Haskell程序在使用-threaded編譯時表現奇怪?

import qualified Control.Parallel.Strategies as Strat 
import qualified Crypto.Hash.SHA256 as SHA256 
import qualified Data.ByteString as BS 
import qualified Data.ByteString.Base16 as BS.Base16 
import qualified Data.ByteString.Char8 as BS.Char8 
import Data.Char (isLower, toUpper) 
import Data.Maybe (listToMaybe) 

variants :: String -> [String] 
variants "" = [""] 
variants (c:s) = [c':s' | s' <- variants s, c' <- subst c] 
    where subst 'a' = "[email protected]" 
     subst 'e' = "eE3" 
     subst 'i' = "iI1" 
     subst 'l' = "lL1" 
     subst 'o' = "oO0" 
     subst 's' = "sS$5" 
     subst 'z' = "zZ2" 
     subst x | isLower x = [x, toUpper x] 
     subst x = [x] 

myMap :: (a -> [a]) -> [a] -> [[a]] 
#ifdef PARMAP 
myMap = Strat.parMap (Strat.evalList Strat.rseq) 
#else 
myMap = map 
#endif 

bruteForce :: [String] -> BS.ByteString -> Maybe String 
bruteForce dictionary hash = listToMaybe $ concat $ myMap match dictionary 
    where match word = [var | var <- variants word, 
         SHA256.hash (BS.Char8.pack var) == hash] 

main :: IO() 
main = do 
    dictionary <- lines `fmap` (readFile "dictionary.txt") 
    hash <- (fst . BS.Base16.decode . BS.Char8.pack) `fmap` getLine 
    case bruteForce dictionary hash of 
    Just preimage -> putStrLn preimage 
    Nothing -> return() 

我編譯這個程序有和沒有都PARMAP-threaded

$ ghc Brute.hs -cpp -fforce-recomp -Wall -O2 -o brute.seq 
$ ghc Brute.hs -cpp -fforce-recomp -Wall -O2 -DPARMAP -o brute.par 
$ ghc Brute.hs -cpp -fforce-recomp -Wall -O2 -threaded -o brute.seq+th 
$ ghc Brute.hs -cpp -fforce-recomp -Wall -O2 -threaded -DPARMAP -o brute.par+th 

爲了運行這個程序,我做了一個小字典,並從中取出最後一個字。

$ shuf -n 300 /usr/share/dict/american-english >dictionary.txt 
$ tail -n 1 dictionary.txt 
desalinates 
$ echo -n '[email protected]' | sha256sum 
3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 - 

我在一個雙核CPU上運行它。此機器上沒有其他CPU密集型進程正在運行。

順序映射版本按預期執行。

$ TIMEFORMAT='real %R user %U sys %S' 
$ time ./brute.seq <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 39.797 user 39.574 sys 0.156 
$ time ./brute.seq+th <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 44.313 user 44.159 sys 0.088 
$ time ./brute.seq+th +RTS -N <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 44.990 user 44.835 sys 0.876 

編譯時沒有-threaded的並行地圖版本具有相同的性能。

$ time ./brute.par <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 39.847 user 39.742 sys 0.056 

當我結合並行地圖-threaded,但不要用2個內核,只是還沒有,事情開始尋找奇怪。

$ time ./brute.par+th <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 58.636 user 73.661 sys 17.717 

當我使用2個核心,事情變得陌生。現在的性能在運行之間變化很大,以前的版本不會顯示。有時它比單核brute.par+th快兩倍,這正是我所期望的,因爲算法令人尷尬並行。但有時甚至比一個核心更慢。

$ time ./brute.par+th +RTS -N <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 28.589 user 51.615 sys 2.304 
$ time ./brute.par+th +RTS -N <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 59.149 user 106.255 sys 4.664 
$ time ./brute.par+th +RTS -N <<<3c76d2487f8094a8bf4cbb9e7de7624fab48c3d4f82112cb150b35b9a1cb9072 
[email protected] 
real 49.428 user 87.193 sys 3.816 

現在我有兩個問題可能有關。

  1. 爲什麼1-core brute.par+th比1-core brute.par慢得多?這些線程在做什麼?在內核模式下它在17秒鐘內幹什麼?
  2. 爲什麼2-core brute.par+th的性能差異很大,而不是可靠地成爲單核brute.par+th性能的兩倍?

我使用GHC 7.4.1和parallel-3.2.0.2。我知道我應該使用更新的版本,但這是我目前所掌握的。

我試着編譯-rtsopts並禁用線程化GC與+RTS -qg,沒有任何效果。

我試過ThreadScope,但是它瘋狂地交換,並且甚至在我使用更小的字典時也無法加載事件日誌。

+0

您可能想嘗試[profileur](http://jaspervdj.be/posts/2014-02-25-profiteur-ghc-prof-visualiser.html),它是爲處理較大的事件日誌而編寫的。 –

回答

3

意外的性能可以通過「Crypto.Hash.SHA256」調用不安全的FFI code來解釋。 GHC不會guarantee其他Haskell線程在調用此代碼期間不會被阻塞。如果par產生的線程被GHC阻塞,它會在你的程序中引起很多爭用,這會導致運行時間長和運行時間結果不一致。

+0

用'BS.reverse'重寫爲虛擬散列函數。這解決了沒有。 1:沒有'+ RTS -N',所有4個版本現在都有相同的性能。 –

+0

至於沒有。 2,我已經注意到現在運行時間從T增加到〜2T並重復運行,然後在CPU冷卻時從字面上回落到T.所以,也許不是Haskell相關的問題。 –

+0

謝謝。如果沒有人提供更好的答案,我會在明天接受這個答案。 –

相關問題