Escape Analysis and Stack Allocation on Go lang 1.2 レシーバ編

最近、Go言語にいろんな意味ではまっているので、調べたメモpart2。
前回のやつは色々と中途半端で間違っていたので、書き直し+go 1.2開発版 (go version devel +f1545db4a9c4)で実験。
最終版ではないので、今後もぼちぼち修正+更新するかもしれません。

Introduction

Go言語の特徴として、スタック領域ヒープ領域を区別しないという特徴がある。
そのため、C言語初心者がやるような、こんなプログラムを書いてもよい。

package main
import "fmt"

func main() {
	var a1 *int = call1()
	fmt.Printf("value = %d", *a1)
}

func call1() *int {
	var b1 int = 10
	return &b1
}

とはいえこれは、プログラマが明示的には区別しないというだけで、実際にはコンパイラがコードを解析し割り当て先を決めている。 ref: http://golang.jp/go_faq#stack_or_heap
一般に、変数はスタックに割り当てたほうが高速に動作する。確保も解放も速く、キャッシュに乗りやすい(たぶん)し、GCによって回収する必要もないからだ。
では、どのような変数がスタックに割り当てられるのか?
素直に考えると、↑のコードの`b1`はヒープに割り当てられるように見える。ポインタa1によってmain関数からb1を参照するためには、b1がcall1関数のスタック内にあるとダメだからだ。
が、実際はもうちょっと複雑である。Goのコンパイラはわりとかしこいので、インライン化エスケープ解析をしてくれる。
↑のコードのコンパイル中の動作を-mオプションで出力すると、こうなる。

go build -o /dev/null -gcflags -m main.go 2>&1| tee main
# command-line-arguments
./main.go:9: can inline call1
./main.go:5: inlining call to call1
./main.go:5: call1 &b1 does not escape
./main.go:6: main ... argument does not escape
./main.go:10: moved to heap: b1
./main.go:11: &b1 escapes to heap

main関数内ではcall1が展開され(インライン化)、b1がmain関数のスタックから外に出ないことが確認できた(エスケープ解析)結果、b1はスタックに割り当てられている。
一方、call1関数そのものは、b1がcall1関数のスタックから外に出ていることが解析されたため、b1をヒープに割り当てている。

という長い前振りは置いておいて、どういう条件ならスタックに割り当てられるのか?をいくつか実験してみたのでその結果。 コードはこのへん https://github.com/t-yuki/goalloctest

Analysis1

Source: analysis1.gopkg1/pkg1.go -> Result

Analysis1では、int型の引数と返り値をもつ、レシーバーつきの別パッケージの関数Max1/Max2/Max3/Max4を、呼び出し方を変えながらmainから呼び出している。
pkg1のMax1/Max2/Max3/Max4/Max5は以下の通り。

Method Description
Max1 公開パッケージ関数Maxを呼ぶ
Max2 非公開パッケージ関数maxを呼ぶ
Max3 非公開レシーバつき関数max3を呼ぶ
Max4 数値を比較し、大きいほうを返す
Max5 公開レシーバつき関数Max4を呼ぶ

Maxは非公開パッケージ関数maxを呼ぶ関数で、max/max3はMax4と同様に、数値を比較し、大きいほうを返すだけの関数である。

Resultから抽出した、HEAP/STACKの割り当てパターンを以下に示す。

Declaration Max1 Max2 Max3 Max4 Max5
var c pkg1.MaxerImpl HEAP HEAP STACK STACK STACK
d := new(pkg1.MaxerImpl) HEAP HEAP STACK STACK STACK
e := pkg1.NewMaxerImpl() HEAP HEAP STACK STACK STACK
f := pkg1.NewMaxer() HEAP HEAP HEAP HEAP HEAP

interfaceとして扱ったfは常にHEAPが使用された。
c-eについては、パッケージ関数を呼び出しているMax1/Max2がHEAPに割り当てられた一方で、Max3/Max4/Max5はSTACK割り当てになった。

mainについての出力をみると、pkg1のMax4, NewMaxerImpl, NewMaxerをインライン化していることがわかる。
他の関数を呼び出しているMax1/Max2/Max3/Max5はインライン化されていない。
どうやら、他の関数を呼び出している場合はインライン化しないようだ。(インライン化の深さやサイズに制限がある?)
pkg1についての出力をみると、すでにpkg1のコンパイル段階でインライン化しているようにみえるので、実質的にはどれも大差ないはずなので、いささか奇妙な振る舞いに感じる。
このあたりはコンパイラの出来次第だろうか。

pkg1についての出力をみると、Max4, NewMaxerImpl, NewMaxer関数は`can inline XXX`されている、ここでインライン化できると判定された関数のみが、他のパッケージから使用したときにインライン化されるのだろう。
加えて、Max3, Max5は`XXX does not escape`と、レシーバがエスケープするかしないかも含まれている。

これらのことから、Max1,2がHEAP割り当てでMax3,4,5がSTACK割り当てとなったのは、

1. pkg1のコンパイル時にレシーバがエスケープしないと判定できた(Max3,5)
2. インライン化できると判定されたので、mainのコンテキストで処理された(Max4)
3. パッケージ関数を呼び出しているので、エスケープすると判定された

ためと推測される。
3.の振る舞いはかなり奇妙に感じる。引数がわたっているならともかく、なぜまったく関与していないレシーバがエスケープすると判定されているのか?
いや、pkg1の出力を見る限りでは、Max1,2,4はレシーバがエスケープすると判定されたわけではないので、エスケープしないと判定されていないのは、ただのバグかパターン漏れなのかもしれない。
また、Go言語のメモリ割り当ての特性を考えると、もっとアグレッシブにインライン化できると判定してくれてもいい気はする。
このあたりも、コンパイラの出来次第だろうか。
結果、要約としては、

1. newで確保しようが、変数宣言で確保しようが関係ない
2. エスケープしないと判定されていない関数を呼び出すと、インライン化できない限りHEAPに割り当てられる
3. エスケープしないと判定された関数を呼び出すと、インライン化できなくてもSTACKに割り当てられる
4. interfaceとして扱うとからなずHEAPに割り当てられる

となる。

Golang のコードカバレッジ測定とJenkins + Cobertura Plugin

最近、Goで書いたプログラム+テストコードのカバレッジ測定ができないかなーということで、二つほど取り組んでました。
gocov-xml と、gocover-cobertura です。
ほぼ同じコトをするツールですが、入力元がgocov-xmlかgo tool coverかという違いがあります。
両方とも、結果をCovertura XML形式で吐き出すので、 Jenkins Cobertura Pluginに食わせることができます。ソースコードアノテーション付き!

少し説明すると、まず前提として、Go言語でカバレッジ測定をする方法は二つあります。
一つは https://github.com/axw/gocov を使う方法で、`go test XXX`のかわりに` gocov test XXX`と書くことで、テスト実行+カバレッジ測定ができます。
もう一つは go tool cover http://golang.org/doc/go1.2#cover を使う方法で、これはGo 1.2から導入予定のツールです。
基本的には gocov は go tool coverに置き換わるものと考えられてますが、周辺ツールがなく不便だったので、 gocov-xml から派生して gocover-cobertura を作成しました。

Jenkins上の画面はこんな感じになります。

Escape Analysis and Stack Allocation on Go lang 1.1

最近、Go言語にいろんな意味ではまっているので、調べたメモ。

結果が色々まちがってるので、Escape Analysis and Stack Allocation on Go lang 1.2 レシーバ編 - fousの日記をみてください。

Introduction

Go言語の特徴として、スタック領域ヒープ領域を区別しないという特徴がある。
そのため、C言語初心者がやるような、こんなプログラムを書いてもよい。

package main
import "fmt"

func main() {
	var a1 *int = call1()
	fmt.Printf("value = %d", *a1)
}

func call1() *int {
	var b1 int = 10
	return &b1
}

とはいえこれは、プログラマが明示的には区別しないというだけで、実際にはコンパイラがコードを解析し割り当て先を決めている。 ref: http://golang.jp/go_faq#stack_or_heap
一般に、変数はスタックに割り当てたほうが高速に動作する。確保も解放も速く、キャッシュに乗りやすい(たぶん)し、GCによって回収する必要もないからだ。
では、どのような変数がスタックに割り当てられるのか?
素直に考えると、↑のコードの`b1`はヒープに割り当てられるように見える。ポインタa1によってmain関数からb1を参照するためには、b1がcall1関数のスタック内にあるとダメだからだ。
が、実際はもうちょっと複雑である。Goのコンパイラはわりとかしこいので、インライン化エスケープ解析をしてくれる。
↑のコードのコンパイル中の動作を-mオプションで出力すると、こうなる。

go build -o /dev/null -gcflags -m main.go 2>&1| tee main
# command-line-arguments
./main.go:9: can inline call1
./main.go:5: inlining call to call1
./main.go:5: call1 &b1 does not escape
./main.go:6: main ... argument does not escape
./main.go:10: moved to heap: b1
./main.go:11: &b1 escapes to heap

main関数内ではcall1が展開され(インライン化)、b1がmain関数のスタックから外に出ないことが確認できた(エスケープ解析)結果、b1はスタックに割り当てられている。
一方、call1関数そのものは、b1がcall1関数のスタックから外に出ていることが解析されたため、b1をヒープに割り当てている。

という長い前振りは置いておいて、どういう条件ならスタックに割り当てられるのか?をいくつか実験してみたのでその結果。 コードはこのへん https://github.com/t-yuki/goalloctest

Analysis1

Source: analysis1.gopkg1/pkg1.go -> Result

Analysis1では、int型の引数と返り値をもつ、別パッケージの関数Max1/Max2/Max3をmainから呼び出している。それぞれの詳細はSourceを参照。

Resultをみてわかる通り、別パッケージであってもインライン化している。C++で、ヘッダファイルで宣言したclass中でメソッドを定義したときの挙動に近い。
ただしインライン化しているのはMax1関数だけで、他の関数を呼び出しているMax2/Max3はインライン化されていない。
解析の深さの制限があるのかもしれないが、ひとまずここまで。

Analysis2

Source: analysis2.gopkg2/pkg2.go -> Result

Analysis2では、int型の引数と返り値をもつ、レシーバーつきの別パッケージの関数Max1/Max2/Max3/Max4/Max5を、呼び出し方を変えながらmainから呼び出している。それぞれの詳細はSourceを参照。

Resultの要約としては、

1. newで確保しようが、変数宣言で確保しようが関係ない
2. Exportedだろうがなかろうが関係ない
3. receiverがあろうがなかろうが関係ない
4. interfaceとして扱うとHEAP

Declaration Max1 Max2 Max3 Max4 Max5 Max6 Max7
var c1 pkg2.MaxerImpl STACK STACK STACK STACK STACK STACK STACK
d1 := new(pkg2.MaxerImpl) STACK STACK STACK STACK STACK STACK STACK
e1 := pkg2.NewMaxerImpl() STACK STACK STACK STACK STACK STACK STACK
f1 := pkg2.NewMaxer() HEAP HEAP HEAP HEAP HEAP HEAP HEAP

とりあえずAlminiumを入れてみる

Redmine/Alminiumは、タスク管理のためのツール。Trac的な。
最近某所で運用&プラグインいじりとかしてるので、ついでにHomeで趣味作業やるためにVPS上に入れたときのメモ。
細かい解説は他のサイトに譲るとして、 https://github.com/alminium/alminium/blob/master/README.mkd を参考に。
Apache + mod_passenger構成で、/opt/alminium以下にインストールされます。

# 適当な作業ディレクトリで
git clone https://github.com/alminium/alminium.git
cd alminium
bash ./smelt # 色々なmodインストール+MySQL DB作成+Apache再起動とか走る
vi /etc/apache2/sites-enabled/redmine # passengerのpathが違ってたので修正
mysql -p < config/createdb.sql # MySQLにパスワードがかかっててうまく入らなかったので、ユーザー+DB作成
cd /opt/alminium
rake db:migrate RAILS_ENV="production" # DB有効化
rake redmine:plugins:migrate RAILS_ENV=production # プラグイン有効化
service apache2 restart

ランニング用のGPSのめも

久しぶりに。この存在を忘れてた。最近は就職したりとかRedmineさわったりとかGithubにちょっと手を出してみたりとかしてます。

というわけで、ランニング用のGPS Loggerがほしいなーと思って調べたメモ。

要件としては

  1. MUST 単体のGPSで動くこと。ランニング中にスマートフォン持ち歩くのは重い
  2. MUST iPadでデータを閲覧できること。サイクリングでiPadを持っているときに経路をみたい
  3. MUST 小型軽量であること(100g以下)
  4. WANT iPadGPS代わりに使える(地図アプリとして使える/MFi取得)
  5. WANT Androidで以下略
  6. WANT Bluetooth 4.0 (BLE)でデータをリアルタイムORバッチで引き取れること
  7. WANT MicroUSBで充電できること

・・・ぜんぶを満たすものはありませんでした。
調べたもの

単体で動作 iPad (MFi) MicroUSB USB Storage Mode Bluetooth SPP BLE その他
QSTARZ BT-Q1300S × × × ×
Transystem 747pro × × × ×
Transystem PhotoMate887 × × × ×
Wintec WBT-202 × × ×
HOLUX M-241 × × × ×
i-gotU GT-900Pro × × × 時計型, BLEは心拍/ケイデンスセンサ用, QZSS○
NIKE+ SPORTWATCH GPS × × × × 時計型, バンドがUSB A端子になってる
GARMIN ForeAthlete10J × × × × × 時計型, 専用ケーブルでUSB A端子, QZSS○
Bad Elf GPS Pro × 国内未発売
Dual XGPS150 × × ×
GNS 1000 × × ×

どれも一長一短。ていうか、なんでこんなに各社同じ仕様で作るのか?ここ数年はMiniUSBじゃなくてMicroUSBのほうがいいだろうに。
MiniUSBはコネクタの耐久性が非常に弱いのが問題。かつスマートフォン用のケーブルを使いまわせないので面倒。専用ケーブルタイプも同様で、外出先で使いたいときやケーブルをなくしたときが面倒。

GPSレシーバ・ロガーはBluetoothやUSBのクラスドライバがないようで、対応はばらばら。一応、通信プロトコルはNMEAである程度統一されているようだが、SPP上でやってたらOSが標準対応しないので意味がない。Over BLE(GATT)に至ってはなにもなし。
唯一、Bad Elf GPS Proが単体で動いてiPadでも使用できるが、BLEかは不明(たぶん違う)で国内未発売。
ANTとかで通信できるやつをアダプタ経由でつなぐしかないのかなー
値段を考えると、ふつうのBluetooth GPSロガーを買ってAndroid経由で我慢するか。

近況など

特に音沙汰もなく元気です。
というわけで近況報告。

といっても最近はひたすら現在の研究、神経科学関連の論文を読むばかりです。

Mendeleyという論文管理ソフトを使い始めたので、7月はKindleとMendeleyをごにょごにょするツールなどを書いていました。
Kindle 2.5になってCollectionsという機能が増えたので、ファイルの整理がしやすくなりました。
と思ったら新しいのがでてしました。
ついでに誘惑に負けてXperiaを買いました。
Android SDKはまだ手をつけたばかりですが、そのうち何かアップしようかと思います。

Kindle DX first impression

Xperiaを買うつもりが、なぜかKindle DXを買ってしまいました。

バイス的にはまあまあ満足で、英語の論文などはほぼ問題なく見れます。
サイズも大きすぎずちょうどいい感じで、空いてる電車内でなら使えそう。

不満としてはファイル管理が完全におまけなので、大量のpdfを整理するのがすごく大変なこと。
せめてタグ付けぐらいはほしいところです。

あと、日本の書籍が購入できないのはやはり痛い。
Kindle Storeから簡単に洋書が購入できるのをみると、日本の電子書籍は周回遅れと感じてしまいますね。
まあ、これで雑誌やら小説やらが購入できたら歯止めが効かなくなりそうですけども。

日本語のpdfはフォントが埋め込まれていれば普通に表示できますが、それ以外については日本語フォントを入れても表示できないものもあるようです。