IT練習ノート

IT関連で調べたこと(実際は嵌ったこと)を書いています。

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のアドオンを作ってみた

あらかじめユーザ登録をして、ログインをしておく。

f:id:naotoogawa:20190520224420g:plain
登録サイト

Submit Your First Add-onを選択

f:id:naotoogawa:20190520224501g:plain
配布方法の選択

Firefox Add-onsに登録するのでOn this siteを選択しContinueを選択、次の画面で、Select a fileを選択し、作成したAdd-onをアップロードする。

f:id:naotoogawa:20190520224520g:plain
検証エラー

See full validation reportのリンクを選択すると、エラーの内容がわかる。

f:id:naotoogawa:20190520224533g:plain
エラー詳細のページ

エラーを確認し、修正し、再度アップロードする。

f:id:naotoogawa:20190520224554g:plain
検証に成功

コードを難読化などをしていると、ソースコードの提出が求められる。

f:id:naotoogawa:20190520224605g:plain
ソースコード要求の確認

今回はAdd-onのコードは素のJavaScriptなので、Noを選択して、Continueを選択し、次の画面で、Add-onの説明文を登録する。

f:id:naotoogawa:20190520224617g:plain
説明文の登録

しばらくすると、登録される。

f:id:naotoogawa:20190521214247g:plain
登録完了

Add-onを更新する手順は、新規登録と同じ。

f:id:naotoogawa:20190520224656g:plain
更新失敗した場合

履歴が確認できる。

f:id:naotoogawa:20190520224710g:plain
更新成功

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

io-stream sample 2