2017/04/06

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

今日こんなツイートをした。

qt_luigi さんからどうしてかを聞かれたので説明したいと思います。

golang では宣言した位置で初めて自動変数としてメモリが確保され、ゼロクリアされます。

for i := 0; i < b.N; i++ {
    var foo Foo
    bar, err := doSomething()
    if err != nil {
        continue
    }
    foo.v = bar
    fmt.Fprintln(ioutil.Discard, foo)
}

なので例えばこの様なコードで doSomething() が err を返した場合、foo が無駄に初期化されてしまうのです。

本当にそうなのか、以下のベンチマークを見て貰えると分かります。

package var_test

import (
    "errors"
    "fmt"
    "io/ioutil"
    "testing"
)

type Foo struct {
    v *Bar
    b [1000]int64
}

type Bar struct {
}

func doSomething() (*Bar, error) {
    return nil, errors.New("bad some")
}

func BenchmarkVar1(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var foo Foo
        bar, err := doSomething()
        if err != nil {
            continue
        }
        foo.v = bar
        fmt.Fprintln(ioutil.Discard, foo)
    }
}

func BenchmarkVar2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        bar, err := doSomething()
        if err != nil {
            continue
        }
        var foo Foo
        foo.v = bar
        fmt.Fprintln(ioutil.Discard, foo)
    }
}

通るはずのない所に Println を書いたのはコンパイラが最適化して消し去ってしまわない様にです。(本当に消し去るかは未確認)

goos: windows
goarch: amd64
pkg: github.com/mattn/go-sandbox/var
BenchmarkVar1-4     10000000           226 ns/op           0 B/op          0 allocs/op
BenchmarkVar2-4     2000000000           1.26 ns/op        0 B/op          0 allocs/op
PASS
ok      github.com/mattn/go-sandbox/var 5.222s

ちょっと大げさに int64 変数が1000個保持されるような struct で確認しているので180倍近い差が出ていますが、少し大きめの構造体でも幾らかは差が出てしまいます。early return は golang の良い文化ではありますが、さらに変数の宣言位置も気を付けておくとよりパフォーマンスの良いアプリケーションになっていくでしょう。

Posted at by