ใช้ Grand Central Dispatch ใน Swift เพื่อขนานและเร่งความเร็วลูป "สำหรับ" หรือไม่

ฉันกำลังพยายามคิดว่าจะใช้ GCD อย่างไรเพื่อขนานและเร่งความเร็วการจำลองมอนติคาร์โล มีการนำเสนอตัวอย่างง่ายๆ ส่วนใหญ่/ทั้งหมดสำหรับ Objective C และฉันต้องการตัวอย่างง่ายๆ สำหรับ Swift เนื่องจาก Swift เป็นภาษาโปรแกรม "จริง" ภาษาแรกของฉัน

เวอร์ชันการทำงานขั้นต่ำของการจำลอง Monte Carlo ใน Swift จะเป็นดังนี้:

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)

รหัสทำงานโดยตรงวางในโครงการโปรแกรมบรรทัดคำสั่งใน xcode 6.1

ลูปด้านในสุดไม่สามารถขนานได้เนื่องจากค่าใหม่ของตัวแปร "จุด" จะถูกนำมาใช้ในการวนซ้ำถัดไป แต่ส่วนนอกสุดเพียงรันการจำลองด้านในสุด 1000,000 ครั้งและนับรวมผลลัพธ์ และควรเป็นตัวเลือกที่เหมาะสมที่สุดสำหรับการทำขนาน

ดังนั้นคำถามของฉันคือจะใช้ GCD เพื่อขนานส่วนนอกสุดของ for loop ได้อย่างไร


person user2523167    schedule 24.11.2014    source แหล่งที่มา


คำตอบ (2)


"การวนซ้ำแบบมัลติเธรด" สามารถทำได้ด้วย 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 {
       // ...
    }
}

(คุณต้องหาความสัมพันธ์ที่ดีที่สุดระหว่างจำนวนภายนอกและภายใน)

มีสองสิ่งที่ควรสังเกต:

แล้วจะมีลักษณะประมาณนี้:

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
ไม่รู้เกี่ยวกับ arc4random()! โปรดใช้ความระมัดระวังในการแก้ไขอาร์เรย์จากหลายเธรด - ดูคำถาม & คำตอบนี้สำหรับข้อมูลเพิ่มเติม: stackoverflow.com/questions/26693838/ - person Nate Cook; 24.11.2014
comment
ฉันคิดว่าคำตอบที่สองของฉัน (ไม่ใช่คำตอบที่ยอมรับ) สำหรับ Q นั้นคือ Swiftiest เนื่องจากได้รับบัฟเฟอร์หน่วยความจำที่อยู่ติดกันอย่างชัดเจนสำหรับอาเรย์ - person Nate Cook; 24.11.2014
comment
@NateCook: ฉันรู้สึกแล้วว่าเคยเห็นอะไรแบบนั้นแต่หาไม่เจอ ขอบคุณสำหรับการอ้างอิงและวิธีแก้ปัญหาของคุณ (+1) ฉันได้อัปเดตคำตอบตามนั้นแล้ว - person Martin R; 24.11.2014
comment
ขอบคุณ. โดยเฉพาะอย่างยิ่งสำหรับการใช้ตัวอย่างง่ายๆของฉัน ด้วยวิธีนี้ฉันอาจมีโอกาสเข้าใจสิ่งที่เกิดขึ้น แทบรอไม่ไหวที่จะลองในภายหลังวันนี้ มีคำถามหนึ่งข้อ: คุณกำลังแบ่ง "ลูป" 1000,000 รายการออกเป็นส่วนๆ ก่อนที่จะเข้าคิว นั่นเป็นการหลีกเลี่ยงค่าใช้จ่ายมากเกินไปหรือไม่? ในโค้ดที่ฉันใช้จริง ๆ แล้ววงในสุดมีการจัดการตัวแปรหลายตัวมากกว่ามาก นั่นจะส่งผลต่อวิธีการแบ่ง 1000,000 ลูปเดิมหรือไม่ หรือเป็นปัจจัยที่สำคัญกว่าว่าคุณป้อน "ชิ้น" จำนวนเท่าใดในคิว - person user2523167; 24.11.2014
comment
@ user2523167: ดู dispatch_apply(3): บางครั้ง เมื่อบล็อกส่งผ่านไปยัง Dispatch_apply() เป็นเรื่องง่าย การใช้ Striding สามารถปรับประสิทธิภาพได้ การคำนวณก้าวย่างที่เหมาะสมที่สุดถือเป็นการทดลองที่ดีที่สุด ... - person Martin R; 24.11.2014
comment
ใครช่วยกรุณาแสดงเวอร์ชั่น Swift 3 นี้หน่อยได้ไหม มันน่าสับสนมากเพราะคุณไม่สามารถบอกคิวใดคิวหนึ่งให้ดำเนินการพร้อมกันได้ และโค้ดของฉันไม่ทำงาน - person Richard Birkett; 18.12.2016
comment
@RichardBirkett: ใช่ ฉันก็สงสัยเกี่ยวกับเรื่องนั้นเช่นกัน ดูความคิดเห็นตามคำตอบนี้ stackoverflow.com/a/39949292/1187415 - person Martin R; 18.12.2016
comment
@MartinR ขอบคุณ! ดังนั้นมันควรจะทำงานได้ดี และมันจะเลือกคิวโดยอิสระจากคิวที่ถูกส่งเข้ามา ดังนั้นหากฉันไม่ต้องการให้กระบวนการที่เกิดขึ้นพร้อมกันบล็อกเธรด ฉันสามารถส่งแบบอะซิงก์ไปยังคิวที่มีลำดับความสำคัญและเนื้อหาได้ จะยังคงอยู่ในคิวสำคัญหรือไม่? ไม่ว่ารหัสของฉันจะยังใช้งานไม่ได้ :( - person Richard Birkett; 18.12.2016

เพียงเพื่ออัปเดตสิ่งนี้สำหรับไวยากรณ์ร่วมสมัย ตอนนี้เราใช้ concurrentPerform (ซึ่งแทนที่ dispatch_apply)

เราจึงสามารถทดแทนได้

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

กับ

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

โปรดทราบว่าการขนานจะทำให้เกิดค่าใช้จ่ายเล็กน้อย ทั้งในกลไกการจัดส่ง GCD พื้นฐาน รวมถึงการซิงโครไนซ์ผลลัพธ์ หากคุณมีการวนซ้ำ 32 ครั้งในการวนซ้ำขนาน สิ่งนี้อาจไม่สำคัญ แต่คุณมีการวนซ้ำเป็นล้านครั้ง และจะเริ่มรวมกัน

โดยทั่วไปเราแก้ปัญหานี้ด้วยการ "ก้าว": แทนที่จะขนานการวนซ้ำ 1 ล้านครั้ง คุณอาจทำซ้ำแบบคู่ขนานได้เพียง 100 ครั้ง โดยแต่ละครั้งทำซ้ำได้ 10,000 ครั้ง เช่น. สิ่งที่ต้องการ:

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