【Go言語】map生成時の再代入と存在確認の速度比較

2023/02/10に公開約2,600字

Go言語でmapを生成する際に再代入するか存在確認するかどちらが速いか検証した際の備忘録。

下記のような状況でmapを生成する処理を実装した。

  • あるユーザの注文データに紐づく商品のカテゴリの一覧を取得したい
  • ユーザが注文したn個のデータがある
  • カテゴリの種類はm個
  • 注文、商品、カテゴリの関係は「1 : 1 : 1」

カテゴリ一覧を取得するためにmapを利用することにし、下記のようなコードを実装。

categoryMap := make(map[int]string)
for _, order := range orders {
	category := order.Product.Category

	categoryMap[category.Id] = category.Name
}

mapの性質を利用し全て代入もしくは再代入させてたが、下記のように存在確認をして存在しない場合のみに代入させることもできる。

categoryMap := make(map[int]string)
for _, order := range orders {
	category := order.Product.Category

+	if _, ok := categoryMap[category.Id]; ok {
+		continue
+	}

	categoryMap[category.Id] = category.Name
}

後者のほうが意図が伝わりやすいし、こちらのほうがいいかなと思ったが、速度的に違いがあるのか気になったので検証してみた。

速度計測

mapを生成する処理を2パターン作成し、速度を計測[1]してみる。

package main

import (
	"fmt"
	"math/rand"
	"time"
)

type category struct {
	id   int
	name string
}

func makeCategories() []category {
	// カテゴリ数
	m := 10
	categories := make([]category, 0)
	for i := 1; i <= m; i++ {
		categories = append(categories, category{id: i, name: "カテゴリ"})
	}

	return categories
}

func main() {
	// 試行回数
	x := 10

	// カテゴリ
	categories := makeCategories()
	fmt.Printf("カテゴリ数: %d\n", len(categories))

	// 注文数
	n := 100
	fmt.Printf("注文数: %d\n", n)

	// 計測
	var time1 int64
	var time2 int64
	for i := 0; i < x; i++ {
		// 再代入
		categoryMap1 := make(map[int]string)
		now := time.Now()
		for i := 0; i < n; i++ {
			index := rand.Intn(len(categories))
			category := categories[index]

			categoryMap1[category.id] = category.name
		}
		time1 += time.Since(now).Nanoseconds()

		// 存在確認
		categoryMap2 := make(map[int]string)
		now2 := time.Now()
		for i := 0; i < n; i++ {
			index := rand.Intn(len(categories))
			category := categories[index]

			if _, ok := categoryMap2[category.id]; ok {
				continue
			}

			categoryMap2[category.id] = category.name
		}
		time2 += time.Since(now2).Nanoseconds()
	}
	fmt.Printf("再代入パターン処理時間:   %vns\n", int(time1)/x)
	fmt.Printf("存在確認パターン処理時間: %vns\n", int(time2)/x)

}

計測結果

❯ go run ./test.go
カテゴリ数: 10
注文数: 100
再代入パターン処理時間:   8516ns
存在確認パターン処理時間: 7750ns

❯ go run ./test.go
カテゴリ数: 10
注文数: 1000
再代入パターン処理時間:   54537ns
存在確認パターン処理時間: 50245ns

❯ go run ./test.go
カテゴリ数: 100
注文数: 1000
再代入パターン処理時間:   81679ns
存在確認パターン処理時間: 79391ns

❯ go run ./test.go
カテゴリ数: 100
注文数: 10000
再代入パターン処理時間:   577529ns
存在確認パターン処理時間: 546937ns

データ数が少ないとかなり早く処理が終わってしまうので、単位は全てナノ秒に統一している。

何度か検証してみた結果、存在確認したほうが常に早いが、誤差の範囲といった印象。

ただ、データ数(今回でいうと注文データ数)が億を超えるとその誤差も馬鹿にならない差になる。

コード量は少し増えてしまうが、map生成時は存在確認をしたほうが無難という結果になった。

脚注
  1. https://public-constructor.com/golang-elapsed-time/ ↩︎

Discussion

ログインするとコメントできます