2014-11-01 26 views
10

Tôi có một mảng lớn mà tôi muốn xử lý bằng cách giao lát của nó cho một vài nhiệm vụ không đồng bộ. Như một bằng chứng của khái niệm, tôi đã viết các đoạn mã sau:Xử lý mảng song song bằng cách sử dụng GCD

class TestParallelArrayProcessing { 
    let array: [Int] 
    var summary: [Int] 

    init() { 
     array = Array<Int>(count: 500000, repeatedValue: 0) 
     for i in 0 ..< 500000 { 
      array[i] = Int(arc4random_uniform(10)) 
     } 
     summary = Array<Int>(count: 10, repeatedValue: 0) 
    } 

    func calcSummary() { 
     let group = dispatch_group_create() 
     let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) 

     for i in 0 ..< 10 { 
      dispatch_group_async(group, queue, { 
       let base = i * 50000 
       for x in base ..< base + 50000 { 
        self.summary[i] += self.array[x] 
       } 
      }) 
     } 
     dispatch_group_notify(group, queue, { 
      println(self.summary) 
     }) 
    } 
} 

Sau init(), array sẽ được khởi tạo với số nguyên ngẫu nhiên giữa 0 và 9.

Chức năng calcSummary công văn 10 công việc mà mất khối rời nhau trong số 50000 mục từ array và thêm chúng lên, sử dụng vị trí tương ứng của chúng trong summary làm trình bổ sung.

Chương trình này gặp sự cố tại đường dây self.summary[i] += self.array[x]. Lỗi này là:

EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP). 

tôi có thể thấy, trong trình gỡ lỗi, rằng nó đã cố gắng lặp một vài lần trước khi đâm, và rằng các biến, tại thời điểm vụ tai nạn, có giá trị trong phạm vi giới hạn chính xác.

Tôi đã đọc rằng EXC_I386_INVOP có thể xảy ra khi cố gắng truy cập một đối tượng đã được phát hành. Tôi tự hỏi liệu điều này có liên quan gì đến Swift khi tạo một bản sao của mảng nếu nó được sửa đổi, và nếu có thì làm thế nào để tránh nó.

Trả lời

5

Đây là cách khác biệt một chút về cách tiếp cận trong câu trả lời của @ Eduardo, sử dụng phương thức withUnsafeMutableBufferPointer<R>(body: (inout UnsafeMutableBufferPointer<T>) -> R) -> R của Array. That method's documentation states:

Gọi body(p), nơi p là một con trỏ để lưu trữ tiếp giáp có thể thay đổi các 's Array. Nếu không có lưu trữ như vậy tồn tại, nó được tạo ra đầu tiên.

Thông thường, trình tối ưu hóa có thể loại bỏ kiểm tra giới hạn và duy nhất trong thuật toán mảng, nhưng khi không thành công, hãy gọi cùng một thuật toán trên đối số body cho phép bạn giao dịch an toàn cho tốc độ.

Đó đoạn thứ hai dường như được chính xác những gì đang xảy ra ở đây, vì vậy sử dụng phương pháp này có thể được nhiều hơn "thành ngữ" trong Swift, bất cứ điều gì đó có nghĩa là:

func calcSummary() { 
    let group = dispatch_group_create() 
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) 

    self.summary.withUnsafeMutableBufferPointer { 
     summaryMem -> Void in 
     for i in 0 ..< 10 { 
      dispatch_group_async(group, queue, { 
       let base = i * 50000 
       for x in base ..< base + 50000 { 
        summaryMem[i] += self.array[x] 
       } 
      }) 
     } 
    } 

    dispatch_group_notify(group, queue, { 
     println(self.summary) 
    }) 
} 
3

Khi bạn sử dụng toán tử +=, LHS là tham số inout - Tôi nghĩ bạn đang nhận được điều kiện chủng tộc khi, như bạn đề cập trong bản cập nhật, Swift di chuyển quanh mảng để tối ưu hóa. Tôi đã có thể để làm cho nó hoạt động bằng cách tổng hợp các đoạn trong một biến địa phương, sau đó chỉ cần gán cho các chỉ số ngay trong summary:

func calcSummary() { 
    let group = dispatch_group_create() 
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) 

    for i in 0 ..< 10 { 
     dispatch_group_async(group, queue, { 
      let base = i * 50000 
      var sum = 0 
      for x in base ..< base + 50000 { 
       sum += self.array[x] 
      } 
      self.summary[i] = sum 
     }) 
    } 

    dispatch_group_notify(group, queue, { 
     println(self.summary) 
    }) 
} 
+1

Cảm ơn. Tôi tự hỏi nếu điều này thực sự hoạt động, hoặc nếu nó là do cơ hội bởi vì chỉ có mười bài tập (mã ban đầu của tôi có thể lặp lại một vài lần trước khi bị rơi). Tôi sẽ thử với một mảng 'tóm tắt' lớn hơn và xem điều gì xảy ra. – Eduardo

+0

Thú vị ... dựa trên mã của bạn, tôi đã tăng các tác vụ lên 100 và nó vẫn hoạt động. Vấn đề của tôi là mã tôi đăng là đơn giản hóa những gì tôi cần, và, trong cuộc sống thực, tôi thực sự cần phải tích lũy trên 'tóm tắt'. Tôi sẽ cố gắng giải quyết nó bằng văn bản cho bộ nhớ trực tiếp. – Eduardo

+0

Làm việc trực tiếp với bộ nhớ 'tóm tắt' để giải quyết vấn đề. Tôi thực sự nhớ là có thể vượt qua mảng bằng cách tham khảo! – Eduardo

2

Tôi nghĩ Nate là đúng: có điều kiện chủng tộc với biến summary. Để khắc phục sự cố, tôi đã sử dụng trực tiếp bộ nhớ của summary:

func calcSummary() { 
    let group = dispatch_group_create() 
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) 

    let summaryMem = UnsafeMutableBufferPointer<Int>(start: &summary, count: 10) 

    for i in 0 ..< 10 { 
     dispatch_group_async(group, queue, { 
      let base = i * 50000 
      for x in base ..< base + 50000 { 
       summaryMem[i] += self.array[x] 
      } 
     }) 
    } 

    dispatch_group_notify(group, queue, { 
     println(self.summary) 
    }) 
} 

Tác phẩm này (cho đến nay).

EDIT Mike S có một điểm rất tốt, trong bình luận bên dưới. Tôi cũng đã tìm thấy this blog post, điều này làm sáng tỏ vấn đề.

+0

BTW, bạn đã đo điểm chuẩn này so với phương pháp 'Array ' chưa? Lý do tôi hỏi là tôi nhận thấy việc thao tác rộng rãi các pixel trong bộ đệm 30mb pixel nhanh hơn 100 lần khi sử dụng con trỏ bộ nhớ (như ở đây) thay vì sử dụng kỹ thuật 'Array '. Tôi chỉ tự hỏi nếu bạn đã nhìn thấy các vấn đề tương tự ... – Rob

+0

@Rob: Tôi chưa đánh giá nó, nhưng nó chạy khá nhanh. Không chắc chắn nếu nhanh hơn nhiều so với Array với tối ưu hóa đầy đủ và không có kiểm tra ràng buộc. – Eduardo

+2

Tôi không nghĩ rằng có bất kỳ đảm bảo rằng bộ nhớ 'Array' là tiếp giáp, vì vậy bạn có thể dễ dàng ghi vào một vị trí bộ nhớ không hợp lệ bằng cách sử dụng phương pháp này. Nó sẽ hoạt động nếu 'summary' là một [' ContiguousArray'] (http://swifter.natecook.com/type/ContiguousArray/). –

1

Bạn cũng có thể sử dụng concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void) (kể từ Swift 3).

Nó có một cú pháp đơn giản hơn nhiều:

DispatchQueue.concurrentPerform(iterations: iterations) {i in 
     performOperation(i) 
} 

và sẽ chờ đợi cho tất cả các chủ đề để hoàn thiện trước khi trở về.

Các vấn đề liên quan