blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2019.11.06 イベントレポート

ゲームサーバ開発エンジニアがみた Go Conference 2019 Autumn

by Tomohiro Katsukura

#go

Go Conference 2019 Autumn

19新卒の勝倉です。今回、スポンサー枠でGoConference2019に参加させていただきましたので、レポートをお届けいたします。 それぞれのセッションに関しては、Twitterの #gocon でスライドが共有されておりますので、詳細はそちらに譲ることとして、普段の開発に役立てられそうな点を振り返りたいと思います。

Go Conferenceに参加するにあたって

どれも非常に面白そうで全部聞きたかったのですが、当日は2会場展開だったため、なるべく業務に役立ちそうなセッションを重点的に聞こうと思いました。 (全体としては、コンテナ技術やデータ解析に絡んだトピックが多かったように思います。) ゲームサーバーを開発するチームに所属しているので、キーワードとしては、「パフォーマンス」「テスト」「自動生成」あたりをメインに、個人的に関心のある「つくってみました」系もいくつか選んで参加しました。

OSS Performance Tuning Tips

概要

スライドはこちら

「OSSで公開されているライブラリのパフォーマンスを向上させていくためには、どのようにアプローチしていけば良いか」というお話でした。 パフォーマンスチューニングの流れとしては、

  • ボトルネックになっている箇所をpprof(またはgithub.com/pkg/profile)を使って絞り込む
  • 怪しい箇所のベンチマークを書く
  • 試行錯誤してベンチマークを向上させる

発表の中では、試行錯誤を行なっていく際に、どういったポイントに目をつけていくとよさそうか、という紹介もありました。 たとえば、

  • 順序が保証される必要のないsliceをmapにかえることで、探索をO(n)からO(1)で終わるようにできる。
  • 何回も読み書きが必要なところは、都度bufferを確保するのではなく、sync.Poolでバッファープールを作ってあげると無駄なメモリ消費を減らせる。
  • 必要以上に強力なhashアルゴリズムを使っているところは、他の軽いアルゴリズムで代替できれば速くなる。

といったtipsが紹介されていました。

感想

はじめてGoに触ったとき、(=はじめて静的型付け言語に触ったとき)、ポインタの仕組みを理解しないで、でっかい構造体のスライスを毎回コピーしていたら、rubyの10倍くらい遅いコードができあがり、「『Goだから速い』のではなく、『Goを正しく使うから速い』」という教訓を得たときのことを思い出しました。

それ以降は、たとえば、

 // hogeは[]string
 s := []string{}
 for _, v := range hoge {
   s = append(s, v)
 }

このような書き方は避けるようになったのですが、このセッションで、他にも書き方のコツを仕入れることができて、良い勉強になりました。

またセッションの中で、メンテナンス性が下がるので非推奨ではありますが、 AVO というパッケージを使えば、いい感じにGoのコンパイラの特性を生かしてアセンブリをかけるという紹介もありました。

いつの日か、どうしようもないボトルネックになってしまっている箇所を、アセンブリで華麗に解決できたらと思います。

Continuous Automated Go Fuzz Testing

概要

(スライドは共有されていませんでした。)

gofuzz を使って、Fuzz testingを導入して、コードの品質をあげよう」というお話でした。

Fuzz testingは、テスト対象の関数の引数をビット反転などの演算によってsemi-randomに生成し、カバレッジが閾値を超えるまで回し続けて予期せぬエラーが発生しないかを確認するテストのことだそうです。

このテストを導入することによって、たとえば、

 // fooは、与えられたバイト列が「d.e.n.a」ならtrue、それ以外ならfalseを返す関数
 func foo(s []byte) bool {
   if len(s) >= 3 {
     if s[0] == 'd' && s[1] == 'e' && s[2] == 'n' && s[3] == 'a' {
       return true
     }
   }
   return false
 }
 
 var testcases = []*struct{
   in []byte
   want bool
 }{
   {
     []byte("dena"),
     true,
   },
   {
     []byte("baystars"),
     false,
   }
 }

このような関数とテストケースを書いてしまった時に、効果を発揮するとのことでした。

Goの標準パッケージでも、Fuzz testingを使って、100件以上のバグが見つかっているらしいです。 https://github.com/dvyukov/go-fuzz#trophies

注意点として、unit testやintegration testの代替となるわけではなく、実装方法に関する不備を見つけ出すためのテスト、ということでした。

感想

最近、チームで負荷試験の話になったときに「1回の負荷試験で数百万円かかる」と聞き、以前にもましてテスト設計をしっかりと考えたり、意識して変な値(絵文字など)を入れたりするようになりました。 Goの標準Testingパッケージでは、命令網羅率を測ってくれますが、上の例を見せられた時に、自分の書いたシナリオでカバレッジが高くても安心できないなと、ドキッとしました。 たとえば、validationのヘルパー関数などはいちいち関数ごとにテストを書くのはなかなか大変ですが、一番ちゃんと書かないといけないところでもあります。

そういった部分を、こういった手法で自動化できれば、いまと変わらないコストで、いまより自信をもってプロダクトを提供できると思いました。

総括

これ以外にも、CPUやVRAMの挙動をgoでエミュレートしてみたり、OCIのcontainer runtimeを実装してみたり、GCの仕組みを紹介してくれたりと、goをいろんな角度からいじっていてとてもよい刺激となりました。普段の業務でアプリケーションのコードを書いているだけではなかなか持ちづらい観点からGoを見ることができました。 持ち帰った知識はプロダクトにもしっかりと反映させて、その経験をコミュニティに還元できたらと思います。

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。