あどけない話

インターネットに関する技術的な話など

GHC とスペースとリーク

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

:sprint と MonomorphismRestriction

サンクのリークを防ぐには、どの式がサンクかを理解する必要がある。そのために便利なのが、GHCiの:sprint コマンドだ。MonomorphismRestriction 拡張がデフォルトで有効だった GHCi 7.6 以前は、このコマンドは直感的に動く。

> let x = 1 + 2
> let y = (x,x)
> :sprint x
x = _
> :sprint y
y = (_,_)
> print x
3
> :sprint y
y = (3,3)

GHCi 7.8 以降では、MonomorphismRestriction拡張がデフォルトで無効になった。単に式を評価する使い方なら、こちらの方が便利だ。しかし、:sprintは挙動がおかしくなってしまう。

> let x = 1 + 2
> let y = (x,x)
> :sprint x
x = _
> :sprint y
y = _    {- あれれ? -}

MonomorphismRestriction を有効にすれば、:sprintは期待通りに動く。

> :set -XMonomorphismRestriction 
> let x = 1 + 2
> let y = (x,x)
> :sprint x
x = _
> :sprint y
y = (_,_)

問題は型が決まらないことなので、明示的に指定してもよい。

> let x = 1 + 2 :: Int
> let y = (x,x)
> :sprint x
x = _
> :sprint y
y = (_,_)

unbox-small-strict-fieldsフラグ

Haskellでは、基本的にデータは参照で指される。GHC 7.6 までは、即値を使うには、"!" と UNPACK プラグマの両方を指定する必要があった。

data Foo = Foo {
    bar :: {-# UNPACK #-} !Int
  , baz :: {-# UNPACK #-} !Int
  }

これはあまりにもダサい。GHC 7.8 以降では、unbox-small-strict-fieldsフラグがデフォルトで有効になっているので、即値として扱える正格なフィールドは即値となる。

data Foo = Foo {
    bar :: !Int
  , baz :: !Int
  }

マニアックなことを言うと、たとえば Int は以下のように定義される。

data Int = Int Int#

Int が参照で、Int# が即値を表す。詳しくはUnboxed values as first class citizensを参照のこと。

Strict プラグマと StrictData プラグマ

GHC 8.0 以降では、Strict プラグマと StrictData プラグマが使えるようになる。"!" もダサいという人にはオススメ。

Weak ThreadId

スレッドリークの記事で、Weak ThreadId を使えばスレッドがリークしないと書いたが、実はリークしていることが判明した。

  • ThreadId を保持するとスレッドのスタックもスレッドに関するデータもリークする
  • Weak ThreadId を保持するとスレッドのスタックは開放されるが、スレッドに関するデータはリークする

はっきり言ってGHCのバグ。対策として、

  • Weak ThreadId の利用を止めて素直に ThreadId を使う
  • ThreadId を保持する IO アクションを適宜 return () で上書きする

のようにするよい。詳しくは、Weak ThreadId still leaks threadsを読んで欲しい。