MicroAd Developers Blog

マイクロアドのエンジニアブログです。インフラ、開発、分析について発信していきます。

Pythonばっかり書いてたエンジニアがGoに入門して感動したこと

こんにちは。マイクロアドで機械学習エンジニアをしている大庭です。

マイクロアドの機械学習チームでは機械学習関連のメイン言語として長らくPythonを利用していました。 しかし最近は機械学習の成果物をAPIとしてリリースすることも多くなり、並列処理に強い言語のGoを利用することも多くなってきました。

私自身、Goは学び始めの段階で慣れない静的型付け言語に苦戦しているのですが、使えば使うほどGoの洗練された文法や機能が好きになり、最近ではPythonと同じくらいに好きな言語になっています。

今回の記事では、「Python ばっかり書いていたエンジニアGo に入門してみて感動したこと」と題して私が最近感じているGoの素晴らしさを何点か紹介させていただきます。

この記事で Go を学ぼうか悩んでる方に少しでも Go の魅力を伝えられれば幸いです。

テストが標準コマンドラインツールで行える

Goはgo testというテスト機能を標準コマンドラインツールとして提供しているのでユーザーはテスト用ライブラリに何を使うかを悩む必要がありません。

一方で、多くのプログラミング言語では複数のテストツールが存在するので、どれを使うかの選定が必要になります。例えばPythonだとunittest, pytest, doctestなどの選択肢があり、どれを利用するかは一度は悩むテーマです。

そういったツール選定の煩わしさを無くしてくれる点もGoの素晴らしいところです。

Goのテストの実装例として単純な加算関数をテストするコードを載せておきます。

# add_test.go

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    actual := Add(1, 2)
    expect := 3
    if actual != expect {
        t.Errorf("actual: %v, expect: %v", actual, expect)
    }
}

上記のテストコードを go test add_test.go で実行した結果が以下になります。

=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS

Go Playground - The Go Programming Language

ベンチマークテストも完備

また、go testにはベンチマークテスト機能もあり、単体テストと同じような感覚でパフォーマンス測定のためのテストを作れます。

以下はAdd関数のベンチマークテスト版実装です。

func Add(a, b int) int {
    return a + b
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < 10; i++ {
        Add(1, 2)
    }
}

このベンチマークコードを go test -bench . -benchmem add_test.go で実行すると以下のような結果が得られます。

goos: linux
goarch: amd64
cpu: Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHz
BenchmarkAdd-32         1000000000               0.0000004 ns/op               0 B/op          0 allocs/op

-bench をつけることでベンチマークテストの実行実行速度を確認でき、さらに -benchmem をつけることで動的なメモリ確保についての情報を表示できます。

動的に確保したメモリはGCの対象になるなどパフォーマンス低下の原因になりがちなので、そういった情報までオプション1つで表示してくれるのが最高です。

エラーハンドリングがわかりやすい

Go では処理結果と同様にエラーも返り値で渡します。 そのため、Pythonのtry-except、raiseのようなエラーハンドリング専用の文法は必要なく、ifやreturnのような通常の文法でエラーを扱います。

例として、除算と0除算の場合のエラーハンドリングを行う関数は以下のようになります。

func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

関数定義に戻り値の他にエラーを返すことが明示的に書かれています。またエラーもreturnで渡しています。

一方、使う側は次のように、戻り値のerrがnilかどうかでエラーが起きたかどうかを判定します。

x, err := divide(1, 0)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println(x)
}
// division by zero

エラーが明示的かつシンプルな文法で扱われているので、可読性が高いコードになります!

並行並列処理が簡単

PythonではGILによってランタイムがシングルスレッド動作に固定されているため、並列処理を行うにはマルチプロセスや複数コンテナ構成を使用する必要があります。 一方Goは、goroutineと呼ばれる軽量スレッド実行機能をネイティブに備えているため、並列処理を簡単に利用できます。 最近流行りのRustやNimなどでもマルチスレッドの仕組みが後々に追加されている場合が多く、ネイティブにサポートされている言語はかなり珍しいです。

実際にgoroutineを利用するには、次のように関数実行の前にgoを付けるだけです。

func Hoge(a int) {
    fmt.Print(a)
}

func main() {
    for i := 0; i < 4; i++ {
        go Hoge(i) // 関数Hogeをgoroutineで起動
    }
    time.Sleep(time.Second) // 全てのgoroutineが完了するまで待機
}
// 1230(出力は実行時により異なります)

Go Playground - The Go Programming Language

以下の記事がGoの並行処理を理解する上で以下の記事が大変参考になりましたので詳しく知りたい方はご一読をおすすめします。 zenn.dev

機械学習エンジニア絶賛採用中

マイクロアドでは、問題設定からサーベイ、開発・運用まで裁量を持ってチャレンジしたいという仲間を募集しています!また、機械学習エンジニアだけでなく、サーバサイド、フロント、インフラエンジニアなど幅広く募集しています! 気になった方は以下からご応募ください!

recruit.microad.co.jp