デフォルトでは、Goはシェダーと非同期呼び出しを使用して単一のスレッドで実行されます。 (プログラマーはマルチスレッドと並列処理の感覚を持っています。)この場合、チャネルは非常に高速に動作します。 ただし、Goに2つ以上のスレッドを使用するように指示すると、Goがロックの使用を開始し、チャネルのパフォーマンスが低下する場合があります。 チャンネルの使用を制限したくありません。 さらに、ほとんどのサードパーティライブラリはあらゆる機会にチャネルを使用します。 したがって、デフォルトで行われているように、Goを単一のスレッドで実行すると効果的です。
channel01.go
package main import "fmt" import "time" import "runtime" func main() { numcpu := runtime.NumCPU() fmt.Println("NumCPU", numcpu) //runtime.GOMAXPROCS(numcpu) runtime.GOMAXPROCS(1) ch1 := make(chan int) ch2 := make(chan float64) go func() { for i := 0; i < 1000000; i++ { ch1 <- i } ch1 <- -1 ch2 <- 0.0 }() go func() { total := 0.0 for { t1 := time.Now().UnixNano() for i := 0; i < 100000; i++ { m := <-ch1 if m == -1 { ch2 <- total } } t2 := time.Now().UnixNano() dt := float64(t2 - t1) / 1000000.0 total += dt fmt.Println(dt) } }() fmt.Println("Total:", <-ch2, <-ch2) }
users-iMac:channel user$ go run channel01.go NumCPU 4 23.901 24.189 23.957 24.072 24.001 23.807 24.039 23.854 23.798 24.1 Total: 239.718 0
行をコメントアウトして、すべてのカーネルをアクティブにしましょう。
runtime.GOMAXPROCS(numcpu) //runtime.GOMAXPROCS(1)
users-iMac:channel user$ go run channel01.go NumCPU 4 543.092 534.985 535.799 533.039 538.806 533.315 536.501 533.261 537.73 532.585 Total: 5359.113 0
20倍遅い? キャッチは何ですか? デフォルトのチャネルサイズは1です。
ch1 := make(chan int)
100を入れます。
ch1 := make(chan int, 100)
結果1ストリーム
users-iMac:channel user$ go run channel01.go NumCPU 4 9.704 9.618 9.178 9.84 9.869 9.461 9.802 9.743 9.877 9.756 Total: 0 96.848
4ストリーム結果
users-iMac:channel user$ go run channel01.go NumCPU 4 17.046 17.046 16.71 16.315 16.542 16.643 17.69 16.387 17.162 15.232 Total: 0 166.77300000000002
遅いのは2倍だけですが、常に使用できるとは限りません。
チャネルチャネルの例
package main import "fmt" import "time" import "runtime" func main() { numcpu := runtime.NumCPU() fmt.Println("NumCPU", numcpu) //runtime.GOMAXPROCS(numcpu) runtime.GOMAXPROCS(1) ch1 := make(chan chan int, 100) ch2 := make(chan float64, 1) go func() { t1 := time.Now().UnixNano() for i := 0; i < 1000000; i++ { ch := make(chan int, 100) ch1 <- ch <- ch } t2 := time.Now().UnixNano() dt := float64(t2 - t1) / 1000000.0 fmt.Println(dt) ch2 <- 0.0 }() go func() { for i := 0; i < 1000000; i++ { ch := <-ch1 ch <- i } ch2 <- 0.0 }() <-ch2 <-ch2 }
結果1ストリーム
users-iMac:channel user$ go run channel03.go NumCPU 4 1041.489
4ストリーム結果
users-iMac:channel user$ go run channel03.go NumCPU 4 11170.616
したがって、8つのコアがあり、Goでサーバーを作成する場合、Goだけに依存してプログラムを並列化することはできません。また、8つのシングルスレッドプロセスを開始することもできます。その前に、Goでも記述できるバランサーが必要です。 本番環境にサーバーがあり、シングルコアサーバーから4倍に切り替えたときに、処理が10%少なくなりました。
これらの数字はどういう意味ですか? 1つのコンテキストで毎秒3000リクエストを処理するタスクに直面しました(たとえば、各リクエストに連続して番号を付けます:1、2、3、4、5 ...もう少し複雑かもしれません)、毎秒3000リクエストのパフォーマンスは主にチャネルによって制限されます。 スレッドとコアを追加しても、パフォーマンスは期待したほど熱心に向上しません。 Goの1秒あたり3000リクエストは、最新の機器の特定の制限です。
ナイトアップデート:最適化の方法
「 Go:2 Years in Production 」という記事からのコメントは、この記事を書くように促しましたが、このコメントは最初のコメントを上回りました。
サイバーグラインドプロテクターは、次の最適化を提案しました。 8人の他のhabrazhitelamiに既に気に入っています。 彼らがコードを読んだのか、それともダイバーであり、直感的にすべてを行うのかはわかりませんが、説明します。 したがって、この記事はより完全で有益なものになります。
コードは次のとおりです。
package main import "fmt" import "time" import "runtime" func main() { numcpu := runtime.NumCPU() fmt.Println("NumCPU", numcpu) //runtime.GOMAXPROCS(numcpu) runtime.GOMAXPROCS(1) ch3 := make(chan int) ch1 := make(chan int, 1000000) ch2 := make(chan float64) go func() { for i := 0; i < 1000000; i++ { ch1 <- i } ch3 <- 1 ch1 <- -1 ch2 <- 0.0 }() go func() { fmt.Println("TT", <-ch3) total := 0.0 for { t1 := time.Now().UnixNano() for i := 0; i < 100000; i++ { m := <-ch1 if m == -1 { ch2 <- total } } t2 := time.Now().UnixNano() dt := float64(t2 - t1) / 1000000.0 total += dt fmt.Println(dt) } }() fmt.Println("Total:", <-ch2, <-ch2) }
この最適化の本質は何ですか?
1.チャネルch3を追加しました。 このチャネルは、最初のゴルチンの終わりまで、2番目のゴルチンをブロックします。
2. 2番目のゴルチンはチャネルch1から読み取らないため、充填中に最初のゴルチンをブロックします。 したがって、ch1は必要な1,000,000に増加します
つまり、コードはもはや並列ではなく、順番に機能し、チャネルは配列として使用されます。 そしてもちろん、このコードは2番目のコアを使用できません。 このコードのコンテキストでは、「N倍の理想的な加速」について話すことはできません。
主なことは、そのようなコードは最初に定義されたデータ量でのみ機能し、継続的に機能することはできず、生涯にわたって情報を無期限に処理することです。
更新2:Go 1.1.2でのテスト
バッファ1でテスト番号1(channel01.go)
ch1 := make(chan chan int, 1)
1スレッド
go runchannel01.go NumCPU 4 66.0038 66.0038 67.0038 66.0038 67.0038 66.0038 65.0037 67.0038 67.0039 76.0043 Total: 0 673.0385000000001
4スレッド
go run channel01.go NumCPU 4 116.0066 186.0106 112.0064 117.0067 175.01 115.0066 114.0065 148.0084 133.0076 153.0088 Total: 0 1369.0782
結論:はるかに良い。 なぜバッファ1を配置するのか想像するのは難しいですが、おそらくそのようなバッファのアプリケーションがあります。
バッファ100でテスト番号1(channel01.go)
ch1 := make(chan chan int, 100)
1スレッド
go run channel01.go NumCPU 4 16.0009 17.001 16.0009 16.0009 16.0009 16.0009 17.001 16.0009 17.001 16.0009 Total: 0 163.00930000000002
4スレッド
go runchannel01.go NumCPU 4 66.0038 66.0038 67.0038 66.0038 67.0038 66.0038 65.0037 67.0038 67.0039 76.0043 Total: 0 673.0385000000001
結論:バージョン1.0.2より2倍悪い
テスト番号2(channel03.go)
1スレッド
go run channel03.go NumCPU 4 1568.0897
4スレッド
go run channel03.go NumCPU 4 12119.6932
バージョン1.0.2とほぼ同じですが、わずかに優れています。 1:8対1:10