LoginSignup
6
2

More than 5 years have passed since last update.

値と参照のappendはどちらが良いのか

Last updated at Posted at 2017-12-15

はじめに

こんにちは、LIFULL HOME'S事業本部 技術開発部の宮崎です。
これはLIFULL その2 Advent Calendarの15日目の投稿になります。
ついでにLIFULL Advent Calendarもよければご覧下さい。

8日目にも記事を書きました。よければご覧ください。
強いエンジニアにHelloWorldさせてみた

Benchmark

golangは色々なツールが公式で提供されています。
その中で今回はbenchmarkツールを用いて疑問に思っていた内容のベンチマークをとってみようと思います。

内容

今年のISUCONの問題を解いている時に、sliceの先頭に要素を追加する必要がありました。
その時に、sliceの型が参照である場合と、値である時どちらのほうが早いのか疑問に思いました。

以下のようなものです。

type User struct {
    Id int
    a  int
    b  int
    c  int
    d  string
}

// 参照のslice
sliceRef := make([]*User)

// 値のslice
sliceVal := make([]User)

それぞれの要素のサイズがいくつになるか計算してみます。

package main

import (
    "fmt"
    "unsafe"
)

type User struct {
    Id int
    a  int
    b  int
    c  int
    d  string
}

func main() {
    u0 := &User{}
    u1 := User{}

    fmt.Println(unsafe.Sizeof(u0))
    fmt.Println(unsafe.Sizeof(u1))
}
実行結果
8
48

&User{}は参照で、User{}は値なので当然ですね。

参照のほうがサイズが小さいため、早そうです。

実行結果

コード
package main

import "testing"

type User struct {
    Id int
    a  int
    b  int
    c  int
    d  string
}

func BenchmarkRef(b *testing.B) {
    slice := make([]*User, 0)
    for i := 0; i < b.N; i++ {
        slice = append(slice, &User{Id: i})
    }
}

func BenchmarkVal(b *testing.B) {
    slice := make([]User, 0)
    for i := 0; i < b.N; i++ {
        slice = append(slice, User{Id: i})
    }
}
ベンチ結果
$ go test -bench . -benchmem
goos: darwin
goarch: amd64
BenchmarkRef-4       5000000           227 ns/op          91 B/op          1 allocs/op
BenchmarkVal-4       2000000           524 ns/op         246 B/op          0 allocs/op
PASS
ok      _/Users/taisuke/Desktop/bench   3.141s

やはり参照のほうが早そうです。

しかし、このベンチのコードの中では、sliceを確保する際に容量を指定していません。
容量を指定してみます。

コード
package main

import "testing"

func BenchmarkRef(b *testing.B) {
    slice := make([]*User, 0, b.N)
    for i := 0; i < b.N; i++ {
        slice = append(slice, &User{Id: i})
    }
}

func BenchmarkVal(b *testing.B) {
    slice := make([]User, 0, b.N)
    for i := 0; i < b.N; i++ {
        slice = append(slice, User{Id: i})
    }
}

ベンチ結果
$ go test -bench . -benchmem
goos: darwin
goarch: amd64
BenchmarkRef-4      10000000           193 ns/op          56 B/op          1 allocs/op
BenchmarkVal-4      20000000           184 ns/op          48 B/op          0 allocs/op
PASS
ok      _/Users/taisuke/Desktop/bench   6.111s

意外な結果になりました。

まとめ

想像だけでコードを書いてはいけないですね。
場面によって内容が異なると思いますので、パフォーマンスが気になる場合は自分でベンチマークを取ってみてはいかがでしょうか。

どうしてこのような原因になったのかは後日調べてみようと思います。

本当はgolangでのduck typeの実装について調べようと思っていたけど、気付いたら時間が…

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2