Menggunakan Grand Central Dispatch di Swift untuk memparalelkan dan mempercepat loop "untuk"?

Saya mencoba memahami cara menggunakan GCD untuk memparalelkan dan mempercepat simulasi Monte Carlo. Sebagian besar/semua contoh sederhana disajikan untuk Objective C dan saya sangat membutuhkan contoh sederhana untuk Swift karena Swift adalah bahasa pemrograman “nyata” pertama saya.

Versi kerja minimal dari simulasi monte carlo di Swift akan menjadi seperti ini:

import Foundation

import Cocoa
var winner = 0
var j = 0
var i = 0
var chance = 0
var points = 0
for j=1;j<1000001;++j{
    var ability = 500

    var player1points = 0

    for i=1;i<1000;++i{
        chance = Int(arc4random_uniform(1001))
        if chance<(ability-points) {++points}
        else{points = points - 1}
    }
    if points > 0{++winner}
}
    println(winner)

Kode berfungsi langsung ditempelkan ke proyek program baris perintah di xcode 6.1

Perulangan terdalam tidak dapat diparalelkan karena nilai baru dari variabel “titik” digunakan pada perulangan berikutnya. Namun yang terluar hanya menjalankan simulasi terdalam 10.00000 kali dan menghitung hasilnya dan harus menjadi kandidat ideal untuk paralelisasi.

Jadi pertanyaan saya adalah bagaimana cara menggunakan GCD untuk memparalelkan loop for terluar?


person user2523167    schedule 24.11.2014    source sumber


Jawaban (2)


Sebuah "iterasi multi-utas" dapat dilakukan dengan dispatch_apply():

let outerCount = 100    // # of concurrent block iterations
let innerCount = 10000  // # of iterations within each block

let the_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(UInt(outerCount), the_queue) { outerIdx -> Void in
    for innerIdx in 1 ... innerCount {
       // ...
    }
}

(Anda harus mengetahui hubungan terbaik antara hitungan luar dan dalam.)

Ada dua hal yang perlu diperhatikan:

Maka kira-kira akan terlihat seperti ini:

let outerCount = 100     // # of concurrent block iterations
let innerCount = 10000   // # of iterations within each block

let the_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

var winners = [Int](count: outerCount, repeatedValue: 0)
winners.withUnsafeMutableBufferPointer { winnersPtr -> Void in

    dispatch_apply(UInt(outerCount), the_queue) { outerIdx -> Void in
        var seed = arc4random() // seed for rand_r() in this "thread"

        for innerIdx in 1 ... innerCount {
            var points = 0
            var ability = 500

            for i in 1 ... 1000 {
                let chance = Int(rand_r(&seed) % 1001)
                if chance < (ability-points) { ++points }
                else {points = points - 1}
            }
            if points > 0 {
                winnersPtr[Int(outerIdx)] += 1
            }
        }
    }
}

// Add results:
let winner = reduce(winners, 0, +)
println(winner)
person Martin R    schedule 24.11.2014
comment
Tidak tahu tentang arc4random()! Berhati-hatilah dalam memodifikasi array dari beberapa thread - lihat Tanya Jawab ini untuk mengetahui lebih lanjut: stackoverflow.com/questions/26693838/ - person Nate Cook; 24.11.2014
comment
Saya pikir jawaban kedua saya (bukan yang diterima) untuk Q adalah yang paling cepat, karena secara eksplisit mendapat buffer memori yang berdekatan untuk array. - person Nate Cook; 24.11.2014
comment
@NateCook: Saya merasa telah melihat sesuatu seperti itu, tetapi tidak dapat menemukannya. Terima kasih atas referensi dan solusi Anda (+1), saya telah memperbarui jawabannya. - person Martin R; 24.11.2014
comment
Terima kasih. Terutama untuk menggunakan contoh saya yang sangat sederhana. Dengan cara ini saya mungkin memiliki kesempatan untuk memahami apa yang sedang terjadi. Tidak sabar untuk mencobanya nanti hari ini. Namun ada satu pertanyaan: Anda membagi 10.00000 "loop" menjadi beberapa bagian sebelum mengantrinya. Apakah itu untuk menghindari terlalu banyak overhead? Dalam kode saya sebenarnya menggunakan loop terdalam memiliki lebih banyak manipulasi banyak variabel. Apakah hal itu akan mempengaruhi cara membagi 10.00000 loop asli atau apakah faktor yang lebih penting adalah berapa banyak “potongan” yang Anda masukkan ke antrean? - person user2523167; 24.11.2014
comment
@ pengguna2523167: Lihat dispatch_apply(3): Terkadang, ketika blok yang diteruskan ke pengiriman_apply() sederhana, penggunaan striding dapat menyesuaikan performa. Menghitung langkah optimal sebaiknya diserahkan pada eksperimen. ... - person Martin R; 24.11.2014
comment
Bisakah seseorang menunjukkan versi Swift 3 ini? Ini benar-benar membingungkan karena Anda tidak dapat memberi tahu antrian tertentu untuk melakukan performConcurrent dan tidak ada kode saya yang berfungsi. - person Richard Birkett; 18.12.2016
comment
@RichardBirkett: Ya, saya juga bertanya-tanya tentang hal itu. Lihat komentar setelah jawaban ini stackoverflow.com/a/39949292/1187415. - person Martin R; 18.12.2016
comment
@MartinR Terima kasih! Jadi itu seharusnya berfungsi dengan baik, dan ia akan memilih antriannya secara independen dari antrian apa yang dikirim. Jadi jika saya tidak ingin proses bersamaan memblokir utas, saya dapat mengirimkannya async ke antrian dengan prioritas dan konten apa pun akankah tetap berada di antrian prioritas? Apa pun yang terjadi, kode saya masih tidak berfungsi :( - person Richard Birkett; 18.12.2016

Hanya untuk memperbarui sintaksis kontemporer, kami sekarang menggunakan concurrentPerform (yang menggantikan dispatch_apply).

Jadi kita bisa menggantinya

for j in 0 ..< 1_000_000 {
    for i in 0 ..< 1000 {
        ...
    }
}

Dengan

DispatchQueue.concurrentPerform(1_000_000) { j in
    for i in 0 ..< 1000 {
        ...
    }
}

Perlu diperhatikan, paralelisasi menimbulkan sedikit overhead, baik dalam mekanisme pengiriman GCD dasar, maupun sinkronisasi hasilnya. Jika Anda memiliki 32 iterasi dalam loop paralel, ini tidak akan menjadi masalah, tetapi Anda memiliki satu juta iterasi, dan iterasi tersebut akan mulai bertambah.

Kami biasanya menyelesaikan masalah ini dengan “melangkah”: Daripada memparalelkan 1 juta iterasi, Anda mungkin hanya melakukan 100 iterasi paralel, yang masing-masing melakukan 10.000 iterasi. Misalnya. sesuatu seperti:

let totalIterations = 1_000_000
let stride = 10_000
let (quotient, remainder) = totalIterations.quotientAndRemainder(dividingBy: stride)
let iterations = quotient + remainder == 0 ? 0 : 1

DispatchQueue.concurrentPerform(iterations: iterations) { iteration in
    for j in iteration * stride ..< min(totalIterations, (iteration + 1) * stride) {
        for i in 0 ..< 1000 {
            ...
        }
    }
}
person Rob    schedule 09.05.2020