[Haskell]僕が人生で起こした唯一のスペースリーク

これは、Haskellスペースリーク Advent Calendar 2015の7日目の記事です。

僕は Haskell で主に IO なコードを書いているからか、あまりスペースリークを起こしたことがない。これまで起こした唯一のスペースリークは、サーバプログラムの中の以下のようなコードだった。

atomicModifyIORef ref (\_ -> (tmstr, ()))

これは、時間を文字列に変換して IORef にキャッシュさせるコードだ。あるスレッドが、毎秒このコードを呼び出す。不思議なことに、クライアントからのアクセスがあるとスペースリークにはならないのだが、長時間アクセスがないとサーバのプロセスが太っていた。

このリークは、結果を利用していないために起こる。そもそも、このコードは古い値を利用せずに、単に新しい値で置き換えているだけだから、writeIORef で十分だ。

writeIORef ref tmstr

では、カウンターのように古い値を使う場合はどうすればいいだろう?

atomicModifyIORef ref (\i -> (i+1, ()))

このスペースリークをなくすための定石は、結果を評価してやることである。

x <- atomicModifyIORef ref (\i -> (i+1, ()))
x `seq` return ()

BanPatterns を使うと、すこしスッキリする。

!_ <- atomicModifyIORef ref (\i -> (i+1, ()))

一番よいのは、CAS で値を置き換えるときは遅延評価で、CAS が成功し後に正格評価する atomicModifyIORef' を使うことだ。

atomicModifyIORef' ref (\i -> (i+1, ()))

おまけ

昔この話を Monad.Reader 19に書いたところ、atomicModifyIORef のマニュアルに注意書きが足された。:-)

atomicModifyIORef' は、並行 Haskell の奥義である。詳しくは「Haskellによる並列・並行プログラミング」を読んでほしい。