今日こんなツイートをした。
@mattn_jp よろしければベターな理由をm(_ _)m 名前空間を短くする作法なのはわかるのですがメモリやGCやコンパイラなど、どの辺に優しい感じですか?
— Ryuji IWATA (@qt_luigi) April 5, 2017
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 の良い文化ではありますが、さらに変数の宣言位置も気を付けておくとよりパフォーマンスの良いアプリケーションになっていくでしょう。