Go Conference 2014 Autumnレポート

Go Conference 2014 Autumn、その他のセッションのまとめ

前回の記事では、3つのセッションについてレポートを記しました。この記事では、残りのセッションについてまとめたいと思います。

Why my Go program is slow?@methane氏

このセッションでは、@methane氏からpprofというCPUプロファイラとGo言語におけるパフォーマンスチューニングについての話がされました。なお、このセッションの発表資料は、以下のURLから閲覧することができます。

写真1 @methane氏の発表の様子
写真1 @methane氏の発表の様子

サンプリングプロファイラ

pprofサンプリングプロファイラで、一定時間ごとにCPUがどこを処理しているかというサンプルを取ることでプロファイリングを行うそうです。そのため、多くのサンプルが取れた関数は時間がかかっているということがわかります。同氏は、サンプリングプロファイラは、モンテカルロ法に似ていると述べていました。サンプリングプロファイラに対して、決定論的なプロファイラというものが存在するらしいのですが、そちらは関数の始まると終わりにタイマーを仕掛けておき、それを集計することでプロファイリングを行うそうです。そのため、精度は高いそうですが、プロファイリングの処理自体が重たいため、計測対象のプログラムの実行速度に影響を与えてしまうそうです。一方、サンプリングプロファイラは、実行速度への影響は少なく、プロダクト版のプログラムに導入しても問題ないと同氏は述べていました。

用途に適したパッケージを使う

同氏は、デーモン型のプログラムや長時間動くプログラムでは、runtime/pprofを直接使用するのではなく、net/http/pprofを使用すると良いと主張していました。なお、net/http/pprofは以下のようにimportするだけで使えるそうです。

import (
    "net/http"
    _ "net/http/pprof"
)

func main() {
    go http.ListenAndServe(":5000", nil)
}

また同氏は、処理時間の短いコマンドラインツールなどは、davecheney/profileを使うと良いと述べていました。

Macでサンプリングプロファイラを使用するためには

pprofSIGPROFというシグナルを送る機能を利用しているそうですが、Macではマルチスレッドプログラムでその機能がうまく動かないため、pprofを利用できないそうです。この件の詳細は、Go言語チームのRuss Cox氏の記事を見ると良いそうです。Russ Cox氏の記事では、Macのカーネルにパッチを当ててpprofできるようにしているそうです。なお、カーネルにパッチを当てるのは大変危険なので、元に戻せる環境が無い方は試さないほうが良さそうです。

methane氏は、Mac上でサンプリングプロファイラを利用したい場合は、アクティビティモニタやInstrumentsを利用すると良いと述べていました。

go tool pprof

runtime/pprofおよびnet/http/pprofでプロファイリングした結果は、go tool pprofを使って解析することができるそうです。元々このツールは、Googleが開発しているC/C++向けのperftoolsを利用していたそうですが、Go1.4からはGo言語で書き直されているそうです。Go1.3までの使い方と多少異なるようですが、動作も安定してきているそうなのでGo1.4のgo tool pprofを使うことを同氏は推奨していました。このセッションでは、同氏が実際にpprofを使ってプロファイリングをするデモが行われました。pprofの使い方については、以下の記事が参考になります。

Go言語で書かれたプログラムが遅くなる理由

同氏は、Go言語で書かれたプログラムが遅くなる原因として、低レイヤーの話では以下の3つが多いと述べていました。

  • ガベージコレクション(GC)
  • メモリコピー
  • 関数呼び出し

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

GCが重い場合は、GODEBUG=gctrace=1というデバッグフラグを付けるとGCの情報が見られるそうです。また、pprofのヒープのプロファイラを使うと、どこでどれだけメモリ確保しているかが分かるため、メモリを多く確保しているところを見つけることができるそうです。そしてその部分で、Go1.3で入ったsync.Poolを使ってオブジェクトをプールしておき、メモリを確保する回数を減らすと良いそうです。

また、作成するスライスやマップの大きさが分かっている場合は、十分なキャパシティをmake関数で指定すると、要素を追加したときにメモリ確保が発生しないので速いとのことでした。さらに、GOGCという環境変数の値を大きくすることで、GCの負荷を減らし、早めにヒープを増やすと言うISUCON向けのチューニング方法も説明されていました。

なお、同氏の記事で、ISUCON4の予選のチューニング方法についてくわしく解説されています。

メモリコピー

string型で持っている値を実際に使う際に頻繁に[]byte型に変換
する場合、その度にメモリコピーが発生してしまうので注意したほうが良いと同氏は指摘していました。そのためそのような場合には、はじめから[]byte型で値を持つと良いと述べていました。また、net/textprotoパッケージのReader.ReadMIMEHeaderで行われているテクニックについての説明がされていました。

なお、Reader.ReadMIMEHeaderについては同氏の記事で詳しく解説されています。

関数呼び出し

同氏は以下の理由により、Go言語の関数呼び出しはC言語の関数呼び出しとくらべて遅いと主張していました。

  • 引数や戻り値がレジスタではなくスタック渡しであるため
  • 呼び出す側が使用しているすべてのレジスタを退避させる必要があるため
  • 関数呼び出し時にゴルーチンの切り替えやGCのストップザワールドのために起こるランタイムのフックがあるため

一方で、Go言語では十分に小さい、葉の関数はインライン展開されると同氏は述べていました。

Golang JP Community (@qt_luigi氏

このセッションでは、@qt_luigi氏から日本のGo言語のコミュニティについての紹介がされていました。なお、このセッションの発表資料は、以下のURLから閲覧することができます。

写真2 @qt_luigi氏の発表の様子
写真2 @qt_luigi氏の発表の様子

Golang JP Community(Google+)

まずはじめに、同氏が管理しているGoogle+のGolang JP Communityについての紹介が行われました。Google+上のGo言語のコミュニティでは5番目の規模(発表当時)であるそうです。本稿執筆時には641人の方が参加されていました。同氏は多くの方にこのコミュニティに参加して、投稿(または+1)してほしいと述べていました。

日本各地の勉強会

つぎに2014年に日本各地で行われたGo言語の勉強会について紹介されていました。2014年にはおよそ140の勉強会が日本各地で行われたそうです。その中でも、定期的に行われている勉強会について紹介が行われました。

関東

  • Go Conference:毎年春と秋に行われる日本最大のGo言語のカンファレンス
  • タネマキGAE:Google App Engine for GoとPythonについての勉強会
  • Tokyo Golang Developers:日本に住む外国人が中心になって行っているイベント
  • 質実Go研:主にGo言語の標準パッケージのコードリーディングや参加者でさまざまな課題の実装方法模索するイベント
  • Go弱の会:初心者向けのイベント
  • 大井町Go:大井町で行われている勉強会
  • Gunosy.go:株式会社Gunosyで行われる勉強会
  • ヒカルのGo:株式会社ディー・エヌ・エー(ヒカリエ)で行われる勉強会
  • dwanGo:株式会社ドワンゴで行われる勉強会

その他の地域

  • Shizuoka.go:静岡で行われる勉強会
  • Golang Cafe:GDGChugoku主催の勉強会でGo言語の勉強会では開催数は最多
  • Fukuoka.go:福岡で行われる勉強会

インターネット上の情報源

さいごにGo言語に関するインターネット上の情報源について紹介がされていました。

NSQ-Centric Architecture(Greg氏)

このセッションでは、Greg氏からNSQを使って開発したチャットシステムの構造について説明されました。なお、このセッションの発表資料は、以下のURLから閲覧することができます。

写真3 Greg氏の発表の様子
写真3 Greg氏の発表の様子

NSQ

NSQとは、bit.lyが開発しているGo言語で書かれたメッセージキューであるそうです。同氏は、以下の要件を実現するために、NSQを採用したと説明していました。

  • チャットサーバを作る
  • リアルタイム
  • スケールしやすい
  • 分散
  • 柔軟でシンプルに設定できる
  • Go言語で書きたい
  • アプリ内Webviewで使うためWebsocketを利用
  • ロードバランスさせたい

同氏はNSQの特徴として以下の点を挙げていました。

  • 単一障害点(SPOF)がない分散システム
  • 横にスケールしやすい
  • レイテンシーが低く、プッシュでメッセージを送る
  • ロードバラスもマルチキャストもできる
  • データフォーマットは何でも利用できる

また、NSQの決まり事(Guarantees)として以下の点を挙げていました。

  • デフォルトでは永続化されない
  • メッセージは必ず1回以上届く
  • メッセージが届く順序は決まっていない
  • コンシューマは少し時間がかかっても最終的には全部のトピックを見つける

なお、永続化を保証したい場合は、--mem-queue-size=0というオプションを付けると良いと同氏は述べていました。

NSQにはトピック、チャネル(Go言語のチャネルとは別⁠⁠、コンシューマという3つの概念があるそうです。メッセージはトピックに送られ、そしてトピックに関連付けられているチャネルにコピーされます。チャネルは最終的にメッセージを受け取るコンシューマを持ち、チャネルに複数のコンシューマが存在する場合は、メッセージはランダムに各コンシューマに届けられるそうです。また、チャネルにコンシューマが存在しない場合は、メッセージは破棄されず保管されるそうです。

WebviewでiMessage風なアプリを作る

プロトコル

同氏はNSQを使って、WebviewでiMessage風なアプリを実現したいと考えていたそうです。そして、このシステムで使用するプロトコルは以下のようなものを想定したそうです。

  • シンプルなJSON
  • サーバ同士とサーバ・クライアントのやりとりが同じ形式
  • ルーティングしやすい

最終的に、メッセージは以下のような形式になったそうです。

{
  "type":<message-type>,
  "to":<recipient-id>,
  "body:" {
    ...
  }
}

チャネル

同氏はいくつかの用途によって以下のチャネルを作成したそうです。

  • Websocketチャネル
  • アーカイブチャネル
  • プッシュチャネル
  • マイクロサービス

Websocketチャネルは以下のように実装したそうです。

  • Websocketサーバ1台につき1チャネル
  • すべてのメッセージがすべてのサーバに届くため、宛先を見て関係無いものは無視をする
  • ネットワーク障害があった場合はNSQがメッセージを保管する
  • 分散(少なくともマルチプレックス)チャットになる

また、アーカイブチャネルは以下のように実装したそうです。

  • 1つのチャネルに複数のコンシューマ
  • すべてのメッセージを受取り、DBに書き込む
  • ロードバランスを実現するためには、単にサーバを足せばよい
  • アーカイブプロセスとDBを同じサーバにすれば直接メッセージをDBに送ることができる

プッシュチャネルは以下のように実装したそうです。

  • 1つのチャネルに1つのコンシューマ
  • チャットメッセージと既読メッセージを受け取って、未読数を数える
  • 「新着メッセージが◯件あります。」というプッシュ通知を送る

現在はプッシュサーバが1台であるそうですが、じきに複数台にして分散させる予定だと同氏は述べていました。

NSQを利用して開発したシステムでは、チャネルを追加することでマイクロサービスを追加できるそうです。また、同氏はNSQの管理画面からトピックとチャネルの一時停止もでき、デプロイも容易であると述べていました。

クライアントサイド

同氏は、クライアントサイドを実装するうえで、以下のことを考慮しなければならなかったと述べていました。

  • メッセージが届く順番が決まっていない
  • メッセージは2回以上届く可能性がある

同氏はメッセージは、日付でソートすれば良いと述べていました。ただし、サーバ間で時計を合わせておく必要があると指摘していました。また、メッセージはUUIDを使って管理しておけば、重複しているかどうか分かると主張していました。なお、メッセージが更新されたかどうかは、バージョンや日付で見分けると良いと述べていました。

Hacking Go Compiler Internals(moriyoshi氏)

このセッションでは、moriyoshi氏からGo言語のコンパイラをハックする方法について解説がされました。なお、このセッションの発表資料は、以下のURLから閲覧することができます。

写真4 moriyoshi氏の発表の様子
写真4 moriyoshi氏の発表の様子

Go言語のコンパイラの構造

まずはじめにGo言語のコンパイラの簡単な説明がされました。同氏はGo言語のコンパイラは以下のような順序で処理を行っていると説明していました。

  1. 字句解析
  2. 構文解析
  3. エスケープ解析
  4. Typegen, GCproggen
  5. コード生成

字句解析におけるハック

字句解析器では、ソースコードをトークンに変換すると同氏は述べていました。src/go/cmd/gc/lexer.cを読むと、構文解析器がどのように実装されているかが分かると同氏は述べていました。構文解析器を変更することで、キーワードやオペレーターを好きな文字列に置き換えたりできるそうです。同氏は、絵文字を識別子として使えるように改造し、絵文字の寿司を名前に使った関数をコンパイルできるようにしていました。詳しくは同氏のブログ記事に書かれているそうです。

構文解析におけるハック

構文解析器は、字句解析器が生成したトークン列を抽象構文木に変換するそうです。また、単に抽象構文木に変換するだけではなく、コード生成がしやすい形に置き換えることもするそうです。なお、Go言語の構文解析器はyaccで書かれているとのことでした。

同氏は、以下の(A)というコードを(B)というコードに解釈されるように改造してみたそうです。

コード(A)
a := &struct{}{}
fmt.Println(a[1])
a[1] = "test2"
コード(B)
fmt.Println(a.__getindex(1))
a.__setindex(1, "test2")

上記のようにインデクサのオーバライドができるように、以下のような処理を追加したそうです。

  • 抽象構文木に新しいノードタイプOINDEXINTERを追加
  • typecheckという抽象構文木に型情報を埋め込むフェーズで、通常インデクサが有効な型(文字列、配列、スライス、マップ)以外の場合にOINDEXINTERを処理する部分を追加
  • walkというフェーズで、OINDEXINTERのノードを関数呼び出しのノードに変換し、再度その部分にtypecheckwalkを施すという処理を行う
  • 評価順序を修正する部分でOINDEXINTERを処理するコードを追加する

同氏はセッションの最後に、print関数がノードや型の情報を出力する書式を提供しているためハックする際に大変便利だったと述べていました。

How we use Go@ironzeb氏

このセッションでは、@ironzeb氏からGengoでのGo言語の使用事例についての紹介がされていました。なお残念ながら発表資料が公開されていないため、詳しい内容は紹介することができません。

写真5 @ironzeb氏の発表の様子
写真5 @ironzeb氏の発表の様子

Gengoでは、goshipというデプロイツールを作っているそうです。また、CodeigniterというPHPのフレームワークで書かれたAPIをGoで書き直したところ、500msだったところが10msになったそうです。この他にもパッケージの依存関係を解決するツールについての話題やテストについての話題が紹介されていました。

なお、GengoによるGo言語の採用事例はGengoのブログ記事も参考になります。

まとめ

Go Conference 2014 Autumnについて、4回に渡ってレポート記事を書きました。本レポートでは紹介しなかったLTについては、connpass上に発表資料がまとめられていますので、そちらを参考にしてください。

2014年は、多くの企業でGo言語を製品の開発に採用する事例が増えたようです。しかしながら、まだまだ開発チームの規模や採用している部分の規模が大きい物は少ないようです。

2015年はさらにGo言語を採用する場面が増えていき、規模も大きくなっていくのではないかと思います。

つぎのGo Conference 2015 Spring(本稿執筆時点では、開催はまだ未定)では、多人数のチームでの開発ノウハウなどが聞けるのではないかと期待しています。

おすすめ記事

記事・ニュース一覧