Go の 構造体とChannel を組み合わせたコンカレントプログラムの方法と比較

業務でGo言語を使う必要があり、Go のコンカレントプログラミングの方法を理解する必要が出てきた。以前 go ルーチンや、ロックのメカニズム、チャネルについて学んだが、次のような構造体とそのメソッドを持ったプログラミングはどのように書くべきだろう?

Team -* Service
______-* Challenge -*History

そして、オブジェクト指向的に考えると、例えば、Team は複数あり、Team の StatusCheck 関数を持っていて並列に実行する。StatusCheck 関数は、並列実行で、Service のStatusCheck 関数を実行する。Service は、エンドポイントのURLを持っていてそこに対してリクエストを投げて、ステータスに合わせて、Service の Status の値をアップデートする。といった具合。(そのあと、Historyの作成を行うけど、今回は対象外)つまり、2重に並列実行を実施して、すべての並列処理の実行をまって、データをアップデートするということができないといけません。Fan-out, Fan-in のような感じです。

type Service struct {
    Name  string
    Value int
}
type Team struct {
    Name     string
    Services *[]Service
}

このような場合、どのようなコードを書けばいいだろう?

チャネルを使ったプログラミング

このような場合はチャネルを使ったプログラムを書くといいだろう。Team と、Service の Update メソッドを用意する。GoRoutineWithoutChannel メソッドを実行するとよい感じにできる。その関数を見てみよう。基本はチャネルを用意して、チームの数だけループをさせて、go routine の中で、team の Update 関数を呼んでいる。その後、Team の要素数だけ Channel の終了を待って、それを、newTeam として、Channel から帰ってきた値 (Team) を新しいTeam の配列にして終わり。

func (c *Team) Update() {
    ch := make(chan Service)

    for _, v := range *c.Services {
        go func(service Service) {
            service.Update()
            ch <- service
        }(v)
    }
    var services []Service
    for i := 0; i < len(*c.Services); i++ {
        result := <-ch
        services = append(services, result)
    }
    c.Services = &services
}

func (s *Service) Update() {
    s.Value = s.Value + 1
    time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
}

func GoRoutineWithoutChannel(teams *[]Team) string {
    ch := make(chan Team)
    for _, v := range *teams {
        go func(team Team) {
            team.Update()
            ch <- team
        }(v)
    }
    var newTeams []Team
    for i := 0; i < len(*teams); i++ {
        result := <-ch
        newTeams = append(newTeams, result)
    }

    jsonBytes, err := json.Marshal(newTeams)
    if err != nil {
        fmt.Println("JSON Marshal 2 error:", err)
        return "GoRoutineWithoutChannel Error!"
    }
    return string(jsonBytes)
}

実行結果

見ずらいが、しっかり想定通りの値(各サービスの Value が0から1に更新されている) ちゃんと平行実行されている様子。ただし、順番は保証されていない。並列に実行されて早く終わったところからチャネルに出力されるので当然といえる。

********************************************** Go without Channel
[{"Name":"Team 13","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 16","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 3","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer
2","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 11","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 14","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer
2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 5","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 4","Services":[{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 2","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 1","Services":[{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 17","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 19","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 9","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 8","Services":[{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 15","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 0","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 7","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 10","Services":[{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 12","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 6","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 18","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 4","Value":1}]}]

go routine の書き方の注意

最初、この部分を

    ch := make(chan Team)
    for _, v := range *teams {
        go func(team Team) {
            team.Update()
            ch <- team
        }(v)
    }

次のように書いていた。こうするとどうなるかというと、すべてのデータが、Team 19 つまり最後のチームのデータになった。なんでや!と思ったのですが、後から考えると当然で、range でループを回しているときに、go routine になっているので、非同期実行されて、次に進みます。ところが、ここでいう v の変数は変更されていきます。つまり、go ルーチンの中の v は、シェアされた変数になっていますので、そのようなことが起こります。上の例だと、go routine に引数として、vを渡しているので、値渡しとしてTeam がコピーされて渡されますので思った通りの挙動になります。

    ch := make(chan Team)
    for _, v := range *teams {
        go func() {
            v.Update()
            ch <- v
        }()
    }

同期実行との比較

では、こんなメソッドを書いてみて、同期実行と、平行実行とどれぐらい効率が違うか見てみよう。同期で書くとシンプルだ。

func SyncExecution(teams *[]Team) string {
    var newTeams []Team
    for _, v := range *teams {
        v.SyncUpdate()
        newTeams = append(newTeams, v)
    }
    jsonBytes, err := json.Marshal(newTeams)
    if err != nil {
        fmt.Println("JSON Marshal 4 error:", err)
        return "Sync Exec Error!"
    }
    return string(jsonBytes)
}

func (c *Team) SyncUpdate() {
    var services []Service
    for _, v := range *c.Services {
        v.Update()
        services = append(services, v)
    }
    c.Services = &services
}

実行結果

同期実行でループを回しているので、元の構造とかわらず、チームなどの並びも元のデータのままである。

********************************************** Go with Sync exec
[{"Name":"Team 0","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer
4","Value":1}]},{"Name":"Team 1","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 2","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 3","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 4","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 5","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 6","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 7","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 8","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 9","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 10","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 11","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 12","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 13","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 14","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 15","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 16","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 17","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 18","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 19","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1}]}]

ベンチマーク

さて、両者のベンチマークはどうだろう?
作った関数に対してベンチマークをかけてみた。

    fmt.Println("********************************************** Go without Channel")
    result := testing.Benchmark(func(b *testing.B) { GoRoutineWithoutChannel(teams) })
    fmt.Printf("Benchmark Repeat: %d Duration: %v Bytes: %d  Memory Allocation: %d Memory Bytes: %d\n", result.N, result.T, result.Bytes, result.MemAllocs, result.MemBytes)
    fmt.Println(result)
    fmt.Println("********************************************** Go with Sync exec")
    result = testing.Benchmark(func(b *testing.B) { SyncExecution(teams) })
    fmt.Printf("Benchmark Repeat: %d Duration: %v Bytes: %d  Memory Allocation: %d Memory Bytes: %d\n", result.N, result.T, result.Bytes, result.MemAllocs, result.MemBytes)
    fmt.Println(result)

ベンチマーク実行結果

さすがに、トータル実行時間(Duration)が全く違う。実に 500倍ぐらいの効率の違いだ。メモリのアロケーションは同期実行のほうが有利だが、話にならないぐらい並列実行のほうが早い。

********************************************** Go without Channel
Benchmark Repeat: 2000000000 Duration: 100.1246ms Bytes: 0  Memory Allocation: 250 Memory Bytes: 33152
2000000000               0.05 ns/op
********************************************** Go with Sync exec
Benchmark Repeat: 1 Duration: 4.890591s Bytes: 0  Memory Allocation: 116 Memory Bytes: 23216
       1        4890591000 ns/op

Channel を引数にした方法で実装する

次に、同じ go routine を使う方法でも、Channel を引数に使う方法を実験してみる。こちらは、[]Team でループを回すのではなく、最初に、[]Team をチャネルに変換してから、チャネルを渡して関数を実行する方法。最初に、GetTeamsChanel[]Team をチャネルに変更したのち、次の UpdateTeamAsync に渡して、チャネルを引数に並列実行を行う。WaitGroup を使って、すべての go routine が終了するのを待っている。この方法が結構いいのは、チャネルを引数にして、チャネルが返るので、うまく作れば、パイプのようなことができるようになる。実際にGoRoutineWithChannel の起点部分もなかなかかっこいい感じになっている。

func GetTeamsChanel(teams *[]Team) <-chan Team {
    out := make(chan Team)
    go func() {
        for _, n := range *teams {
            out <- n
        }
        close(out)
    }()
    return out
}

func UpdateTeamsAsync(teams ...<-chan Team) <-chan Team {
    var wg sync.WaitGroup
    out := make(chan Team)
    output := func(ts <-chan Team) {
        for t := range ts {
            t.Update()
            out <- t
        }
        wg.Done()
    }
    wg.Add(len(teams))
    for _, t := range teams {
        go output(t)
    }

    go func() {
        wg.Wait()
        close(out)
    }()
    return out
}

func GoRoutineWithChannel(teams *[]Team) string {
    teamsChannel := GetTeamsChanel(teams)
    newTeamsChannel := UpdateTeamsAsync(teamsChannel)
    var resultTeams []Team
    for i := 0; i < len(*teams); i++ {
        t := <-newTeamsChannel
        resultTeams = append(resultTeams, t)
    }
    jsonBytes, err := json.Marshal(resultTeams)
    if err != nil {
        fmt.Println("JSON Marshal 3 error:", err)
        return "GoRoutineWitChannel Error!"
    }
    return string(jsonBytes)
}

実行結果

Service の平行実行は手を抜いて、前の方法と同じなので、バラバラの順番だが、チャネルの方式だと、Team は元の順番のままになっている。なぜなら、チャネル化すると、キューに入るようなイメージになるので、入ったところからデキューされていく形式になるということだろうか?

********************************************** Go with Channel
[{"Name":"Team 0","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer
4","Value":1}]},{"Name":"Team 1","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 2","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 3","Services":[{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 4","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 5","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 6","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 7","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 8","Services":[{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 9","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 10","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 11","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1}]},{"Name":"Team 12","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 13","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 4","Value":1}]},{"Name":"Team 14","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 15","Services":[{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1}]},{"Name":"Team 16","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 17","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1}]},{"Name":"Team 18","Services":[{"Name":"Servicer 3","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1}]},{"Name":"Team 19","Services":[{"Name":"Servicer 4","Value":1},{"Name":"Servicer 0","Value":1},{"Name":"Servicer 2","Value":1},{"Name":"Servicer 1","Value":1},{"Name":"Servicer 3","Value":1}]}]

こっちの書き方はかっこいいので、ベンチマーク見てみましょう。

ベンチマーク

ぐぬぬ、残念ながら、チャネル方式の場合は、10倍以上のオーダーがかかるようです。今回の目的(1秒ごとにポーリング)とかを考えると、チャネル引数じゃない方法を選択するほうがよさそうです。おそらく、チャネルに渡しているのが構造体なので、データを移送するのに時間がかかっているということが想像されます。もし、このコードを改善する方法があれば是非ご指摘ください。

********************************************** Go without Channel
Benchmark Repeat: 2000000000 Duration: 100.1246ms Bytes: 0  Memory Allocation: 250 Memory Bytes: 33152
2000000000               0.05 ns/op
********************************************** Go with Channel
Benchmark Repeat: 1 Duration: 1.5584286s Bytes: 0  Memory Allocation: 239 Memory Bytes: 31776
       1        1558428600 ns/op

ソートを実行する

ということは、一番最初のコードは、ソートが必要なので、ソートを入れても、チャネル引数のものより早いか確認してみます。先ほどのコードにこれを追加します。

    sort.Slice(newTeams, func(i, j int) bool {
        return (newTeams[i].Name < newTeams[j].Name)
    })

実行結果

ソートはほとんど無視できる程度のコストしかかかりませんでした。これはとっても早い!

********************************************** Go without Channel
Benchmark Repeat: 2000000000 Duration: 100.1246ms Bytes: 0  Memory Allocation: 250 Memory Bytes: 33152
2000000000               0.05 ns/op
********************************************** Go without Channel with Sort
Benchmark Repeat: 2000000000 Duration: 100.0795ms Bytes: 0  Memory Allocation: 249 Memory Bytes: 33088
2000000000               0.05 ns/op
********************************************** Go with Channel
Benchmark Repeat: 1 Duration: 1.5584286s Bytes: 0  Memory Allocation: 239 Memory Bytes: 31776
       1        1558428600 ns/op

結論

今回、2種類の方法で並列実行してみました。今回のケースでは、ソート付きのパラメータがチャネルじゃない方式が一番適切と判断しました。しかし、チャネルに渡すものが小さければ、チャネル渡しのほうがカッコいいかもしれません。この実験のおかげで、今回のような構造体付きのFan-out, Fan-in シナリオの実行方式に自信が持てるようになり、理解できました。

あと、今回初めてベンチマークを使ってみましたが、これは素晴らしいです。自分で多分速いだろうとか今まで勝手に想像していましたが、こうやってスパイクの時にベンチマークをとるほうがよっぽど明確です。これは、typescript や C# を書いているときにも習慣にしたいと思いました。

********************************************** Go without Channel
Benchmark Repeat: 2000000000 Duration: 100.1246ms Bytes: 0  Memory Allocation: 250 Memory Bytes: 33152
2000000000               0.05 ns/op
********************************************** Go without Channel with Sort
Benchmark Repeat: 2000000000 Duration: 100.0795ms Bytes: 0  Memory Allocation: 249 Memory Bytes: 33088
2000000000               0.05 ns/op
********************************************** Go with Channel
Benchmark Repeat: 1 Duration: 1.5584286s Bytes: 0  Memory Allocation: 239 Memory Bytes: 31776
       1        1558428600 ns/op
********************************************** Go with Sync exec
Benchmark Repeat: 1 Duration: 4.890591s Bytes: 0  Memory Allocation: 116 Memory Bytes: 23216
       1        4890591000 ns/op

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.