ghciを使ってざっくり性能測定
カジュアルにghci
上で性能測定をしようと思ったのですが、少し工夫が必要なようです。
ghci
では:set +s
することで性能を測ることができます。。
Haskell function execution time - Stack Overflow
ghci
上での計測なので、表示する処理も計測に含まれしまいます。
Prelude> replicate 9999 1 [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 途中省略 ,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] (0.42 secs, 35,127,944 bytes)
表示する処理自体は除いて計測をしたいのですが、ghci
上で表示させないことはできないようです。(そもそも、interactive
に処理結果を見ながら実行していくためのだからと思います。)
https://stackoverflow.com/questions/32926805/disable-printing-of-io-results-in-ghci
表示結果を無理やり無視すると、そもそも計測したい処理が、遅延評価のため、実行されません。
Prelude> (\x -> return ()) $ replicate 9999 1 (0.00 secs, 92,352 bytes)
強制的に評価をさせるために、deepseq
パッケージを使います。
deepseq: Deep evaluation of data structures
Prelude> import Control.DeepSeq (0.00 secs, 0 bytes) Prelude Control.DeepSeq> deepseq (replicate 9999 1) (return ()) (0.13 secs, 648,528 bytes)
これで、おおよそ、評価したい処理の性能を見ることができる(はず)。
実際に性能を比較したかった処理は、リストを2つに分割する実装でした。型としては[a] -> ([a], [a])
です。
一つはlength
を使って2つに分割します。これは、コード上リストを2回走査することになります。
Prelude Control.DeepSeq> splitHalf' xs = splitAt (length xs `div` 2) xs (0.00 secs, 0 bytes)
もう一つは、リストの走査を1回で行う実装です。
Prelude Control.DeepSeq> first f (a,b) = (f a, b) (0.00 secs, 0 bytes) Prelude Control.DeepSeq> :{ Prelude Control.DeepSeq| Prelude Control.DeepSeq| splitHalf :: [a] -> ([a],[a]) Prelude Control.DeepSeq| splitHalf xs = go xs xs Prelude Control.DeepSeq| where Prelude Control.DeepSeq| go (y:ys) (_:_:zs) = first (y:) (go ys zs) Prelude Control.DeepSeq| go ys _ = ([],ys) Prelude Control.DeepSeq| :} (0.01 secs, 0 bytes)
2つを比較すると、1回の走査のほうが処理が速いと思ったのですが、結果は逆でした。
Prelude Control.DeepSeq> deepseq (splitHalf' $ replicate 9999999 'a') (return ()) (1.58 secs, 1,200,091,064 bytes)
Prelude Control.DeepSeq> deepseq (splitHalf $ replicate 9999999 'a') (return ()) (7.39 secs, 1,732,534,400 bytes)
Firefoxのアドオンを作ってみた
あらかじめユーザ登録をして、ログインをしておく。
Submit Your First Add-onを選択
Firefox Add-onsに登録するのでOn this siteを選択しContinueを選択、次の画面で、Select a fileを選択し、作成したAdd-onをアップロードする。
See full validation reportのリンクを選択すると、エラーの内容がわかる。
エラーを確認し、修正し、再度アップロードする。
コードを難読化などをしていると、ソースコードの提出が求められる。
今回はAdd-onのコードは素のJavaScriptなので、Noを選択して、Continueを選択し、次の画面で、Add-onの説明文を登録する。
しばらくすると、登録される。
Add-onを更新する手順は、新規登録と同じ。
履歴が確認できる。
HLintでよく指摘される内容
今作成しているライブラリでHLint
を実行してみました。傾向としては、冗長なdo
と冗長なカッコの指摘が多いようでした。
src/Database\SQLServer\Internal\Tokens.hs:626:1: Suggestion: Use newtype instead of data Found: data ROW = ROW{columnValues :: [ColumnValue]} deriving (Show, Eq, Generic) Why not: newtype ROW = ROW{columnValues :: [ColumnValue]} deriving (Show, Eq, Generic) Note: decreases laziness
src/Database\SQLServer\Internal\Types.hs:406:20: Warning: Use replicate Found: take n $ repeat $ Bit.block Bit.bool Why not: replicate n (Bit.block Bit.bool)
src/Database\SQLServer\ResultSet.hs:467:37: Suggestion: Avoid lambda Found: \ row -> getRecord' meta row Why not: getRecord' meta
src/Lib.hs:71:20: Suggestion: Use null Found: length x == 0 Why not: null x Note: increases laziness
src/Lib.hs:90:12: Suggestion: Move brackets to avoid $ Found: "[trace] [c] ====================> RCPRequest request " ++ (show $ BS.length rcpreq) Why not: "[trace] [c] ====================> RCPRequest request " ++ show (BS.length rcpreq)
src/Lib.hs:127:3: Warning: Use maybe Found: if isJust rawsql then example_execute_sql_raw socket $ fromJust rawsql else maybe (case rpc of Just (r, _) -> example_execute_rpc socket r Nothing -> error "invalid parameter") (example_execute_sql socket) file Why not: maybe (maybe (case rpc of Just (r, _) -> example_execute_rpc socket r Nothing -> error "invalid parameter") (example_execute_sql socket) file) (example_execute_sql_raw socket) rawsql
src/Lib.hs:142:16: Suggestion: Redundant $ Found: runGet (MT.evalStateT resolveTokens []) $ batchRes Why not: runGet (MT.evalStateT resolveTokens []) batchRes
src/Lib.hs:143:3: Warning: Use when Found: if info then mapM_ (printToken) tokens else return () Why not: Control.Monad.when info $ mapM_ (printToken) tokens
src/Lib.hs:150:15: Warning: Redundant do Found: do mapM_ (mapM_ putStrLn) $ getRecordList2Str rs Why not: mapM_ (mapM_ putStrLn) $ getRecordList2Str rs
rc/Lib.hs:226:13: Suggestion: Use <$> Found: MT.lift (get :: Get RETURNSTATUS) >>= return . TknreturnStatus Why not: TknreturnStatus <$> MT.lift (get :: Get RETURNSTATUS)
src/Lib.hs:410:4: Suggestion: Redundant bracket Found: "@foo" @@ (fromGregorian 1900 1 1) Why not: "@foo" @@ fromGregorian 1900 1
.\src\Lib.hs:483:54: Suggestion: Use section Found: ((==) '=') Why not: ('=' ==)
.\src\Lib.hs:516:1: Suggestion: Use camelCase Found: exNullSql_variant = ... Why not: exNullSqlVariant = ...
io-streamでストリームを変更する
Generator
を作る必要があります。
以下のようにしてしまうと、固定値を永遠に流すストリームになってしまいます。
badFunc :: InputStream Int -> IO (InputStream Int) badFunc s = do v <- S.read s case v of Just x -> makeInputStream $ return $ Just x Nothing -> makeInputStream $ return Nothin