Goでパフォーマンスに差があらわれがちなポイントにスライスとマップのメモリ確保がある。
メモリ確保を高速化する方法を説明する。
スライスのメモリ確保を高速化する
スライスの拡張append()
は以下のプロセスになっている
- すでに確保済みのメモリが不足した場合は、OSに要求
- 確保済みのものから新しい配列用にメモリを割り当て
- 新しい配列に古い配列の内容をコピー
- 古い配列にアクセスするスライスや変数がなくなったらメモリを解放
上記のようにメモリ不足が発生するつどOSにメモリ追加の確保を依頼しているので、あらかじめ必要な要素数が分かっている場合はサイズを指定してスライスを宣言するのが望ましい。
サンプル
実際にパフォーマンス計測も行った
package main
import (
"fmt"
"time"
)
func main() {
// パフォーマンス計測開始
start := time.Now()
// スライスの動的拡張
dynamicSlice := make([]int, 0)
for i := 0; i < 1000000000; i++ {
dynamicSlice = append(dynamicSlice, i)
}
// パフォーマンス計測終了
dynamicDuration := time.Since(start)
fmt.Println("動的拡張スライスの処理時間:", dynamicDuration)
// 動的拡張スライスの処理時間: 1m10.7608742s
// 再度パフォーマンス計測開始
start = time.Now()
// スライスの事前確保
preAllocatedSlice := make([]int, 0, 1000000000)
for i := 0; i < 1000000000; i++ {
preAllocatedSlice = append(preAllocatedSlice, i)
}
// パフォーマンス計測終了
preAllocatedDuration := time.Since(start)
fmt.Println("事前確保スライスの処理時間:", preAllocatedDuration)
// 事前確保スライスの処理時間: 30.4573957s
}
マップのメモリ確保を高速化する
マップの背後には「バケット」と呼ばれるデータ構造がある。
マップの場合も、事前にメモリを確保することでパフォーマンスが向上します。
サンプル
package main
import (
"fmt"
"time"
)
func main() {
// マップの動的確保
dynamicMap := make(map[int]int)
start := time.Now()
for i := 0; i < 100000000; i++ {
dynamicMap[i] = i
}
dynamicDuration := time.Since(start)
fmt.Println("動的確保マップの処理時間:", dynamicDuration)
// 動的確保マップの処理時間: 18.3244288s
// マップの事前確保
preAllocatedMap := make(map[int]int, 100000000)
start = time.Now()
for i := 0; i < 100000000; i++ {
preAllocatedMap[i] = i
}
preAllocatedDuration := time.Since(start)
fmt.Println("事前確保マップの処理時間:", preAllocatedDuration)
// 事前確保マップの処理時間: 12.1498153s
}
参考