23378


行列をパディングするコードをGoで書きました。  
そこで下記のように並列化するものとしないものを用いてベンチマークを実行しました。しかしgoroutineを使ったほうが使わかなった方よりも時間がかかりメモリも多く使うという結果になりました。

goroutineを使ったほうが速くなると考えましたがなぜこのようなことが起こったのでしょうか。  
下記に作成したコード、ベンチマークのコード、コマンド、結果を記述します。

**コード**

[code]

    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
    }
    
[/code]

**ベンチマーク**

[code]

    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)
    }
    
[/code]

**コマンド**

[code]

    go test -cpu 4 -bench . -benchmem
    
[/code]

**結果**

**goroutineなし**

[code]

    BenchmarkEdge-4      100      11697971 ns/op    12066503 B/op       1198 allocs/op
    
[/code]

**goroutineあり**

[code]

    BenchmarkEdge-4      200      18919942 ns/op    33900980 B/op       1901 allocs/op
    
[/code]

**実行時間**

**goroutineなし** : 1.193s  
**goroutineあり** : 4.581s

**動作環境**  
MBA Intel Core i7 2.2 GHz  
コア数: 2  
メモリ:8GB

**追記**

ちなみにzeroPadはパディ‌​‌​ング部をEdgeの値でなく0で埋めます。goroutineのコストがこの計算よりも負荷が高いのではと想像しています。もし実装を変える事でgoroutineを‌‌​​有効活用できる方法があれば知りたいです。あとgoroutineを使わなかった時の処理速度(np/op)が遅いのに、繰り返し回数は大きくなっているのはなぜなんでし‌​ょ‌​うか


CC BY-SA 3.0
2016-03-22T14:49:32.467
23353
2016-03-23T09:14:15.650
2016-03-23T07:04:06.543
7232
7232
question
go
Goroutineを使うと性能が落ちるのはなぜか


ベンチマークを取る場合、実行回数(`b.N`)を直接には指定できない様で、`-benchtime` オプションで調整するしかない様です。

**動作環境**

Intel Xeon E5-2640 2.6GHz  
コア数: 16  
メモリ: 32GB  
Golang: 1.6

**goroutineあり**

[code]

    $ go test -cpu 4 -bench . -benchmem -benchtime=100ms
    testing: warning: no tests to run
    BenchmarkEdge-4    100     4283143 ns/op  12070650 B/op   1210 allocs/op
    PASS
    ok    _/home/nemo  0.489s
    
[/code]

**goroutineなし**

[code]

    $ go test -cpu 4 -bench . -benchmem -benchtime=200ms
    testing: warning: no tests to run
    BenchmarkEdge-4    100     9670161 ns/op  12066484 B/op   1198 allocs/op
    PASS
    ok    _/home/nemo  1.101s
    
[/code]

**追記**

[code]

    func BenchmarkEdge(b *testing.B) {
      m := GetMatrix(480, 360)
      for i := 0; i < b.N; i++ {
        err := m.Pad(7, Edge)
    
[/code]

`m.Pad(7, Edge)` となっていますが、

[code]

    // 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
    
[/code]

1
ループ毎に行列のサイズが増大していくのですね(`+14`)…。なので、ベンチマークにおける実行回数が増えると実行時間が線形時間(`O(n)`)以上に増大していくことになります。

そこで、`err := m.Pad(7, Edge)` を `err := m.Pad(0, Edge)`
としてベンチマークを実行してみました(意味のないことかも知れませんが)。

**goroutineあり**

[code]

    $ go test -cpu 4 -bench . -benchmem
    BenchmarkEdge-4   2000      937189 ns/op   1487671 B/op       486 allocs/op
    ok    _/home/nemo  2.000s
    
[/code]

**goroutineなし**

[code]

    $ go test -cpu 4 -bench . -benchmem
    BenchmarkEdge-4   1000     1232650 ns/op   1488394 B/op       486 allocs/op
    ok    _/home/nemo  1.389s
    
[/code]


CC BY-SA 3.0
2016-03-23T07:56:55.087
23378
2016-03-23T09:14:15.650
2016-03-23T09:14:15.650
23353
answer