2012-01-07 4 views
3

最近、私はベクトル記憶タイプを使って波形を計算するのに必要な時間を決定しようとしていました。厳密に計算のタイミングを決めるこのHaskell関数に問題はありますか?

このような長さなどを印刷しなくてもいいと思っていました。最後に、次の2つの定義を考えました。それは十分にシンプルなように見えますが、最初に関数を実行するときにゼロ以外の計算時間を期待通りに出力することがわかりますが、ここで怠けた場合の注意点がありますか?

import System.IO 
import System.CPUTime 
import qualified Data.Vector.Storable as V 

timerIO f = do 
    start <- getCPUTime 
    x <- f 
    let !y = x 
    end <- getCPUTime 
    let diff = (fromIntegral (end - start))/(10^12) 
    print $ "Computation time: " ++ show diff ++ " sec\n" 

timer f = timerIO $ do return f 

main :: IO() 
main = do 
    let sr = 1000.0 
     time = V.map (/ sr) $ V.enumFromN 0 120000 :: V.Vector Float 
     wave = V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float 

    timer wave 
    timer wave 

版画、

Computation time: 0.16001 sec 
Computation time: 0.0 sec 

は、任意の隠されたバグがここにありますか?私は実際に厳密性フラグを持つletが本当にここに行く最良の方法であるかどうか確信していません。これを書くより簡潔な方法がありますか?私が知っておくべきこれを既に行っている標準機能はありますか?

編集:私は基準について読んだことを言及する必要がありますが、この場合、プロファイリング専用の平均タイミングを計算するための堅牢な方法は探していませんでした。むしろ、アプリケーションの通常の実行中にいくつかの計算のタイミングをトレースするために、単一のタイマーを自分のプログラムに統合する単純な/低オーバーヘッドな方法を探していました。クライテリアはクールですが、これは若干異なるユースケースでした。

答えて

4

厳密なVectorまたはUArrayの場合は、タイミングコードが正しく動作しますが、レットバインディングのbangパターンの代わりに、モナドバインドで、私には立派に見える

start <- getCPUTime 
!x <- f 
end <- getCPUTime 

は、またはあなたが強打パターンがGHC拡張されているのに対し、(はず)移植性の利点を持っているControl.Exception.evaluate

start <- getCPUTime 
evaluate f 
end <- getCPUTime 

を使用することができます。 WHNFが十分でない場合、あなたはそれが毛であると、繰り返し同じ計算をタイミング、しかし

start <- getCPUTime 
!x <- rnf `fmap` f 
end <- getCPUTime 

のように、rnfまたはdeepseqを使用して、たとえば、完全な評価を強制する必要があります。場合は、あなたの例のように、あなたはものに名前を付け、それ

timer wave 
timer wave 

コンパイラの株式を計算を呼び出して、それは一度だけ行うのと最初timerの呼び出しが、すべてがゼロ(または非常に近いを返します0回)。あなたは、名前の代わりにコードでそれを呼び出す場合は、共通部分式除去を行う場合、

timer (V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float) 
timer (V.map (\x -> sin $ x * 2 * pi * 10) time :: V.Vector Float) 

コンパイラは、計算を共有することができます。 GHCはCSEをあまり使っていませんが、いくつかのことがあります。私は、これを見つけて共有すると確信しています(最適化でコンパイルするとき)。コンパイラが確実に計算を繰り返すようにするには、計算に必要な時間に影響を与えずに簡単に行うことができないという事実を、それらが同じである(またはいくつかの低レベルの内部構造を使用する)ことを隠す必要があります。

¹計算にかなりの時間がかかる場合はうまく動作します。短い時間しかかからない場合、外部の影響(CPU負荷、スケジューリング、...)によってもたらされるジッタは、単一のタイミングをあまりにも信頼性の低いものにします。次に、複数の測定を行う必要があります。そのためには、他の場所で言及したように、criterionライブラリは、ロバストなタイミングコードを書く負担を軽減する優れた方法です。

+0

ありがとうございます!その理由は、私がモナドバインドにバングを追加しようとしたのですが、コンパイラによって許可されていないようです。しかし、私は文法が間違っている(変数の反対側にあるので)ことを知っています。 – Steve

+0

おっと、typo。変数は '!x < - f'の前に来ます。 –

+0

奇妙なことに、私は '!x < - f'を置くと"不正なバングパターン(use -XBangPatterns) "が出ます。 (そして、はい、 'x!< - f'も動作しませんでした)私はGHC 7.0.4を使用しています。 – Steve

3

あなたはthe deepseq packageをよく知っていますか?これは、かなり多くの目的のためにthe criterion packageによって使用されています。

と言えば、criterion自体が必要な処理を行うかどうかを検討したい場合があります。

+3

「基準」と言えば、ベンチマークを行っている人は誰でも実際に使用するはずです。 – Carl

+1

@Carl:良い点、それを言及しているはずです。 –

+1

私は基準に感銘を受けていません。すべてのケースで、基準結果に、私が必要としなかった偏差などに関する多くの情報が含まれていることを除けば、System.CPUTimeと同じ結果を出したことをテストしました。 –

関連する問題