トップ «前の日記(2013-12-31) 最新 次の日記(2015-07-04)» 月表示 編集

日々の流転


2014-04-30 [長年日記]

λ. IOモナドでヒープ割り当てを強制される件

Haskellでプログラムを書いていて、数年以上ずっと悩んでいる点として、

sumI :: UArray Int Int -> Int -> Int -> Int -> Int
sumI a i end ret
  | i <= end  = sumI a (i+1) end (ret + (a ! i))
  | otherwise = ret

みたいな関数だと、UArray Int Int -> Int# -> Int# -> Int# -> Int# という型のワーカ関数が生成されるので、結果を即消費するような場合にはヒープ割り当てが発生しないのに対して、

sumM :: IOUArray Int Int -> Int -> Int -> Int -> IO Int
sumM a i end !ret
  | i <= end = do
      x <- readArray a i
      sumM a (i+1) end (ret+x)
  | otherwise =
      return ret

みたいな関数だと、IOUArray Int Int -> Int# -> Int# -> Int# -> State# RealWorld -> (# State# RealWorld, Int# #) という型のワーカ関数ではなく、IOUArray Int Int -> Int# -> Int# -> Int# -> State# RealWorld -> (# State# RealWorld, Int #) という型のワーカーしか生成されず、Intが必ずヒープ割り当てされてしまう。

通常は大して問題にならないのだけど、凄まじい回数呼ばれる関数だと、それが積もり積もってプログラム全体のメモリ割り当ての数割を占めてしまったりして、そういう場合にはGCプレッシャーも馬鹿にならないので、出来ることなら1バイトたりともヒープ割り当てしたくないのである。 望む worker-wrapper transformation 結果を手で書いてしまえば良いのだけれど、GHC拡張に依存したコードを直書きするのも気が引けることもあって……

Tags: haskell
本日のツッコミ(全2件) [ツッコミを入れる]
ψ mkotha (2014-05-01 22:05)

ご存知かもしれませんがJoachim Breitnerさんがこの問題に取り組んでますね https://ghc.haskell.org/trac/ghc/wiki/NestedCPR

ψ さかい (2014-05-07 18:01)

おお。知らなかったです。ありがとうございます!