備忘録 blog

Docker/Machine Learning/Linux

ガベージコレクション(GC)

tl;dr

ガベージコレクションGC)についてサーベイしたので、メモを残す。

ガベージコレクションのアルゴリズムと実装

ガベージコレクションのアルゴリズムと実装

本文中の多くの記述は、上記の書籍を参考にした。筆者は素人なので、詳細な記述や実装については参考文献を参照されたい。

Introduction

プログラムを書く上では、例えば入力長が分からない入力を受け取るためなどでメモリを動的に確保したい場合がある。その際ヒープ領域とよばれる領域にメモリを確保するが、使い終わった際には解放しなければ、確保しているが使われない領域が生じるメモリリークが発生してしまう。

しかし一般に、その解放を手動で管理することは面倒である。プログラマがどの時点で確保した領域が不要となるか、その全てのオブジェクトに対して追跡することは厄介である。そのために言語処理系やライブラリでメモリ解放の手間を肩代わりするような機能が実装されている。これがガベージコレクションである。

確保したメモリ領域を手動でfreeする場合に比べて、ガベコレのある言語を用いると実装が楽になる一方、GCが起きる条件を制御できず、また多くの実装で無視できない停止時間が存在するといった問題点はあるが、小さいオブジェクトが大量に死ぬような場合ではある種のGCのほうが有利であり、どちらを選択するのが良いかは時と場合による。

GCの評価基準

スループット(単位時間当たりの処理能力)とレイテンシ(要は最大で停止している時間)はトレードオフにあり、GCの種類によりどちらにより重きを置くかは変わる。またヒープの使用効率を向上させる(例えばコピーGCで、断片化を防ぐために生存しているオブジェクトをつめるような実装がある)ことが必要となる場合もある。

C, C++では

Cでは基本的にはGCはない。mallocしたヒープ領域は自分でfreeしなければならない。 C++でもnewした領域はdeleteしなければならないが、スマートポインタというものがあり、boostに実装されているほかC++11では以下のものがある。スマートポインタは、確保したメモリ空間を、スコープから外れたときに自動で開放してくれるポインタである。

  • unique_ptr, shared_ptr, weak_ptr unique_ptrはメモリの所有権をそこに限定するもの。shared_ptrは所有権を参照カウンタによって管理し、共有することが可能である。

参照カウンタ

そのポインタを参照するごとにカウンタをインクリメントしていき、参照しなくなるとデクリメントする。カウンタが0になるとそのカウンタはどこからも参照されないということになり、安全に回収することができる。

このときの問題点として、循環的にオブジェクトを参照したいときにshared_ptrでは、その領域が解放されなくなってしまう。これを防ぐためにweak_ptrが用いられる。弱い参照と呼ばれるが、これは所有権を持たずにメモリを参照できるポインタである。例えば木構造で親子の双方向にポインタを持ちたい場合などに、これを用いることが可能である。

その他、利点として最大停止時間が短いこと、ポインタを走査するという手順が必要ないこと、逆に欠点としてカウンタの処理や実装が煩雑となることがあげられる。

またC/C++上でGCを実装したライブラリもある。

  • Boehm-GC

スマートポインタでなくともmallocのかわりの関数を呼ぶと、自動でfreeしてくれるGCは古くから提案されている。このときGCする対象となるオブジェクトを知りたいが、どのオブジェクトが参照されているかを知るためには、そのオブジェクトをさすポインタがあるかどうかで区別するしかない。しかしながら、入っている値が単なる数値なのかポインタなどのかは区別することができないので、全ての値をポインタと見なしてオブジェクトを辿り、辿れないオブジェクトをゴミとして回収する。このような仕組みで動くGCを保守的GCと呼ぶ。

従って、もしスタックに載った値がたまたまオブジェクトを指すポインタになってしまうような場合は、そのポインタの先がゴミであっても回収されず、データセットによっては大きいスペースリークが発生してしまうことが考えられる。32bitのマシンでは、このようなコリジョンが発生してしまうことは可能性としてそれほど低くないが、64bitのマシンでは場合にもよるが無視できるレベルだろう。

APGAS型の並列計算言語であるX10も、C++にトランスパイルした場合はこのGCによってヒープは管理される。

sharply.hatenablog.com

少し注意しなければならないというか、当たり前のことであるが、参照を外さないとGCで回収されない。逆に言えば参照が外れてしまうとGCで回収されてしまう可能性がある。例えば双方向連結リストでメモリ空間を節約するために、左右のポインタのxorを持っておくという方法があるが、そのような方法で管理しているポインタはこうしたGCでは辿ることができないので、オブジェクトが破棄されてしまうおそれがある。

Haskell

コピーGCをベースに、Cheneyのアルゴリズムや世代別GCを取り入れている。以下のスライドをもとに詳説を試みる。

CS240h Lecture slides

https://takenobu-hs.github.io/downloads/haskell_ghc_illustrated.pdf

Haskellでは、RTS(Runtime-System)がネイティブスレッドやGCについて司る。

コピーGC

生きているオブジェクトを別領域に全てコピーし、もとの領域を全て解放するようなGC

  • Stop the World
    • 実行系は全て停まってガベコレのスレッドだけが動く

コピーGCはゴミを捨てるのにほとんどコストがかからないために生きているオブジェクトが少ないほど有利であり、また別領域に詰め込むことでヒープのフラグメンテーションが起きないことが利点である。そして、GCの途中で参照関係にあるオブジェクトが近接するようになるので、GCに有利である。

デメリットとしては、ヒープ領域の使用効率が悪化すること、保守的GCとの相性が悪いことがある。これは、コピーGCでヒープの値のアドレスが変わってしまうと、それをさすアドレスも書き換えなければならないが、保守的GCをしたいような場合には、スタックなどに積まれている値がアドレスであるか値であるか分からないため、書き換えられるか分からないということがあるためである。

世代別GC

少しの長生きしたオブジェクトは長生きするが、残りのオブジェクトは量が多く、すぐ破棄されるという経験的な性質をもとに、アロケートされたオブジェクトは第一世代に属し、Haskellの場合では生きているオブジェクトが少ないときに有利なコピーGCで回収し、ある程度長生きしたオブジェクトは第二世代に移行し、一般にはそこでは回収しないか、低頻度でコピーGCやMark & Sweepで回収するという仕組みである。

これを組み合わせ世代別コピーGCは、JVMの実装でもみられるが、全く書き換えられないオブジェクトが何度もコピーされるのを防ぐことができる。ここでは高頻度でガベコレされる領域Nurseryと、長生きする領域Generation0, step1とGeneration1を分ける。Nurseryは高頻度にガベコレされる(Minor GC)が、一定以上長生きしたオブジェクトはstep1に移行し、Generation1へと昇格し、それほど高頻度にガベコレされなくなる(Major GC)。

ところで、JVMでもこうしたフィーチャーは実装されているが、Javaのような言語ではthisがあるせいで、Generation1に相当するオブジェクトがNurseryへのポインタを持つことがしばしば起こる。このとき、Nurseryを注意深くガベコレしなければSeg4が起こるため、Nurseryの高速なガベコレを妨げる。これに対してHaskellではmutationがrareであること、またIORefで触るような場合はどのみち遅いことから、一般の場合でそれによるオーバーヘッドはそれほど大きくならない。

また更にmajor GCにおいてはParallel GCが可能である。これはGCのスレッドが複数あり、そのそれぞれがコピーGCを行うが、複数のスレッドが同じオブジェクトをコピーしようとすることがある。しかしそうであったととしても、immutableな小さいオブジェクトであるならば複数コピーされることによるオーバーヘッドはそれほど大きくないため、ロックせずにガベコレを並列で走らせることができる。これで純粋関数であることで柔軟さを得ることが可能となる。

このように、ハイスループットGCHaskellRTSでは実現している。

Go

Go1.5〜でのGCはレイテンシの短縮にフォーカスしている。

Mark & Sweep

  • Mark
    • ポインタを辿り、オブジェクトにマークする
  • Sweep
    • マークが付けられていないオブジェクトは捨てて良い

しかしこの場合、GC実行中にプログラムが動作してポイントの様態が変化してしまうと、本来捨てるべきでない領域をSweepしてしまうかもしれない。こういった問題を防ぐために、一般にはSTWで実行系は全て停まってガベコレのスレッドだけが動くようにしている。しかし当然、STWの間は実行系が動けず、そのレイテンシはヒープ使用量が大きいほど増大することが見込まれる。

また一般に、Mark & Sweepの特性として、以下のものがある。

  • 保守的GCとの相性が良い
  • CopyGCに比べて、フラグメンテーションが起きやすい。また開放されたメモリ空間も非連続になりがち。

Go1.5〜では、Tri-color markingとよばれるオブジェクトを3分類し、コンカレントGCでほとんどSTWを起こさずにGCすることが可能となっている。これはインクリメンタルGCの一種である。

Rust

Rustでは生存時間が型レベルで管理されており、生ポインタを触ることは一般に忌避される。そこで何らかの型でラッピングしてポインタにアクセスする必要がある。

postd.cc

qiita.com

qiita.com

Rustではヒープ領域を確保する方法はBox, Rc, Arcがある。Boxはunique_ptrに対応するようなもの、Rc、Arcはshared_ptrに対応するようなものだと認識している。例えば再帰的データ構造を定義する際、Haskellではそれが参照であることを明示する必要はないが、RustではBoxで明示的にヒープを確保していることを示す必要がある。またRcとArcの違いは、それがスレッド間で共有されるかどうかという点である。

ところが、BoxやRcで指す先の値をmutableにしたいという要請がある。これを一般化するとimmutableなオブジェクトの内部に、mutableなオブジェクトを持つ要請であり、こうした要請に対してRefCell、Cellが用いられる。Cellは単純な型ぐらいに対してしか使えないので、参照型を持つ場合ではRefCellを使うことが多く、Rcと組み合わせてRc<RefCell<T>>のようになる。更に更に、スレッド間でmutableなオブジェクトを共有したい場合はMutexを用い、Arcと組み合わせてArc<Mutex<T>>のようになる。

困ること

ところで、こうした参照カウント方式ではDAGならともかく、循環リストや双方向連結リストのようなグラフ構造を持つことができない。

agtn.hatenablog.com

qnighy.hatenablog.com

これに対してTyped Arenaという方法があるが、領域を開放するためのインターフェースとして実装されているのは、全て捨てるということしかできない。つまり参照しなくなったオブジェクトがArena内にあったとしてもそれをガベコレすることができない。参照しなくなる要素が増える場合、メモリリークが大量に発生することになってしまう。

doc.rust-lang.org

これに対応する方法としては2つであり、1つはfreeListを作り、そこに削除した要素を登録し、次にnewする際にその要素を用いるようにする。ただしその場合は削除した要素が飛び飛びになっている場合、近接して確保したい領域が離れて確保される懸念がある。もう1つの方法としてはある程度使われない要素が増えた時にもう1つArena領域を取り、参照されている要素を走査し詰め込んだ後、元のArenaを全て捨てるというさながらコピーGCのような実装をするかである。

どちらにしてもTyped ArenaはArena内部に持つ要素の型と生存時間が管理されることは利点であるが、参照しなくなった領域に対するメモリリークに対する解決策ではないように思われる。

その他調べたこと

  • RubyはMRubyではマーク&スイープ、Ruby2.1からは世代別GCを取り入れている。
  • Pythonは参照カウントで管理。循環参照GCは世代別GCで管理している。
  • JavascriptのV8は世代別GCで、マイナーGCはコピーGC、メジャーGCはマーク&スイープGC

まとめ

きわめておおざっぱな分類は以下。

言語 種類 特徴 詳細
C 基本的にはなし Malloc, freeで管理
C++11 参照カウンタ unique_ptr, shared_ptr, weak
C,C++(Boehm-GC) Mark & Sweep 保守的GC,STW ライブラリを導入
X10 Mark & Sweep 保守的GC,STW C++にトランスパイルするとBoehmGCが使われる
Haskell コピーGC STW Cheneyのアルゴリズム+世代別GCなど
Go1.4 Mark & Sweep parallel STW
Go1.5〜 Mark & Sweep Concurrent/Incremental
Rust 参照カウンタ 生存時間で管理 Rc, Arc, Arena

参考

www.slideshare.net

GolangのGCを追う | SOTA

postd.cc

dic.nicovideo.jp

qiita.com

zonomasa.hatenablog.com

世代別ガベージコレクション - Wikipedia

seesaawiki.jp

http://matsu-www.is.titech.ac.jp/~endo/gc/gc.pdf

Mostly-Concurrent Mark & Sweep GC のアルゴリズム

qiita.com

dev.classmethod.jp

UEFI環境下でのgrubでLinuxとWindows 10をデュアルブートする

Windowsでのブートの制約

Dual boot with Windows - ArchWiki

上記より、Windows 10ではUEFI-GPTあるいはBIOS-MBRの組合せでしかブート出来ません。ところで旧来のBIOSブートに比べてUEFIブートは高速であることが知られています。またMBRに比べてGPTでは基本パーティション数の制限が緩和されているというように、UEFI-GPTブートがモダンなブート環境であることは間違いないでしょう。特にWindows 10をクリーンインストールするとき、デフォルトでパーティションを切ろうとするとEFI System Partition (ESP) を作る挙動がみられるように、UEFI-GPTでのブートを推奨しているようです。

ここでは、もともとLinuxを起動する際に用いていた従来のBIOS-MBR型で定義されていたgrubのブートをUEFIブートにリフトし、Windows 10とのデュアルブート環境を構築することを目論みます。grub2.02 rc2を用いました。

予めWindows 10の高速スタートアップ機能は無効にしておきましょう。

grub2の仕様

デュアルブート対象のLinux上で作業します。まずは既存のgrubから、UEFIWindowsをチェインして呼び出すことを考えます。そのためにはUEFIWindowsを認識させなければなりません。しかしos-proberを行っても、Windows Boot Managerを発見できません。そこでwikiを参照して/etc/grub.d/40_customに書き込んで、grub-mkconfigしました。

GRUB - ArchWiki

# grub-mkconfig -o /boot/grub/grub.cfg

こうしてgrubのエントリを追加すればもうWindows 10を起動できるようになったのかというとそうではなく、grubのメニューから追加したエントリを選択するとInvalid signatureと言って怒られます。これはBIOS型のgrubからUEFIブートローダーをチェインして起動することが出来ないためであると考えられます。

後で試してわかるのですが、LinuxBIOSブートで起動している場合はUEFIブートのOSを検出してくれませんが、UEFIブートで起動した場合はos-proberWindows Boot Managerを検出でき、systemd-bootやrEFIndと同様にgrub-mkconfigで自動でUEFIWindowsを探してくれます。そこで上記の手順を省略し、下記の方法でUEFIブートによって起動してからgrub-mkconfigを行えば、手動でWindowsのエントリを追加する必要はないと考えられます。

そこで次に、UEFIモードでgrubをインストールすることを考えます。UEFIの大きな特徴として、一つのESP内に複数のブートローダーを共存させることができます。 ここでは既存のMBRブートのパーティションを残し、Windows 10インストール時に生成したESPにWindows Boot Managerと共存させる形でgrubをインストールすることにしました。/dev/sda1がESPであったとします。

# mount /dev/sda1 /mnt
# grub-install --target=x86_64-efi --efi-directory=/mnt --bootloader-id=grub

としたいのですが、このコマンドを実行するとUEFI経由で起動しろと怒られます。

efibootmgr: EFI variables are not supported on this system.

この状況でUEFIブートを行ったとしても、このgrubは起動できません。ブートエントリにgrubが追記されていないためです。しかしUEFIのブートエントリに追加しようにも、内部的にはefibootmgrを用いているようですが、従来のBIOSブートで起動したlinux側からはそれを編集することができません。UEFIシェル上でbcfgコマンドでブートローダーを追記してもよいのですが、失敗するのが怖いのでgrubの機能を用いて自動でやってもらいたいと思います。そのためにはUEFI経由で起動する必要があります。

UEFIシェルを触る

先述の理由からUEFI経由で起動したいのですが、BIOSからUEFIの設定を変えてgrubから起動しようとしても、今grub-installしたブートローダーはエントリには表示されないので、UEFIシェルを叩いて直接起動するしかありません。BIOSのブートオプションでUEFI shellを選択します。このシェル画面で先ほどgrub-installしたefiファイルを実行します。

> fs1:
> cd EFI
> cd grub
> grubx64.efi

こうしてから起動すると、grubのブート画面が立ち上がります。この画面でLinuxを選択するとEFI経由で起動したことになるので、再びgrub-installすることでファームウェアのブートエントリにgrubを追記することができます。こうすると、再起動した時にUEFI経由でgrubの画面が表示され、Windows 10を選択するとgrub経由で起動できるようになります。

以上のようにして、grubによるWindows 10とLinuxのマルチブートを行いました。ただしこの場合、BIOSブートでインストールされたWindows 7などを今までのgrub経由でブートしていた場合は、UEFI経由のgrubからでは起動できなくなります。

関連URL

uefi

Unified Extensible Firmware Interface - ArchWiki

EFI システムパーティション - ArchWiki

Rustの覚書

全体的に

postd.cc

https://japaric.github.io/discovery/

インストール

rustを手元の環境に導入する方法として、rustc(rustコンパイラ)を直に入れるほかにrustupと呼ばれるツールチェーン管理用のソフトウェアを介してインストールする方法がある。これはnightlyビルドやstableビルドなどが選べたり、バージョン管理が可能だったりするので、nightlyビルドでなければ通らないライブラリを使うときに個人的には便利であった。

blog.rust-lang.org

Cross Compile

github.com

によれば、

It's hard to find a cross C toolchain (and cross compiled C libraries) between different OSes (except perhaps from Linux to Windows). A much simpler and less error prone way is to build natively for these targets because they are tier 1 platforms. You may not have direct access to all these OSes but that's not a problem because you can use CI services like Travis CI and AppVeyor. Check my rust-everywhere project for instructions on how to do that.

ということだそうだ。

所有権・参照

qiita.com

qiita.com

Box, Rc, Cell, RefCell

agtn.hatenablog.com

qiita.com

ヒープ領域を確保するためのBox、C++のスマートポインタに相当するようなRcなど。

Optional / Result型

qiita.com

Logger

qiita.com

コマンドライン引数

qiita.com

文字列処理

qiita.com

HaskellにおけるHTMLのテンプレートエンジンを調査する

HaskellはWebフレームワークだけでもYesod, Snap, Happstackといった重厚長大型のフレームワークに加え、軽量なもの/API向けのものでもSpock, Scotty, Servantなど乱立しているが、Template Engineとて例外ではない。Blaze-html, shakespeare, mustache, stache, ede, lucid, heistなどこれもまた様々存在し、特にSpockやScottyといったフレームワークではテンプレートエンジン選択の自由度が高いので、どれを使おうかと思い、調べたのでここにメモを残す。

Template Engine

stackoverflow.com

stackoverflowでも何を使えばよいか、議論となっている。

stache

stache: Mustache templates for Haskell

Mustacheという汎用テンプレートエンジンのhaskell実装の1つ。 ただMustache自体の仕様として記法が簡潔でよいのだが、if文の分岐がBooleanのTrue/Falseしかできないなど、少し凝ったことをしづらい面がある。 ただAesonのインスタンスをそのまま渡せるのは心強く、Viewに送りたいデータを新しいデータ型として定義し、deriveJSONを呼んでおけば、あとはテンプレートにそのインスタンスを渡すだけでよいというのは手軽である。

記法は以下のような形。

Hi, {{name}}! You have:
{{#things}}
  * {{.}}
{{/things}}

こうすると、things配列の要素が.のところに順々に出力される。

Mustache

mustache: A mustache template parser library.

これも上と同じMustacheの実装。Data.AesonのValueを渡せるのはよいが、Maybe型の値のデータをみたとき、Nothingの時の値は空白が入っていることを期待したらnullが入っているというのはstacheと異なる点。そういった点で、stacheの時と同じmustacheファイルに対して同じ挙動を示すというわけではなかった。下の記事によれば、

Mustache templates in Haskell - Tutorials

it again makes simple things complex using Aeson's Value (good) and at the same time introducing its own Value type (with conflicting names of constructors and naturally not so numerous instances).

とのことで、Mustacheを使うぐらいであればstacheを使ったほうがよさそう。

ede

ede: Templating language with similar syntax and features to Liquid or Jinja2.

Mustache風味のHTMLベースのテンプレートエンジンだが、こちらのほうが表現力が高い。

qiita.com

こちらもData.AesonのValue型をObject型に変換し、それをテンプレートに代入してrenderしてくれる機構が備わっているので、テンプレートに与えたいデータをデータ型で用意しておけばよいのは変わらない。

--Unwrap a Value to an Object safely.
fromValue :: Value -> Maybe Object

記法はこんな感じ。

<div class="checkbox">
  {% for chara in characters %}
    <label class="checkbox-inline">
      <input name="chara" value="{{chara.value.name}}" type="checkbox">
      {{chara.value.name}}
    </label>
  {% endfor %}
</div>

if文では、比較演算子if-elif-elseと複数の条件で分岐させることもできるので、View側で凝ったことをすることも容易。

lucid

lucid: Clear to write, read and edit DSL for HTML

一方でこちらはhaskellの記法でHTMLを記述するDSL

ただSpockで使う場合、テンプレートを書き換えるごとにコンパイルしなおさなければならない。ede, stacheの場合は別ファイルで呼び出すので、コンパイルをし直す必要はないが、記法が誤っている場合は実行時エラーとなる。そう考えるとCompile時にValidateしてくれるlucidや、特にshakespeareではリンクのバリデーションも施してくれるので、これらのほうが型安全であると考えられる。

HTMLテンプレート?Lucidを使ってみた

Haskellの文法そのままなので、例えばリストの要素を列挙したい場合では

div_ [class_ "container"] do $
  h1_ [] "List"
  ul_ [class_ "list-group"] $
    mapM_ (li_ []) ["a", "b"]

などと書くことで、["a"], ["b"]の中身がliタグに囲まれて列挙される。

blaze-html

blaze-html: A blazingly fast HTML combinator library for Haskell

広く使われるHTMLライブラリではあるのですが、先述のLucidはこちらの改良版と言うか、blaze-htmlの問題点を解決したライブラリである。チュートリアルより、

import Text.Blaze.Html5 as H
import Text.Blaze.Html5.Attributes as A

userInfo :: Maybe User -> Html
userInfo u = H.div ! A.id "user-info" $ case u of
    Nothing ->
        a ! href "/login" $ "Please login."
    Just user -> do
        "Logged in as "
        toHtml $ getUserName user
        ". Your points: "
        toHtml $ getPoints user

記法はこんな感じで、これの何が問題かというと、下記のブログによればいくつかあるのだが、div, id, head, mapといった要素はbaseとconflictしているのでqualifiedして呼ばなければならないし、AttributeとElementの名前がコンフリクトするものがあるので、そうするとAやらHやらを頭に付けなければならない。結果としてコードが見にくくなってしまうのだ。それよりかは、Lucidのようにすべてsuffixに_をつけるほうが統一性があり、見栄えも良さそうだ。

Lucid: templating DSL for HTML

shakespeare

shakespeare: A toolkit for making compile-time interpolated templates

yesodで使われているテンプレートエンジン。hamletはこれにdeprecateされた。拡張子としては.hamletがHTML、.juliusがjs, .luciusCSSに対応している。 記法はこんな感じ。

<div .container>
  <h1> All Posts

  <div .jumbotron>
    <ul>
      $forall Entity id post <- allPosts
        <h4>
          <li>
            <a href=@{PostDetailsR id}>#{blogPostTitle post}

タグは閉じないで、インデントで親子関係を示す。HTMLに近い記法ができる一方でコンパイル時に変数が確実に埋め込まれる。たとえば@{}では型付きのURLが埋め込まれるため、それが有効なURLかどうかコンパイル時にバリデートされる。これがリンク切れを起こさない秘訣だ。変数の代入は#{}を使う。このとき、hamletではXSS attackを防ぐために適切にエスケープしてくれる。

制御構文は$から始まる行に記述し、if, elseif, forall, caseなど場合分けや、Maybe型への対応なども充実している。hamletはQuasiquotesでコード内部に埋め込むこともできるし、外部ファイルに記述することもできる。外部ファイルの場合はTemplateHaskellで参照され、コンパイル時に組み込まれる。

上記はyesodの場合で、これはscreencastから持ってきた例だが、yesodと無関係に使うこともできる。

www.yesodweb.com

sites.google.com

heist

heist: An Haskell template system supporting both HTML5 and XML.

snapというフレームワークで採用されているテンプレートエンジン。これは実際には試していないので、ウェブ上の例を見てみよう。

<bind tag="special">special-id</bind>
<div id="${special}">very special</div>

<bind tag="message">some text</bind>
<p><message/></p>

snapframework.com

heistの特徴となる主なタグはbindapplyで、bindはその名の通り変数を束縛し、 applyは<apply template="nav"/>のようにすると部分テンプレートを代入することができるらしい。Haskellからどう呼び出すかというと、公式サイトの例をみると

factSplice :: Splice Snap
factSplice = do
    input <- getParamNode
    let text = T.unpack $ X.nodeText input
        n = read text :: Int
    return [X.TextNode $ T.pack $ show $ product [1..n]]

というコードに対して、bindSplice "fact" factSplice templateStateとすると、<fact>タグにfactSpliceがbindされるので、<fact>5</fact>120になるとのことだ。

シェルコマンドでよく使う引数を書き留める

TL;DR

シェルコマンドの中で、しばしば自分が使っていて便利な引数、毎回検索するも忘れてしまって前回打ったコマンドをback-i-searchで探そうとするも見つからなくて困るものなどを記す。

diff

diff -y -B -b -i -W 200

  • -y : 結果を2行に表示
  • -W 200: 各行の幅を表示
  • -B : 空行を無視
  • -b : 空白スペースを無視
  • -i : 大文字・小文字の違いを無視

sort

sort -n -r -k 3,3

  • -n : 数値順に評価
  • -r : 逆順にする
  • -k : 並び替えるフィールドを指定する。上記の例だと3フィールド目。

tr

標準入力から標準出力へ、文字を変換・削除するなどして流し込むコマンド。

tr -d '\n'

  • -d : 引数の文字を削除する

less

less -N -S -s +F

  • -N : 行番号を付ける
  • -S : 長い行を折り返さず、1行で表示
  • -s : 空行を1行にまとめる
  • +F : 更新をリアルタイムに見る

qiita.com

qiita.com

time

/usr/bin/time -p -o FILE [cmd]

  • /usr/bin/time : シェル組み込みのtimeではなく、GNU timeを使う
  • -p : 互換性のある出力にする
  • -o FILE : 出力のファイル名を指定する

du

フォルダの容量を表示する

du -h --max-depth=2 [dir]

  • -h : サイズの接尾辞が付く
  • --max-depth : 再帰的に探索するディレクトリの深さの上限を指定する

tar

tar czvf file.tar.gz file tar xzvf file.tar.gz

  • c : 圧縮する
  • x : 解凍する
  • z : gzipにする
  • v : ファイル一覧を出力する
  • f : fの引数のアーカイブファイルを使う
  • j : bzip2にする

curl

curl -H 'Content-Type:application/json' -d "{\"session\": \"test\"}" -u user:pass [url]

  • -H : header
  • -d : パラメータをつける
  • -u : BasicAuthのユーザー情報

qiita.com

ps

プロセスを見る。

ps aux

  • a : 自分以外のプロセスも見る
  • u : ユーザー名が出る
  • x : 端末を持たないプロセスも見る

convert

ImageMagickのコマンド。identifyもそうだが、パッケージ名とコマンド名が一致しないので注意。

タイル状に切り抜く時のコマンド。

convert -crop 64x64 +repage in.png out.png

こうすると、64x64でタイル状に左上から切り取ってくれる。ただし、割り切れずに端数がでる場合は端数サイズのファイルが出力されてしまうし、出力ファイル名はout-1.png,out-2.pngのようになるので、sortするときに文字順で並べるとout-1.png,out-10.png,out-11.png...のように並んで困ることになる場合もある。

Imagemagick - code snippets

xargs

xargs -P 22 -n 2 -t -I {} [cmd]

  • -P : 指定した個数のプロセスを立ち上げて並列実行
  • -n : cmdに渡す引数の個数
  • -t : コマンドの実行内容を表示
  • -I : [cmd]の部分に引数で渡した{}を使うと、その部分にxargsで渡される引数が入る。
    • -i : -iも-Iと同様で、引数を省略すると自動で{}が採用されるが、deplecated.

orebibou.com

d.hatena.ne.jp

ただしxargsからパイプでファイルに書き込みたいと思って、> output.txtなどと書いても、プロセスを並列化していると複数のプロセスから同時に書き込まれるので、1つのプロセスから書き込まれる内容が別々の行にばらけてしまうことがある。そういった諸々の問題が面倒なので、下のコマンドを使うこともある。

parallel

xargsより高性能に並列処理を行ってくれるコマンド。このGNU parallelは、apt-getなどで別途導入する。

bicycle1885.hatenablog.com

GNU Parallel

シェルスクリプト(bash)

bash -x hoge.sh

  • -x : 実行するコマンドも同時に出力する
  • -c : 引数の文字列をシェルコマンドとして実行する

shellscript.sunone.me

モニタリングツール

htop

プロセスをモニタリングするtopコマンドの拡張で、見やすい。 htop 上でキーをたたくと、表示様式を変更することができる。F1キーをたたけばヘルプが見られる。

  • u : ユーザーを限定してプロセスを表示
  • H : ユーザープロセスのスレッドを隠すか表示させるかをトグルする
  • l : lsofで開いているファイルを読む
  • s : straceで読んだシステムコールを読む

iostat

ioの状況を表示するコマンド

bmon

ネットワークアダプタの状況を表示するコマンド

その他

80 Linux Monitoring Tools for SysAdmins - Server Density Blog

入出力制御

qiita.com

Haskellで参考にしたもの

Web Framework

Scotty / Spock

Rubyで言う所のSinatraのような軽量フレームワークhaskell版。ScottyよりSpockの方が、waiに準拠している上に機能も充実していて良いらしい。

Spockの紹介記事は以下。

qiita.com

ScottyをHerokuで動かしてみたサンプルアプリは

github.com

Yesod

Yesodのすごいところは、shakespeareのおかげでビューにまで型付きの制約があるところで、このおかげでリンク切れとか起きないらしい。(もしリンクが切れるようなことがあれば、それはコンパイルが通らない。)

またketerと言うデプロイマネージャーは、ローカルでコンパイルして、実行に必要なファイルをすべてまとめてサーバーにscpし、ダウンタイム0でデプロイしてくれるようにできるらしい。とても便利。

公式のScreecastsには、わずか40分程度!で簡単なブログを組み立てる動画が見られる。

HTMLエスケープやXSSを防ぐ機構についてはライブラリが面倒を見てくれるため、我々はそういったことに気をつかうことなく安心してWebアプリを書くことができる。

www.yesodweb.com

Other

Web/Comparison of Happstack, Snap and Yesod - HaskellWiki

テンプレートエンジンについては記事を書きました。

sharply.hatenablog.com

DB操作をする

YesodのDatabase.Persistentが、sqlite, mongoDB, postgresSQLなどのDBとのアダプターとなって、マイグレーションを自動でやってくれるとか便利。

www.yesodweb.com

qiita.com

rf0444.hatenablog.jp

http://blog.fujimuradaisuke.com/post/26887032662/haskell-de-json-web-api
blog.fujimuradaisuke.com

Libraries

d.hatena.ne.jp

http://haskell-distributed.github.io/

実際にこれを使って分散処理を書くのがやりやすいかというと、やはりもう少しダイナミックに型を扱える言語の方が書きやすいのかもしれない。

チュートリアル

1. Getting Started

  • IntervalMap

区間を保持する構造のhaskellにおける実装。

github.com

Haskell IntervalMap

  • Aeson

Template Haskellを用いて、dataの定義からderiveJSONするのがスマートそうな気がします。

qiita.com

Data.AesonでJSONを扱う | saito's memo

文字列型

bicycle1885.hatenablog.com

qiita.com

Blaze.ByteString.Builder

Other

例えばm [a] => [m a]にする関数が何かを知りたいと思った時、Hoogle で検索するのが便利そうです。初心者なのでコンパイルが通らない時に型の帳尻合わせをしたくなるので、そういった時に重宝しました。

Hoogle

github.com

React-Nativeで便利なUIライブラリ

tl;dr

React-NativeでiOSAndroidのネイティブアプリを作る際、自分が使った中で便利だったUIに関するライブラリをいくつか紹介する。

探し方や導入方法など

github.com

ここにReact-nativeに関連する様々な情報がまとまっており、その中にライブラリも多数収録されている。

依存がjavascriptだけのものはnpm install --saveすればよい。一方でコンパイル時に組み込まなければならないものは、GithubのReadmeなどに説明があると思うが、最近ではrnpmを使う、あるいはreact-native link <packagename>といったコマンドでプロジェクトに自動で組み込んでくれるようになっているので、手動で各ファイルに追記するよりミスを防げるので安全であると言えよう。

react-native-gifted-chat(旧 react-native-gifted-messenger)

github.com

いわゆるL○NE風のチャット画面を誰でも容易に作ることのできるライブラリ。ここにあるようにデザインはもうすでに整っているので、あとはチャットに表示させるメッセージをどこかから引っ張ってくるだけでよい。

React-native-gifted-messenger時代のものだが、簡単なチャットアプリの作り方は以下のQiitaの記事が参考になる。

qiita.com

react-native-vector-icons

github.com

FontAwesomeをはじめとしてFoundation, MaterialIcons, Octionsなど様々なベクターアイコンがバンドルされたライブラリ。

例えばアイコンを表示させたかったら、以下のようなタグを挿入すればよい。

<Icon name="rocket" size={30} color="#900" />

クオリティの高いアイコンが多数揃っているので、必要なアイコンはきっと見つかるはずだ。

react-native-action-button

github.com

Twitterの公式アプリの右下にある感じのボタンと、それを押すと複数のボタン選択肢が開いてくれるような、触っていて楽しいライブラリ。

これとreact-native-vector-iconsを組み合わせると、ボタンのアイコンを自作せずともきれいなベクターアイコンを使えるので便利。

react-native-sglistview

github.com

React NativeにはListViewという実装があり、何らかのデータソースから得られたデータをリスト形式で表示してくれるものであるが、その実装ではスクロールするにつれてメモリ使用量が線形で増え、利用可能なメモリを使い潰すと考えられる。その解決策として、SGListViewを用いることを提案している。スクロールするたびに内部のビューをflushしてくれるので、メモリを節約できるとのこと。

記法はListViewで実装していた部分をそのままSGListViewで置き換えればよく、導入も容易である。ただしアルファ版のため、skepticallyに使うようにとNoticeに書いてあるので注意。

react-native-grid-view

github.com

ListViewに対して、1つの列に複数のアイテムを置くことができるという点が優れている。ライブラリ自体の説明は乏しいが、Exampleを参考に改変しながら実装するとよいと思われる。サムネイルから選択させるような画面を作るときに向いているだろう。

react-native-router-flux

github.com

React NativeではNavigatorでのルーティングがデフォルトとなっており、Sceneと呼ばれる一つ一つの画面をスタックにpop/pushしていくことで、階層的なルーティングをサポートしている。

一方でそもそもAndroidiOSの間では画面遷移の制御や、TabBarやNavBarといったネイティブコンポーネントの違いがあり、その違いをうまく吸収してくれるようなルーターを使いたいと思う。

そこでここで取り上げるのが、fluxの考え方を取り入れたreact-native-router-fluxだ。公式のExampleをみてみると、

    return <Router>
      <Scene key="root">
        <Scene key="login" component={Login} title="Login"/>
        <Scene key="register" component={Register} title="Register"/>
        <Scene key="home" component={Home}/>
      </Scene>
    </Router>

Routerを上記のように定義すると、それぞれのコンポーネントではActions.Key(PARAM)によってpropsにPARAMを代入した状態で、Keyで指定したSceneへの遷移を呼び出せるほか、Actions.pop()と書くとその前のSceneに戻ることができる。

こうすることでまずルーティングを直感的に記述でき、それぞれの遷移も(ライブラリのActionsをそのコンポーネントに依存しない形で呼び出すので少し気持ち悪いように見えるかもしれないが)Keyを指定するだけで手軽に書けるので便利である。

またこのままだとナビゲーションバーが上に出るので、それがうっとうしい場合は<Scene key="root" hideNavBar>とすると、ナビゲーションバーを消すことができる。またそれぞれのSceneで、pop可能なとき、画面の左部中央をスワイプすると勝手にpopされてしまう。この挙動が邪魔な場合は、その挙動が発生してほしくないScene上で例えば<Scene key="home" component={Home} panHandlers={null}/>などとして、panHandlersをnullにすることで、スワイプによる画面遷移を防ぐことができる。

参考:

twins-tech.hatenablog.com

PRML 3.4-3.6 メモ

線形回帰モデルの流れ

3章では、 {y=w^{T}x}に対して、 yと決定変数tの間に加えられるガウスノイズの精度パラメータとしてβ、モデルパラメータwの事前確率分布を期待値0の、分散 {\alpha^{-1} I}の等方的ガウス分布としてαという2つのハイパーパラメータを導入している。ここで、ベイズ線形回帰ではwを周辺化していたが、今度はハイパーパラメータについても同様に事前分布を導入して周辺化したいが、完全に解析的に周辺化することは難しい。そのため、wに関して得られた周辺尤度を最大にするようにハイパーパラメータの値を定めるという近似について議論し、αとβの意味づけについて考える。

3.4 ベイズモデル比較

  • モデルパラメータの値を含んだ同時確率から、それを積分して周辺化することで、パラメータに依存しない確率が手に入れられる。

モデルの事後分布 {p(M_i|D) \propto p(M_i) p(D|M_i)}を計算するにあたって、事前分布 {p(M_i)}が全iに対して等しいとすると、 {p(D|M_i)}の項をモデルエビデンス=データから見たモデルの好みが重要である。それぞれのモデルの事後分布が分かると、全体の予測分布は混合分布で与えられる。

{ \displaystyle
p(t|x, D) = \sum p(t|x, Mi, D)p(M_i|D)
}

これは多峰性の分布となるが、近似するためにここから尤もらしいモデルを1つ選択しよう。

{ \displaystyle
p(D|M_i) = \int p(D|w,M_i)p(w|M_i)
}

この時、モデルエビデンスパラメータの事後確率を選ぶ際の正規化係数そのものになっている。

別の解釈を与える

モデルエビデンスに別の解釈を与える。パラメータの事前分布と事後分布が図3.12の時、事前分布の話は1で幅がΔWpriorだと、その高さは1/ΔWpriorとなるから、このことからp(D)を表現できる。この対数をとると、 {\ln{p(D|w_{MAP})}}が尤もらしいパラメータ wMAPによるフィッティング度で、 Δpos/Δpriを含む項が複雑さに対するペナルティであり、事前分布より事後分布が強くフィットするほど、ペナルティが増える。=これにより、過学習を防ぐことが可能となっていると考えられる。

M個のパラメータに対して、同じ比を持つならばM倍のペナルティがかかる。これが、パラメータ数が増えるにつれてペナルティが増えていることを示している。

カルバックライブラーダイバージェンス

期待ベイズ因子を求める際にKL-Divを用いる。式3.73の情報学的な解釈としては、真の分布 {p(D|M_1)}に対して偽の分布 {p(D|M_2)}を用いた時に必要な追加情報量の平均である。ここで、その2つの分布が一致するならば、必要となる情報量は0になるためにカルバックライブラーダイバージェンスも0になるということも直感的にわかる。

  • 変則事前分布は、先の解釈で言うところの正規化係数が定義できないので、適用できない。
  • 適切な極限をとって近似するほかない。

3.5 エビデンス近似

3.3より線形関数モデルでは、モデルパラメータwの事前確率分布を期待値0の、分散 {\alpha^{-1} I}の等方的ガウス分布を考えることとした。ここでβを決定変数に加えられるガウスノイズの精度パラメータとすると(このβは尤度関数に顕に現れる)、この時にwの事後分布は正規分布 { N (w|M_N,S_N)} で表現される。 これの対数尤度をとると、係数にαとβが現れる形になる。背後に仮定したガウス分布のαとβに対して、対数尤度を最大にするような値を求めるのが本節の目的である。

3.5 エビデンス近似 - 詳細

ハイパーパラメーターα、βに対しても事前分布を導入する。パラメータを周辺化して得られる周辺尤度関数を最大化することを、エビデンス近似と呼ぶ。

周辺尤度関数を最大にするようなα、βを選択すればよい。ではその周辺尤度関数を計算してみよう。重みwを周辺化すれば、αとβの式にできる。

演3.17,演3.18をとくと、演3.19は自明に導けて、エビデンス関数の(対数)表式を求められる。

これを最大化するα、βを求めたい。αは(3.92)から導かれる。βも同様に導かれて、(3.95)から導かれる。αとβはそれぞれに関する陰関数であるため、繰り返しによって計算する。

3.5.3 有効パラメータ数-αの意味づけ

基底関数の変換行列Φの固有値λ,αを用いて、

{ \displaystyle
\gamma = \sum(\frac{\lambda}{\lambda + \alpha})
}

で表される。固有値が正だから、 {\frac{\lambda}{\lambda + \alpha}}は0~1の値をとる。1となるものの和=個数がγであるから、1となるものはどういうものだろうか。

パラメータwiが最尤推定値に近いもの。このパラメータはデータに制約されるから、well-determinedパラメータと呼ばれる。このパラメータは、回帰への相関が高いパラメータに対応している。

一方で0に近い場合、 {y=w^{T} \phi(x)}に対して特徴量空間で値が動きにくくなることから、尤度関数の感度は悪くなっている。

思うに、実際には理想的にw_iの値がくっきり最尤推定値に近いものと0に近いもので別れることはなく、 {\frac{\lambda}{\lambda + \alpha}}が0.5になるようなものも得られるだろうが、その時γはパラメーターの寄与相当数(0.5のものが2つあれば、1相当になるとみなす)という形で捉えればよいのではなかろうか。

ハイパーパラメーターαはそもそも事前分布の分散として与えたものであるから、α=0であれば事前分布を導入してないことになるのでα=0の時は最尤推定に一致するし、α!=0の時はMAP推定の結果で最頻値が与えられると考えられる。

3.5.3 有効パラメータ数-βの意味づけ

βは、回帰関数周りでの目標関数の残差分散だが、ここではデータ数がN-γ、つまりwell-determinedでないパラメータの個数ととなっている。これは分散の最尤推定値と不偏推定量の関係で見たように、自由度が関係している。

ベイズ推定では、γ個のパラメータがデータによって決まっていると考えられるため、総パラメータ数Mに対してM-γ個のパラメータは事前分布に従っている。ここから、γ個のパラメータ=自由度が、最尤推定バイアス補正に用いられていると考えるのが相当である。

データ点に関する陰的な和でΦTΦが表現されることが期待できるようなN>>Mの状況では、すべてのパラメータがwell-determinedとなり、γ=Mとなる。この時αとβは容易に書き下せる。αが正則化項、βがデータ依存の誤差項に対応していることは、事前確率分布を期待値0の、分散 {\alpha^{-1} I}の等方的ガウス分布を考え、βを決定変数に加えられるガウスノイズの精度パラメータとしていたことから直感的にわかる。

固定された基底関数の限界

線形モデルの欠点は、以下である。

  • 基底関数を、観測する前に固定していること。

これに対する対処策は以下である。

  • データベクトルは、本質的な次元数は入力空間よりも小さい非線形多様体上に分布していること。
    • 後述
  • 局所的な基底関数を用いれば良い
  • 目標変数がデータ多様体中のほんの少数の方向にしか依存しないこと

パラメータの隠れ変数, 連続への拡張

周辺化することで、パラメータwはいわば隠れ変数となる。この隠れ変数が離散的であるとしたのがEMアルゴリズムによるE,M過程によるαとβの最適化であり、連続的であるとした時に主成分分析を行うのが12章の流れである。

データベクトルは、本質的な次元数は入力空間よりも小さい非線形多様体上に分布していることの直感的な議論としては、下章で示されている例としては手書き数字において、変化するのは垂直方向と水平方向、回転という3自由度しかないので、実効次元は3次元となる。この時、画素が文字位置の非線形関数であり、平行移動と回転のパラメタが潜在変数である。

KerasでLSTMを使ってテスト駆動開発してみた

tl;dr

Kerasという機械学習フレームワークのサンプルにあるLSTMを使い、テストコードを生成して、それをもとにテスト駆動開発してみた。

Kerasを試してみる

以前、紹介記事を書いたのでそちらを参照していただけると幸いです。

sharply.hatenablog.com

また、MLPを使った文の分類については以下の記事をご参照ください。

sharply.hatenablog.com

ソースコードを生成してみる

Kerasのexamplesに含まれているlstm_text_generation.pyを使って、文を生成してみることにチャレンジしてみたいと思います。ここではLSTMという構造が使われていますが、LSTMについては別の記事で少し紹介したので、そちらとその参考ページを参照いただけたら幸いです。

sharply.hatenablog.com

さて、元のコードではニーチェの文書をそのLSTMに学習させています。CPUマシンで計算させるとそれなりにまともな文章が出力されるまでとんでもなく時間がかかるのですが、例えば17イテレータでこんな文章が出力されました。

----- diversity: 1.0 ----- Generating with seed: "on as religion, art and ethics are so un" on as religion, art and ethics are so untorjuse of the nationar is the greater and repalsife man who law, it was supposed at the inplanatine, sole--of the whole poom befole this chacal we have sight of the are the divine patter. of the bits in the end, and the good and despriating the otherse, consequem and gratprouble autional enemat.--and a thing world agaits this difficeetis: the must is the greater of to the morality. one who are in

何のこっちゃといった感じですが、これは"on as religion, art and ethics are so un"をシード、つまりこの文字列から始まる文として、その続きをLSTMに予測させたものです。ご覧の通りそんなに精度よく文が生成されるわけではないのですが、今回はここにRuby on Rails5のソースコードのうち、テストコードだけを読ませてみたいと思います。

$ git clone https://github.com/rails/rails.git
$ cd rails
$ more `find . | grep _test.rb` > all_test.rb

これだけ抽出するだけでも5MBぐらいになり、5MB程度でも非力なCPUマシンでは1イテレータに10時間ほどかかってしまう見込みが出たので、splitコマンドを使って1MBずつに切り分けて、そのなかで最初のファイルを入力として指定してみます。さて、ちゃんとテストコードらしいコードを出力してくれるのでしょうか。結果はこのようなものがみてとれました。

----- diversity: 0.5 (9イテレータ目)
  def test_create_with_conditions_should_be_shared_with_conditions_on_conditions_on_many_and_limit_with_logs_target_with_propore_the_save
    comment = comment.projects.first
    assert_equal 1, post.all.merge!(:includes => :posts.id).post
    assert_equal 2, post.tags.belongs.size
  end

  def test_attribute_mattring_table_name_uniqueness_with_can_be_should_return_name
    column = companies(:f

----- diversity: 1.0 (9イテレータ目)
equal startup, sponsor.sponsorable
  end

  test 'string withdenv = write ci nect('restraction is instance_destroying peoples- ta/se body face
  def test_precision_sql_from_select_view(*arging using attributet") do
      connection.project_id = forkal
      assert_equal 'w, sole
      assert posts(@connection.insert_line_it(with_serialize, default_end) 
    assert_equal 0, parent.id

    \wifhoun data s iv� ssopeltriv"
      end

----- diversity: 0.5 (16イテレータ目)
    car = car.create!(bulbs: [first_name])
    assert_equal 0, account.where("name = ?', ['author'], :interest.first_name: 'sting'
    end
  end

  def test_bould_name_prefixed_association_loading_with_belongs_to_and_on_association
    assert_equal 1, account.where("credit_limit = 10").to_a
    assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
  end

  def test_no_collection_using_primary_key
        with_example_t

中には駄目そうなのもあり、diversityが高いほど駄目になっていますが、diversity=0.5で見た目では実行できそうなコードをピックアップすることができました。関数名がスネークケースで単語マシマシみたいになっていて英語的には破綻していますが、それでもちゃんとRubyで実行できそうなコードになっているのがわかります。

それではテスト駆動開発ということで、このテストに通りそうなコードを書いてみたいと思います。上のコードで、小文字と大文字を今回同一視しているのでその部分だけを修正してテストコードを作り、それをパスできるコードを書きます。

require 'test/unit'
require "active_record"

ActiveRecord::Base.establish_connection(
  adapter:   'sqlite3',
  database:  ':memory:'
)

class InitialSchema < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.string :name
      t.integer :project_id
    end
    create_table :projects do |t|
      t.string :name
      t.integer :comment_id
    end
    create_table :posts do |t|
      t.string :name
    end
  end
end

InitialSchema.migrate(:up)

class ActiveRecord::Relation
  def post
    1
  end
end

class Symbol
  def id
    self.__id__
  end
end

class Comment < ActiveRecord::Base
  has_many :projects
end

class Project < ActiveRecord::Base
  belongs_to :comment
end

class Post < ActiveRecord::Base
  def self.tags
    Tag.all
  end
end

class Tag < ActiveRecord::Base
  def self.belongs
    [0,1]
  end
end

class TestSample < Test::Unit::TestCase
  def setup
    @comment = Comment.new
  end

  def test_create_with_conditions_should_be_shared_with_conditions_on_conditions_on_many_and_limit_with_logs_target_with_propore_the_save
    comment = @comment.projects.first
    assert_equal 1, Post.all.merge!(:includes => :posts.id).post
    assert_equal 2, Post.tags.belongs.size
  end
end

では、これを実行してみます。

$ ruby test_spec.rb 
Loaded suite test_spec
Started
.

Finished in 0.009579296 seconds.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 tests, 2 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
104.39 tests/s, 208.78 assertions/s

テストに通ったので、うまくいったといえそうです。

近い将来、人工知能がまだプログラムは組めないけれど、仕様書からテストコードぐらいは生成できるようになった過渡期が訪れたとき、我々人類はこのようにして、機械によって生成されたテストコードをパスするためのプログラムを書くだけの存在になってしまうかもしれませんね。

プロセスあたりのメモリ使用量を知りたい

tl;dr

それぞれのプロセスがどのぐらいのメモリを使用しているかを知りたい。

コマンドで確認する

システム全体のメモリ使用量を確認する

  • cat /proc/meminfo

linuxではデバイスもプロセスもファイルとして扱えるので、catできる。

MemTotal:        8073996 kB
MemFree:          333772 kB
MemAvailable:    4262036 kB
Buffers:          600280 kB
Cached:          3386552 kB
SwapCached:            0 kB
Active:          4668596 kB
Inactive:        2382920 kB
Active(anon):    3061940 kB
Inactive(anon):   286724 kB
Active(file):    1606656 kB
Inactive(file):  2096196 kB
Unevictable:           0 kB
Mlocked:               0 kB

enakai00.hatenablog.com

  • free -tm
             total       used       free     shared    buffers     cached
Mem:          7884       6293       1591        260        470       3368
-/+ buffers/cache:       2454       5430
Swap:         2054          0       2054
Total:        9939       6293       3646

上のを高級な表示にした感じ。linuxではbuffersとcachedは、メモリが足りない時は開放するようになるので、buffers/cacheの値を見るとよい。

  • vmstat

仮想メモリの統計を得る。メモリに関してはfreeと同じような感じだが、cpuやswap, ioの状況も見ることができる。

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0      0 2911928 490096 2719356    0    0    48    44  356   16  8  2 87  3  0

またvmstat [sec]として秒数を引数に渡すと、その秒数ごとの出力を得ることができる。

プロセスごとのメモリ使用量を確認する。

用語を以下のように置くことにする。

  • VSS: 仮想メモリの使用量
  • RSS: 物理メモリの使用量(共有メモリの使用量を含める)
  • PSS: 物理メモリの使用量(共有メモリの使用量をプロセス間で等分する)
  • USS: 物理メモリの使用量(共有メモリの使用量を除く)

使用量というのは実際にはページ単位で使われているかであって、Linuxでは4KBを1ページとしているため最小単位は4KBとなる。HugePagesを使えば、より粒度の大きいページを使うことができて、そうすることでページ表へのアクセスを減らすことができ、場合によっては高速化できる。

ここで検知したいのはvirtual memoryで確保した領域ではなく、実際に使われている領域がどのぐらいかということにある。そのため最低でもRSS、欲を言えばそれより細かい粒度での情報が欲しい。

  • ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  35840  5612 ?        Ss   12:43   0:02 /usr/lib/systemd/systemd

これによってRSSは分かるが、結局それは正しい実使用量ではない。

Virtual Threads: Understanding memory usage on Linux

それは先述のように共有ライブラリの使用量が含まれており、reservedなメモリ使用量を全て表示しているからだ。C,C++のソフトウェアに関してはgdb, valgrindなどのデバッガを使えばよいとしている。

  • top/htop
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 3296 root      20   0 2330052 981008 162780 S 35.43 12.15 102:10.74 firefox                                 
 1959 root      20   0 3283584 575640  43724 S 6.954 7.130   6:17.59 dropbox    

topコマンドよりhtopコマンドの方が視覚的にわかりやすいプロセス毎のメモリ/cpu使用率などを表示してくれる。RESがタスクが使用しているスワップされていない物理メモリ、SHRにタスクが利用している共有メモリの総量が載っているので、USS=RES-SHRでとりあえず計算することは可能。

  • pmap -x {procid}
3296: firefox
START               SIZE     RSS     PSS   DIRTY    SWAP PERM MAPPING
00007f4d21e00000  11264K   9320K   9320K   9320K      0K rw-p [anon]
00007f4d23400000  10240K   8120K   8120K   8120K      0K rw-p [anon]
00007f4d23e50000      4K      0K      0K      0K      0K ---p [anon]
00007f4d23e51000   8192K     20K     20K     20K      0K rw-p [anon]
00007f4d24651000      4K      0K      0K      0K      0K ---p [anon]
00007f4d24652000   8192K     12K     12K     12K      0K rw-p [stack:22843]
00007f4d24e52000      4K      0K      0K      0K      0K ---p [anon]
00007f4d24e53000   8192K     12K     12K     12K      0K rw-p [stack:22673]
00007f4d25653000   7860K    428K    428K      0K      0K r--p /usr/share/fonts/truetype/ipamp.ttf
00007f4d25e00000   5120K      0K      0K      0K      0K rw-p [anon]
00007f4d26700000  22528K   9512K   9512K   9512K      0K rw-p [anon]
00007f4d27e00000   5120K   2252K   2252K   2252K      0K rw-p [anon]
00007f4d28c00000   1024K    816K    816K    816K      0K rw-p [anon]
00007f4d28df7000   6092K   2572K    335K      0K      0K r--p /usr/share/fonts/truetype/ipagp.ttf

pmapを使うことでRSSに加え、PSSもわかった。

  • smem
 PID User     Command                         Swap      USS      PSS      RSS 
 1093 hoge     /usr/lib32/skype/skype             0   172280   175262   182704 
 1050 hoge     /usr/bin/dropbox                   0   193672   195052   207152 

それぞれのプロセスのUSS, PSS, RSSを表示してくれる。python2製。

stackoverflow.com

上のStackOverflowによれば、smemでは下の計算式で求められている。

USS = sum of /proc/<pid>/smaps Private_clean + Private_dirty
PSS = sum of /proc/<pid>/smaps Pss
RSS = sum of /proc/<pid>/smaps Rss

smapsで得られる共有メモリの部分には、共有ライブラリだけでなく例えばforkした子プロセスでは、物理メモリのデータをCopy On Write(実際にメモリが書き換えられた時点で複製する)ようにしているので、そのときに共有されている物理メモリについても含まれている。そのためUSSだけがそのプロセスが実際に使うメモリ量とはならないし、RSSであっても共有ライブラリの分のメモリを含んでいる以上、同様となる。

dtrace

さてSolarisMac OS XFreeBSDにはdtraceというコマンドがある。これは動的にシステムをトレースするソフトウェアであるが、残念ながらlinuxには搭載されていない。

以下のツールは、dtraceの代替として紹介されている。それぞれを紹介したい。これを用いて、メモリを確保するようなシステムコールがいつどれだけ発行されたかをたどることができる。

stackoverflow.com

  • sysdig

Sysdig | Home

csysdigでncurseによるインタラクティブモニタリングをすることが可能となっている。そう、sysdigで表示される謎の出力をいちいちgrepしたり、引数をつけて呼び出す必要はないのだ。

最初に開いた画面でできることはたとえば以下がある。

f:id:sharply:20160622151129p:plain

F2を押すと、Viewを変更できる。例えば何らかのプロセスによってopenしているファイルのリスト(lsof)であったり、Systemcallのリストであったり。

Enterキーで選択されている行に限定して詳しく見ることができる。BackSpaceキーで戻れる。例えばSystemCallのリストで、Systemcallを選んでEnterを押すと、そのSystemcallを読んだプロセスが表示される。

F5を押すと、readwriteイベントを見ることができる。F6を押すと、本家sysdigの出力を垣間見ることができる。

なお、実行するにはsuperuser権限で実行しなければならない。

stap とかいうどこかで聞いたことの有るような細胞名のコマンドで呼び出すこのツールは、Systemtap Scriptと呼ばれるスクリプトファイルに従ってカーネルに対する探索を行うことが可能である。

sourceware.org

がまだ詳しくわかっていない...

  • strace

実際にシステムコールがどのタイミングで走っているかについてはstraceで検知することが可能である。プロセスにアタッチして、そのプロセスにおけるシステムコールを確認することもできる。

blog.livedoor.jp

straceを使うと、このような形で呼ばれたシステムコールと戻り値をみることができる。それをもとにmmapbrkがどれだけメモリ確保したかを累算することができるが、いかんせん、これが内部で呼んでいるシステムコールptraceは遅いので、実用性という面では厳しい。

brk(NULL)                               = 0x2156000
brk(0x2188000)                          = 0x2188000
mmap(NULL, 1000000000004096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
brk(0x38d7ea6df0000)                    = 0x2188000
mmap(NULL, 1000000000135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f73a4490000
munmap(0x7f73a4490000, 62324736)        = 0
munmap(0x7f73ac000000, 4784128)         = 0
mprotect(0x7f73a8000000, 135168, PROT_READ|PROT_WRITE) = 0
mmap(NULL, 1000000000004096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
exit_group(0)                           = ?

さいごに

今回の記事については、素人同然の立場でまとめた備忘録ですので、Linuxにおけるメモリの使用方法について不正確な記述が多いおそれがあります。ご注意下さい。

References

sisidovski.hatenablog.com

gntm-mdk.hatenadiary.com

Memory Flame Graphs

qiita.com

qiita.com

Linuxトラブルシューティング探偵団 番外編(3):SystemTapで真犯人を捕まえろ! (1/4) - @IT

d.hatena.ne.jp

th0x4c.github.io