2017-01-13 7 views
1

私は、一般的な並行/並列プログラミングに進んでいます。 goroutinesを試してみるために、私は単純に1億のランダムなものを生成する小さなテストプログラムを作りました。int - 最初は1つのゴルーチンで、次に多くのゴルーチンでruntime.NumCPU()Goで同時に乱数を生成

しかし、私は一貫してより多くのゴ​​ルーチンを使用してパフォーマンスを悪化させます。私は自分のプログラムデザインや、ゴルーチン/チャンネル/その他のGo機能を使う方法で欠かせないものがあると思います。どんなフィードバックも高く評価されます。

以下のコードを添付します。

package main 

import "fmt" 
import "time" 
import "math/rand" 
import "runtime" 

func main() { 
    // Figure out how many CPUs are available and tell Go to use all of them 
    numThreads := runtime.NumCPU() 
    runtime.GOMAXPROCS(numThreads) 

    // Number of random ints to generate 
    var numIntsToGenerate = 100000000 
    // Number of ints to be generated by each spawned goroutine thread 
    var numIntsPerThread = numIntsToGenerate/numThreads 
    // Channel for communicating from goroutines back to main function 
    ch := make(chan int, numIntsToGenerate) 

    // Slices to keep resulting ints 
    singleThreadIntSlice := make([]int, numIntsToGenerate, numIntsToGenerate) 
    multiThreadIntSlice := make([]int, numIntsToGenerate, numIntsToGenerate) 

    fmt.Printf("Initiating single-threaded random number generation.\n") 
    startSingleRun := time.Now() 
    // Generate all of the ints from a single goroutine, retrieve the expected 
    // number of ints from the channel and put in target slice 
    go makeRandomNumbers(numIntsToGenerate, ch) 
    for i := 0; i < numIntsToGenerate; i++ { 
    singleThreadIntSlice = append(singleThreadIntSlice,(<-ch)) 
    } 
    elapsedSingleRun := time.Since(startSingleRun) 
    fmt.Printf("Single-threaded run took %s\n", elapsedSingleRun) 


    fmt.Printf("Initiating multi-threaded random number generation.\n") 
    startMultiRun := time.Now() 
    // Run the designated number of goroutines, each of which generates its 
    // expected share of the total random ints, retrieve the expected number 
    // of ints from the channel and put in target slice 
    for i := 0; i < numThreads; i++ { 
    go makeRandomNumbers(numIntsPerThread, ch) 
    } 
    for i := 0; i < numIntsToGenerate; i++ { 
    multiThreadIntSlice = append(multiThreadIntSlice,(<-ch)) 
    } 
    elapsedMultiRun := time.Since(startMultiRun) 
    fmt.Printf("Multi-threaded run took %s\n", elapsedMultiRun) 
} 


func makeRandomNumbers(numInts int, ch chan int) { 
    source := rand.NewSource(time.Now().UnixNano()) 
    generator := rand.New(source) 
    for i := 0; i < numInts; i++ { 
     ch <- generator.Intn(numInts*100) 
    } 
} 

答えて

4

まずましょう正しいとあなたのコード内のいくつかの最適化:

ゴー1.5以降、使用可能なCPUコアの数なので、(それは問題はありませんが)それを設定する必要はありませんにGOMAXPROCSデフォルト。生成する

番号:

var numIntsToGenerate = 100000000 
var numIntsPerThread = numIntsToGenerate/numThreads 

numThreadsは3のようなものである場合には、マルチゴルーチンの場合には、あなたが(除算を整数に起因する)より少ない数の生成を持っているので、のは、それを修正してもらおう:

numIntsToGenerate = numIntsPerThread * numThreads 

いいえ賢明な値(例えば1000)にそれを減らす、億個の値のバッファが必要になります。

ch := make(chan int, 1000) 

あなたがappend()を使用する場合は、作成したスライスが0の長さ(および適切な容量)を持っている必要があります。

singleThreadIntSlice := make([]int, 0, numIntsToGenerate) 
multiThreadIntSlice := make([]int, 0, numIntsToGenerate) 

しかし、不必要だあなたのケースでは、唯一の1つのゴルーチンが結果を収集しているとして、あなたは、単に使用することができますこのようなインデックス、および作成スライス:

singleThreadIntSlice := make([]int, numIntsToGenerate) 
multiThreadIntSlice := make([]int, numIntsToGenerate) 

そして、収集結果:

for i := 0; i < numIntsToGenerate; i++ { 
    singleThreadIntSlice[i] = <-ch 
} 

// ... 

for i := 0; i < numIntsToGenerate; i++ { 
    multiThreadIntSlice[i] = <-ch 
} 

[OK]をクリックします。コードが改善されました。それを実行しようとすると、マルチゴルーチンバージョンが遅く実行されることがあります。何故ですか?

複数のゴルーチンの結果を制御、同期、収集するのにオーバーヘッドがあるからです。実行するタスクが少ない場合は、通信オーバーヘッドが大きくなり、全体的にパフォーマンスが低下します。

あなたのケースはそのようなケースです。 rand.Rand()を設定すると、1つの乱数を生成するのがかなり高速です。

のは、我々は、複数のゴルーチンの利益見ることができるように十分な大きさためにあなたの「タスク」を変更してみましょう:乱数を取得するには、このケースで

// 1 million is enough now: 
var numIntsToGenerate = 1000 * 1000 


func makeRandomNumbers(numInts int, ch chan int) { 
    source := rand.NewSource(time.Now().UnixNano()) 
    generator := rand.New(source) 
    for i := 0; i < numInts; i++ { 
     // Kill time, do some processing: 
     for j := 0; j < 1000; j++ { 
      generator.Intn(numInts * 100) 
     } 
     // and now return a single random number 
     ch <- generator.Intn(numInts * 100) 
    } 
} 

を、我々は1000個の乱数を生成し、ちょうどそれらを投げます私たちが返すものを生成する前に、ある程度の計算/時間をとるために)。これは、作業者のゴルーチンの計算時間がよりも大きい場合に、複数のゴルーチンの通信オーバーヘッドであるが実行されるように行います。今のアプリを実行

、4コアのマシン上で私の結果:

Initiating single-threaded random number generation. 
Single-threaded run took 2.440604504s 
Initiating multi-threaded random number generation. 
Multi-threaded run took 987.946758ms 

は、マルチゴルーチン版は2.5倍高速を実行します。つまり、ゴルーチンが1000ブロックで乱数を出力すると、単一のゴルーチン世代と比較して2.5倍の速さで実行されます。

最後の一つのノート:

あなたシングルゴルーチンのバージョンは、複数のゴルーチンを使用しています:1の結果を収集するために数字と1を生成します。ほとんどの場合、コレクタはCPUコアを完全には利用せず、ほとんどの場合結果を待つだけですが、2つのCPUコアが使用されます。 「1.5」のCPUコアが使用されていると見積もりましょう。マルチゴルーチンバージョンは4つのCPUコアを使用します。大まかな見積もり:4/1.5 = 2.66で、私たちのパフォーマンス向上に非常に近い。

+1

これはすべて意味があります。私はそれがチャネルの使用に関連するコストに関係しているかもしれないと推測していました(ただし、実行される作業単位に関連する通信オーバーヘッドに関しては考えませんでしたが、チャネルの輻輳の可能性またはそのようなもの)。明確で教育的なウォークスルーに感謝します。 – Karl

0

実際に乱数を生成したい場合は、各タスクで数値を生成してから、一度に1つの数値を生成するタスクではなく、一度にそれらを返す必要があります。チャンネルへの読み書きは、多忙な場合には遅くなります。以下は変更されたコードです。次に、タスクが必要な数値を生成します。これは、マルチ・ゴー・ルーチンの場合より優れています。また、マルチ・ルーチン・ルーチンから結果を収集するスライスを使用しました。

package main 

import "fmt" 
import "time" 
import "math/rand" 
import "runtime" 

func main() { 
    // Figure out how many CPUs are available and tell Go to use all of them 
    numThreads := runtime.NumCPU() 
    runtime.GOMAXPROCS(numThreads) 

    // Number of random ints to generate 
    var numIntsToGenerate = 100000000 
    // Number of ints to be generated by each spawned goroutine thread 
    var numIntsPerThread = numIntsToGenerate/numThreads 

    // Channel for communicating from goroutines back to main function 
    ch := make(chan []int) 

    fmt.Printf("Initiating single-threaded random number generation.\n") 
    startSingleRun := time.Now() 
    // Generate all of the ints from a single goroutine, retrieve the expected 
    // number of ints from the channel and put in target slice 
    go makeRandomNumbers(numIntsToGenerate, ch) 
    singleThreadIntSlice := <-ch 
    elapsedSingleRun := time.Since(startSingleRun) 
    fmt.Printf("Single-threaded run took %s\n", elapsedSingleRun) 

    fmt.Printf("Initiating multi-threaded random number generation.\n") 

    multiThreadIntSlice := make([][]int, numThreads) 
    startMultiRun := time.Now() 
    // Run the designated number of goroutines, each of which generates its 
    // expected share of the total random ints, retrieve the expected number 
    // of ints from the channel and put in target slice 
    for i := 0; i < numThreads; i++ { 
     go makeRandomNumbers(numIntsPerThread, ch) 
    } 
    for i := 0; i < numThreads; i++ { 
     multiThreadIntSlice[i] = <-ch 
    } 
    elapsedMultiRun := time.Since(startMultiRun) 
    fmt.Printf("Multi-threaded run took %s\n", elapsedMultiRun) 
    //To avoid not used warning 
    fmt.Print(len(singleThreadIntSlice)) 
} 

func makeRandomNumbers(numInts int, ch chan []int) { 
    source := rand.NewSource(time.Now().UnixNano()) 
    generator := rand.New(source) 
    result := make([]int, numInts) 
    for i := 0; i < numInts; i++ { 
     result[i] = generator.Intn(numInts * 100) 
    } 
    ch <- result 
} 
+0

私は "多次元スライス"を "スライススライス"に置き換えます:ゴーは多次元スライスを持っていませんので、それらは存在するふりをしません;-) – kostix

+0

@kostix確かに、更新された:) – Ankur