Go:マルチスレッド

Goのマルチスレッド化に関するトピックhabrahabr.ru/post/195574に興味がありました。

著者とコミュニティのコメントを注意深く読み、トピックがまだ完全に開示されていないと判断しました。

将来、誤解されないように、以下では「スレッド」という用語は「ストリーム」の意味ではなく、「スレッド」の意味でのみ使用されることを受け入れてください。 ありがとう



最初のトピックの著者のように、私もGo言語が大好きで、最初の機会にそれを使用します。

また、マルチスレッドのスタイルにも感銘を受け、機会があれば並列化を使用します。

たとえば、これまで複数のスレッドを使用してデータベースを操作する組織では、アクセス速度が大幅に向上し、1つのプロセスでも許容値に達しました。

しかし、ここに私が考えさせられたチャンネルの例があります。

私の車では、違いは10〜20倍ではありませんでしたが、違いはありました。 そして、違いは非常に重要です-2つのスレッドで、タスクは約2倍ゆっくり実行されました。

プログラムを便利な形式に書き直し、ロードサイクルといくつかのキーを追加しました。 そして、これはそれから来たものです。



プログラムテキスト
/* channel_test01.go * Tests how go-routines interact with channels * Call: channel_test01 --help * Pls, do not use name "channel_test", because this name always is used by go-pkg-system */ package main import ( "fmt" "time" "flag" "os" "runtime" ) // flag support for program var MAXPROCS int var LOAD_CYCLES int //internal burden cycle var Usage = func() { fmt.Fprintf(os.Stderr, "Usage channel_test01 [-maxprocs=NN] [-cycles=NN] \n", os.Args[0]) } func init() { flag.IntVar(&MAXPROCS, "maxprocs", 1, "maxprocs for testing. From 1 to 256 "); flag.IntVar(&LOAD_CYCLES, "cycles", 1000, "burden internal cycle for testing. From 1 to 1000000 and more "); } func main() { flag.Parse();//get MAXPROCS and LOAD_CYCLES from flags // runtime.GOMAXPROCS() returns previous max_procs max_procs := runtime.GOMAXPROCS(MAXPROCS) // second call to get real state max_procs = runtime.GOMAXPROCS(MAXPROCS) fmt.Println("MaxProcs = ", max_procs) ch1 := make(chan int) ch2 := make(chan float64) go chan_filler(ch1, ch2) go chan_extractor(ch1, ch2) fmt.Println("Total:", <-ch2, <-ch2) } func chan_filler(ch1 chan int, ch2 chan float64) { const CHANNEL_SIZE = 1000000 for i := 0; i < CHANNEL_SIZE; i++ { for j := 0; j < LOAD_CYCLES; j++ { i++ } //thus we avoid optimizer influence i = i - LOAD_CYCLES ch1 <- i } ch1 <- -1 ch2 <- 0.0 } func chan_extractor(ch1 chan int, ch2 chan float64) { const PORTION_SIZE = 100000 total := 0.0 for { t1 := time.Now().UnixNano() for i := 0; i < PORTION_SIZE; i++ { // burden cycle for j := 0; j < LOAD_CYCLES; j++ { i++ } i = i - LOAD_CYCLES m := <-ch1 if m == -1 { ch2 <- total } } t2 := time.Now().UnixNano() dt := float64(t2 - t1)/1e9 //nanoseconds ==> seconds total += dt fmt.Println(dt) } }
      
      









結果


マシン:Intel、3 GHzの4コア、4 GB RAM、linux-x86-64、kernel-3.11、golang-1.1.2



私のマシンで達成可能なruntime.MAXPROCSの最大値は256です。さらに指定できますが、それでも256に設定されます。デフォルト値は1です。



プログラムによって生成されたスレッドの数(ps -C channel_test01 -L)

-maxprocs = 1:で3スレッド。

-maxprocs = 2以上の場合:常に4スレッド。



長期運用中に、さらに2つのフローが表示され、プロセスの最後までフローが残っていることがありました。 ガベージコレクションのように見えます。



(/ proc / cpuinfoに24個のコアがあることを示す仮想サーバーの1つで試してみました。そのスレッド数も4でした。しかし、5番目のスレッドがあったことがよくあります-貪欲なvdsはメモリを割り当てるのに急いでいなかったようです) 、ガベージコレクターが出て回りました)。



サイクルのフロー数に対するチャネルの充填率の依存性= 1000


perf-procsグラフ



MAXPROCS = 1で最大のパフォーマンスが得られます



バラスト負荷の増加に対するチャネル充填率の依存性




パフォーマンス負荷グラフ



負荷がかかっている場合でも、マルチスレッド化は依然として負荷がかかります。



チャートからの結論


maxprocs = 1の場合、すべてが1つのスレッドで動作し、内部golangスケジューラーによって擬似マルチスレッドが提供されます。これは非常に効率的です。

maxprocs = 2以上では、より多くのフローが表示され、フロー間の相互作用のメカニズムがオンになります。 スリープ状態のスレッドでブロックが発生し、すべてが遅くなります-約2回。

maxprocs = 3..256の場合、システム内の実際のスレッド数は増加しませんが、一部のスレッドは「疑似スレッド」になり、その相互作用は内部golangスケジューラーの制御下に入ります。 これにより、「擬似スレッド」の数が増えるたびに、速度が少し向上します。

maxprocsがさらに増加すると(最大値256まで)、ますます多くのスレッドが擬似スレッドになり、内部スケジューラーの制御下に置かれます。 ただし、一部のスレッドは依然として独立したシステムスレッドのままです。 スリープスレッドと高速コンピューティングでは、これによりパフォーマンスが約10%低下します。



おわりに


runtime.MAXPROCSパラメーターを変更すると、パフォーマンスが向上する場合と低下する場合があります。 これは、並列化の効果が特定のタスクと特定の機器に依存するという一般原則をもう一度確認します。



All Articles