ぽよメモ

レガシーシステム考古学専攻

GoCon 2019 Autumnに参加してきた

はじめに

 今回はGo Conference 2019 Autumnへ参加するにあたり,Wantedly株式会社様よりスカラシップによる支援を頂きました.大変感謝しております.また,運営の方々,大変楽しくためになるカンファレンスでした.次回以降も是非とも参加したいと思っております.本当にありがとうございました.
 なお,本記事では自分が面白いと思って聞いたセッションについて掲載しています.全体の総括ではありませんのでご了承ください.

初めて参加してみて

 Go Conの存在については以前から知っており面白そうだと感じては居ましたが,開催地が遠かったのもあり,今回が初参加となりました.
 これまでのことは存じ上げませんが,今回の会場はどこも登壇者と聴衆の距離が近く,一体となって盛り上げていくような感覚がありました.僕がスカラシップ用のバッジを身につけていたのもあるとは思うのですが,話したことない方からも「学生の方ですか?」とかお声かけ頂いたりして,大変ありがたかったです.

f:id:pudding_info:20191028234912j:plain
スカラシップ参加者用の緑色の園児ニアの証,tofu_on_fire

 セッションはどれも聞き応えがありましたが,ただの全体のまとめをしがない学生が書いても意味は無さそうです.そこで,今回は僕自身が聞いてこれから活かしていけそうだなと思った話や,純粋に興味があり面白かった話などに絞っていくつかピックアップしたいと思います.一応万全は期していますが,内容等に間違いがあればこっそり指摘して頂けると修正します.

面白かったセッション

全て自分で聞きに行ったセッションです.資料のURLはTwitter等で知ったものについては載せています.

1. Go GC algorithm 101

gocon.jp


 まずは最初のセッションである,Wantedlyの20卒内定者taxioくんの発表です.説明が非常に分かりやすく,知見として有益でした.一時期Pythonのプログラムのメモリリークに悩んでいたこともあって,自分でGCについて多少調べたりしていたのですが,入門として非常に優秀なスライドだと思います*1

 特に,GCというものは文章で書かれてもイマイチ理解出来ない部分があり,視覚的に図で示されるとなんとなく理解しやすいように感じます.まだまだ奥深い話が無限にある世界だと思うので,彼がもっと詳しくブログを書いてくれることを期待しています.

発表後に上がった質問

 さて,本Sessionの内容はもうスライドとしてまとまっており,これ以上僕が言うことは何もないので,会場で出た質問とそれに対する発表者による回答をざっと上げていきます.

  • Q: 世代別GCが美味しいケースはどこ?Goでは通常スタックに確保されるはずで,ヒープに短命なオブジェクトが漏れてくるケースは珍しいのでは?
    A: Rick氏の発表*2をもう少し見る必要がある
  • Q: 短命なオブジェクトでヒープに来る物はどんなもの?
    A: エスケープ解析などによって決まり,詳しくないのでなんとも言えない.
  • Q: Bitmap markingでCoWとの相性が上げられているが,GoでフォークしてCoWが活かされるケースはあるか
    A: 調べたがはっきりした言及がない.他の言語などで有効なことは分かっており,一応括弧付きで記載した.
  • Q: Concurrent Mark & Sweepを選んだ理由は?
    A: 最初はCopy GCフラグメンテーションを抑える)などが検討されていたが,Go 1.4でのCからGoでのセルフホストへの移行との兼ね合いもあり,実装コストと効果の面から選ばれた.
  • Q: JVMなどではGCアルゴリズムを変更できるが,Goでもそういうオプションを付ける展望はあるか?
    A: Issueなどで見たことはない

この中で個人的に気になったのは一つ目と二つ目で,これらはお互いに関係しているのですが,突き詰めると

という問いに収束します.ヒープにも短命なオブジェクトのアロケーションが頻繁に起きるなら,世代別GCも効果がありそうに思えますし,そうでないならむしろなぜそのような検討がされているのか気になるところです.

Goでヒープにアロケーションされるのはどのようなとき?

Google先生に聞いてみたところ,このブログがヒットしました.

hnakamur.github.io

この記事自体は

segment.com

これを参考にしているようです.本文からいくつか引用すると,

ポインタはスタック割り当ての阻害要因なので可能なら避ける。
ポインタを使うとほとんどの場合ヒープ割り当てになってしまう。

とか

スライスはサイズが動的でコンパイル時には未決定なのでバックストア(スライス内のポインタが参照する先)の配列がヒープ割り当てになる。

とか

戻り値で文字列やスライスを返す関数には注意。
例えば func (t Time) Format(layout string) string の戻り値のstringの値(正確にはバックストアの配列)はヒープ割り当てになる。

とか書いてあり,Goの文脈に置いても,それなりにヒープへのアロケーションはありそうという感想を持ちました.

結局Goで世代別GCは効くのか?

「go generational gc」とかで検索していると下記のGoogle Formに行き当たりました.

ここでは世代別GCの可能性について,

  • 世代別GCの良いところはSTWの時間を短く出来るところ
  • ただしGoは並列でGCをするので,STWの時間は世代のサイズに依存しない
  • GoではGC時間を最小化する代わりに全体の実行を一次停止するのではなく,異なるコアで並列に実行することの方が良いと想定している
  • とはいえGCが並列で行う必要のある仕事を減らすことで大きな価値をもたらす可能性もある
  • 今この仮説について検証を進めている

ということが述べられているようです(これが2017年).

これを踏まえて

blog.golang.org

を読んでみると(これが2018年),

  • 大きなヒープサイズに対してはそう悪くない
  • ただ私たちのベンチマーク上ではあまり良い結果を示さなかった
  • 世代についての仮説がGoに当てはまらないわけではなく,若いオブジェクトはスタック上で生きて死んでいくというだけ
  • そのため,他の言語よりも世代別GCの効果が弱い

と述べられているようです.

 結局,Goのコントリビューターたちは色々試した上で,今の実装に行き着いているようです.ただし特定のケースでは別の方法のほうが良いというようなことはあり得そうなので,質問でも上がっていたように「GCアルゴリズムを可変にする」というような仕組みがあっても面白いのかなと思いました.

2. Go で"超高速"な経路探索エンジンをつくる


 次にDeNAの井本さんのセッションで,経路探索のアルゴリズムとGoにおける実装上の高速化の話です.井本さんの説明が非常に丁寧でわかりやすくアルゴリズムの話・実装の話のどちらもしっかりと納得しながら話を聞くことができたように感じました.特に実装の話においては,Goにおけるsliceとmapの実装方法を説明した後に,改善の方法と結果を示すという方針で話が組み立てられており,わかりやすかったです.

 LT・セッション等で見かけがちな「でもそれそちらのドメインではの話でしょ?」みたいな高速化ではなく,本当に基礎的な,ちょっとしたGoのコードからでも実践できるようなプラクティスが述べられていることが特に素晴らしかったです.sliceやmapを一切使わずに書くことなんて普通無いと思いますし,それらを使う上で簡単に実践できるプラクティスが背景知識と共にまとまっているのは,知見として非常に素晴らしいと思います.

 また,これは会場の様子を映像と音声でお伝えできないのが悔やまれるのですが,登壇者である井本さんが非常に落ち着いた声でゆっくりと説明されており,聞き取りやすかったというのも大きかったです.人は緊張すると早口になったり,重要なところで噛んでしまったりするものです*3.落ち着いて話されているセッションというのは聞いている側も安心できるので,こういうところも見習っていきたいと思いました.

発表後に上がった質問

 結局mapが遅いので,見方を変えてmapを諦め二次元配列でのアクセスにするという話でmapの高速化の話は終わっていました.それについて,メモリ局所性に基づいた高速なハッシュマップの実装があるはず(?)というような質問が上がっていたのですが,僕自身の知識の無さであまり理解出来なかったため,省略します*4

アルゴリズムの重要さ

 今回はGoConということで,みんなGoの実装についての注目度の方が高いと言うことは間違いないと思います.実際僕もあまりアルゴリズムへの造詣が深いわけではないので,ぱっと使えて分かりやすいsliceやmapの使い方に目が行ってしまいがちです.
 しかし,最も重要なのは「どういうアルゴリズムを選択するか」という部分のはずです.この発表でも前半でアルゴリズムの選択について触れられていますが,実務では単に最速のアルゴリズムを探すわけではなく,実用性を考えて,要件に合うものを使うことになるはずです*5.今回のDeNAさんでの要件だと「渋滞の反映」みたいな,事前計算時間に対する要求があったはずです.何らかの制約がある中でアルゴリズムを選択するのは,どういったところでどういった経験・知識を積むと良いのか知りたいです.競技プログラミングも近い物だとは思いますが,やはり少し実務とは違う部分もあると思っているので.

3. Write Container Runtime in Go

最後に@tomocyくんの発表です.資料が上げられていないので, https://gocon.jp/sessions/write_container_runtime_in_go/ の説明欄から引用します.

昨今では、DockerやKubernetesは多くのプロダクションで採用されています。そしてコンテナランタイムはこれらの技術を支える、正に中心的な技術です。このトークではrunCなどの各コンテナランタイムのGoのコードを紹介しながら、コンテナランタイムについて話します。またOCIが定めるOCI runtimeについてを、自作のコンテナランタイムをそれに準拠させながら話します。このトークの後で実際に自身の手でコンテナランタイムの自作、OCI runtimeへの準拠ができるように、GitHubにログ付きのレポジトリーを公開する予定です。

このセッションではContainer Runtimeを""Goで""実装するという部分に重きが置かれており,実際に彼が実装したコードを示しつつ,OCI Runtime Specに沿って実装を進めていくという形で発表が行われました.
 実は彼もWantedlyスカラシッププログラムで来ていたのですが,自己紹介でおもむろに「実は今日登壇します」と言い出して,全員の度肝を抜いたという裏事情があります.僕は開催前からこのセッションを見る予定をしていたので,まさか同じスカラシップの学生が登壇者だとは思わず,驚きと焦燥でかなり動揺してしまいました……

2019/11/1 0:00 追記
tomocyくんが資料を上げてくれました!

彼のアフターレポートはこちら

medium.com

Container Runtime

コンテナランタイムの実装として最も一般に普及しているのは,おそらくDockerが使用しているruncです.これについては以下の資料が分かりやすかったです.

上記資料の4ページ目にあるように,Dockerのような高レベルランタイムは,runcという低レベルなコンテナランタイムのインターフェースであると言うことが出来ると思います.そのため,Dockerの仕様に沿ったコンテナランタイムを実装すればruncと差し替えて使うことも出来るわけです*6

OCI Runtime Spec

github.com

 コンテナランタイム実装について,標準的な仕様を定義しているのがLinux Foundation傘下にあるOpen Container Initiative(OCI)です.runcの開発元でもあります.

 ランタイムの実装者はこれを見ながら実装するのですが,実はこの仕様では5つの操作方法と,それに対して何を返し,何をするべきかが記述されているだけで,内部の実装をどう書くかには一切言及がされていません.その5つの操作方法が以下です.

操作名 コマンドと引数 何をするべきか
State state <container-id> コンテナの現在の状態を返す
Create create <container-id> <path-to-bundle> コンテナを作る(実行はしない)
Start start <container-id> コンテナに与えられているコマンドを実行する
Kill kill <container-id> <signal> コンテナにシグナルを送って止める
Delete delete <cintainer-id> Createされたコンテナを削除する

上記の5つのコマンドを実行できるCLIを用意すれば,OCI Runtime Specに則ったランタイムを作れる,というわけです.

どう実装するか

 runcと同様,tomocyくんはLinuxの機能(Namespace,cgroup,chroot/pivot_root…etc)をふんだんに利用したランタイムの実装をしたそうで,実際に起動したプロセスが,どのようにホストから隔離されてコンテナとして動作していくのかを順に追って説明してくれたので,仮想化の機能についてそう詳しくない僕でも話について行くことが出来ました.
 また,Goでの実装方法についても

  • urfave/cliで簡単にコマンドを実装する
  • Dependency Injectionを使ってテスタビリティを上げ,CLIを薄く保つ
  • _linuxなどのプレフィクスをファイル名に付けることでBuild ConstraintによってOSごとにビルドされるファイルを変える

などの基本的な,しかし実際によく使われている手法を紹介していました.本当に短いコードで色々示してくれるので,あれっもしかして簡単なのでは?とちょっと思わせてくるあたりが凄かったです.

車輪の再発明が好き」

 この発表で特に印象に残っているのは彼が言っていたこのセリフです.普通だと「それはもうあるし再実装するのは無駄だよ」とか周囲から言われて止められがちなのですが,彼はあえてそういった車輪の再発明をGoでやるということに情熱を傾けているそうです.昼食時にもGoのhttp.Clientを自前で実装し直してみている話とかを聞かせてくれました.

 普段僕は「あるものは使う」というスタンスなので,必要があればサードパーティーの実装を躊躇なく使いますし,言語の持つ便利な機能やモジュールがあれば,同等の機能を自分で実装することはほとんどありません.しかし,逆を言えば

  • そういった機能がどのように実現されているのか
  • 実現するにあたってどういうところに苦労するのか

といった部分をほとんど知らないままでいることになります.例えば今回のコンテナランタイムの話にしても「Namespaceとかchrootとか使ってるらしい」みたいなことは知っていても,どういう順番でそれらが使われていくのか,どのように使うのかといった情報はほとんど知りませんでした.こういったことが他の様々なものに対しても同様に起きていると考えると,自分の無知に絶望してしまいそうです*7

 たまにはそういった,よく使うけどどう動いているのか知らないみたいなものについて,コードリーディングしたり実際に手を動かしてみるなどしてみると,より自分の世界が広がるかなと感じました*8

発表後に上がった質問

  • Q: Dockerを動かすために必要な物にはどういうものがある?
    A: DockerはOCIあんまり守ってないので,イメージのディレクトリレイアウトも違うし,Dockerのためにいいかんじに動かすコマンドが必要
  • Q: 強いランタイムはどういう実装をしている?
    A: 例えばgVisorはセキュリティ特化で,一部ハイパーバイザのようなことをしているらしい.速度はトレードオフかもしれないが,リソース分離をしっかりやっている.評価軸に沿ったランタイム選定をすれば良い.
  • Q: Linux特有の機能を使いまくっていて良い.Mac使ってるみたいだけど,Linuxのやつをどう開発した?
    A: 開発にはエディタの機能を,動作検証はコンテナの中でやった

特に最後のLinuxの機能を使いまくっているのにMac使いってどういうことなんだと思っていたのですが,どうもVSCodeでは環境変数GOOSによって補完される内容が変わるらしく,これを使って実装していたとのこと.確かにGOOS設定できるようにしてくれというIssueがあり,実際に対応されているようです.

github.com

コンテナの検証をコンテナの中でやるというのも,会場の笑いを誘っていました(笑)

tomocyくんの名言(迷言)要約集

  • コンテナランタイムって作ってもあんまり感動がない
  • もっと感動が欲しい人はDockerコマンドから叩けるようにしよう
  • Dockerから使えるようにすればさすがにお母さんも感動してくれるはず

彼はおそらくとんでもない化け物だと思います(褒めてる).

Goコミュニティにどう貢献していくべきか

 残念ながら僕はスーパープログラマではないようなので,Go言語そのものや,Goでの開発に対してとてつもないインパクトのある何かを残せるとは思っていません*9.では,そういう自分が何をやっていけるかというと,やはり

  • Goをこういうシチュエーションで使っている
  • こういうところが便利でこういうところがつらい
  • こんな便利なものを作ってみたよ,実はGoで書いてるよ

みたいな,Goを使うことについての対外的な情報公開かなと思っています.結局ユーザがたくさん居ても情報が出てこないとその活発さは可視化されないし,コミュニティが活発で無いと新規ユーザも増えないと思っています.Goの恩恵をたくさん受けているので,恩返しというと恩着せがましいですが,コミュニティにとってより良い影響を与えられるように頑張っていきます.

まとめ

 Go Conferenceは実務でバリバリGo書いている人でなくても,得られる知見がたくさんあります.開催地が遠くてお金が……という場合にも,何社もスカラシップスポンサーとして学生を支援してくれています.ちょっとでも気になっているなら,次回の参加を検討されてみてはいかがでしょうか.

 次か次の次くらいのGo Conferenceでの登壇目指して精進していきます.同じく登壇を目指す方々,対戦よろしくお願いします.


合わせて読みたい

www.wantedly.com


*1:PythonGCは主に参照カウント方式を,補助的に世代別GCを使用しているのでGoのものとはかなり異なります

*2:blog.golang.org

*3:これは僕の話です

*4:行列演算の例で有名な同じ行の要素に連続してアクセスした方が早いという話に近い物だと思うのですが,短い質問時間の中では僕の理解が追いつきませんでした.

*5:実務でそんなにアルゴリズムをゴリゴリやったことがないので想像で書いています.場合によるとは思います.

*6:これを利用したのが(Docker 18.09までで)コンテナからGPUを使用できるようにしたnvidia-dockerです

*7:例えばGoのmapやsliceの詳しい実装についても井本さんのセッションでこの日初めて知りました

*8:そういう意味で,Wantedlyのスポンサーセッションで紹介されていたGopher's Code Reading Partyはとても良さそうです

*9:これは諦めていると言うことと同義ではありません