Goroutineを使うと性能が落ちるのはなぜか
行列をパディングするコードをGoで書きました。
そこで下記のように並列化するものとしないものを用いてベンチマークを実行しました。しかしgoroutineを使ったほうが使わかなった方よりも時間がかかりメモリも多く使うという結果になりました。
goroutineを使ったほうが速くなると考えましたがなぜこのようなことが起こったのでしょうか。
下記に作成したコード、ベンチマークのコード、コマンド、結果を記述します。
コード
import (
"errors"
"fmt"
"sync"
)
// Matrix is object for matrix.
type Matrix struct {
Rows uint
Cols uint
M [][]float64
}
const (
Zero = iota + 1
Edge
)
// NewMatrix is constructor of Matrix.
func NewMatrix(m [][]float64) *Matrix {
return &Matrix{
M: m,
Rows: uint(len(m)),
Cols: uint(len(m[0])),
}
}
func (m *Matrix) edgePad(rows, cols uint, newMatrix [][]float64, w uint, y int, wg *sync.WaitGroup) {
col := make([]float64, m.Cols)
for x := 0; x < int(m.Cols); x++ {
if y < int(w) && x < int(w) {
col[x] = m.M[0][0]
} else if y < int(w) && x > int(w)-1 && x < int(cols+w) {
col[x] = m.M[0][x-int(w)]
} else if y < int(w) && x > int(cols+w)-1 {
col[x] = m.M[0][cols-1]
} else if y > int(w)-1 && y < int(rows+w) && x < int(w) {
col[x] = m.M[y-int(w)][0]
} else if y > int(w)-1 && y < int(rows+w) && x > int(cols+w)-1 {
col[x] = m.M[y-int(w)][cols-1]
} else if y > int(rows+w)-1 && x < int(w) {
col[x] = m.M[rows-1][0]
} else if y > int(rows+w)-1 && x > int(w)-1 && x < int(cols+w) {
col[x] = m.M[rows-1][x-int(w)]
} else if y > int(rows+w)-1 && x > int(cols+w)-1 {
col[x] = m.M[rows-1][cols-1]
} else {
col[x] = m.M[y-int(w)][x-int(w)]
}
}
newMatrix[y] = col
wg.Done()
}
// Pad pads the Matrix.
func (m *Matrix) Pad(w uint, mode int) error {
newMatrix := make([][]float64, m.Rows+w*2)
rows, cols := m.GetSize()
m.Cols = m.Cols + w*2
m.Rows = m.Rows + w*2
var wg sync.WaitGroup
for y := 0; y < int(m.Rows); y++ {
wg.Add(1)
switch mode {
case Zero:
//go m.zero(...)この部分をかえました。
m.zeroPad(rows, cols, newMatrix, w, y, &wg)
case Edge:
//go m.Edge(...)
m.edgePad(rows, cols, newMatrix, w, y, &wg)
}
}
wg.Wait()
m.M = newMatrix
return nil
}
ベンチマーク
func BenchmarkEdge(b *testing.B) {
m := GetMatrix(480, 360)
for i := 0; i < b.N; i++ {
err := m.Pad(7, Edge)
if err != nil {
b.Fatal(err)
}
}
}
func GetMatrix(rows, cols int) *Matrix {
rand.Seed(time.Now().UnixNano())
s := make([][]float64, rows)
for y := 0; y < rows; y++ {
col := make([]float64, cols)
for x := 0; x < cols; x++ {
col[x] = rand.Float64()
}
s[y] = col
}
return NewMatrix(s)
}
コマンド
go test -cpu 4 -bench . -benchmem
結果
goroutineなし
BenchmarkEdge-4 100 11697971 ns/op 12066503 B/op 1198 allocs/op
goroutineあり
BenchmarkEdge-4 200 18919942 ns/op 33900980 B/op 1901 allocs/op
実行時間
goroutineなし: 1.193s
goroutineあり: 4.581s
動作環境
MBA Intel Core i7 2.2 GHz
コア数: 2
メモリ:8GB
追記
ちなみにzeroPadはパディング部をEdgeの値でなく0で埋めます。goroutineのコストがこの計算よりも負荷が高いのではと想像しています。もし実装を変える事でgoroutineを有効活用できる方法があれば知りたいです。あとgoroutineを使わなかった時の処理速度(np/op)が遅いのに、繰り返し回数は大きくなっているのはなぜなんでしょうか