2015-12-09 17 views
5

ho manipolato array di byte a Swift 2.1 ultimamente, e spesso mi ritrovo a scrivere codice come questo:multithread programmazione funzionale a Swift

// code to add functions to a [UInt8] object 
extension CollectionType where Generator.Element == UInt8 { 

    func xor(with byte: UInt8) -> [UInt8] { 
     return map { $0^byte } 
    } 
} 

// example usage: [67, 108].xor(with: 0) == [67, 108] 

C'è un modo semplice per parallelizzare questo map chiamata, in modo che più thread possono operare contemporaneamente su aree non sovrapposte dell'array?

Potrei scrivere il codice per dividere manualmente la matrice in sub-array e chiamare map su ogni sotto-matrice in thread distinti. Ma mi chiedo se esiste qualche framework in Swift per fare automaticamente la divisione, dal momento che map è una chiamata funzionale che può funzionare in un ambiente thread-safe senza effetti collaterali.

note Chiarire:

  1. Il codice ha solo bisogno di lavorare su un oggetto [UInt8], non necessariamente ogni CollectionType.
  2. Sto scrivendo questo codice solo per imparare Swift, non per alcun tipo di input nel mondo reale. Dopotutto, xor funziona in modo estremamente rapido senza richiedere alcun tipo di parallelizzazione.
  3. Se assolutamente necessario, la funzione di cui sopra può essere vista nel contesto here, dove viene utilizzato per risolvere Set 1, Sfida 3 di Matasano Crypto Challenges.

risposta

7

Il modo più semplice per eseguire un ciclo di calcoli in parallelo è concurrentPerform (precedentemente chiamato dispatch_apply, vedi Performing Loop Iterations Concurrently nella Guida di programmazione Concurrency). Ma no, non esiste una resa map che faccia questo per te. Devi farlo da solo.

Ad esempio, si potrebbe scrivere una proroga per eseguire le attività concomitanti:

extension Array { 

    public func concurrentMap<T>(_ transform: (Element) -> T) -> [T] { 
     var results = [Int: T]() 

     let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".sync", attributes: .concurrent) 

     DispatchQueue.concurrentPerform(iterations: count) { index in 
      let result = transform(self[index]) 
      queue.async { results[index] = result } 
     } 

     return queue.sync(flags: .barrier) { 
      (0 ..< results.count).map { results[$0]! } 
     } 
    } 

} 

Nota:

  • Questo bloccherà il thread si chiama da (proprio come il non-concorrente map will), quindi assicurati di inviarlo a una coda in background.

  • Uno ha bisogno di garantire che ci sia abbastanza lavoro su ogni thread per giustificare il sovraccarico inerente della gestione di tutti questi thread. (Ad esempio, una semplice chiamata xor per ciclo non è sufficiente, e scoprirai che è in realtà più lenta della resa non simultanea.) In questi casi, assicurati di avanzare (vedi Improving Loop Code che bilancia la quantità di lavoro per blocco simultaneo) . Ad esempio, anziché eseguire iterazioni di 5000 un'operazione estremamente semplice, eseguire 10 iterazioni di 500 operazioni per ciclo. Potrebbe essere necessario sperimentare con adeguati valori di passo.


Mentre ho il sospetto che non è necessario questa discussione, per i lettori non hanno familiarità con concurrentPerform (precedentemente noto come dispatch_apply), ti illustrano il suo utilizzo qui di seguito. Per una discussione più completa sull'argomento, fare riferimento ai collegamenti sopra.

Per esempio, prendiamo in considerazione qualcosa di molto più complicato di una semplice xor (perché con qualcosa di così semplice, il sovraccarico supera qualsiasi prestazione maturata), come ad esempio un'implementazione di Fibonacci ingenua:

func fibonacci(_ n: Int) -> Int { 
    if n == 0 || n == 1 { 
     return n 
    } 
    return fibonacci(n - 1) + fibonacci(n - 2) 
} 

Se si ha avuto un array di Int valori per i quali si voleva calcolare, piuttosto che:

let results = array.map { fibonacci($0) } 

si potrebbe:

012.351.641.061.
var results = [Int](count: array.count, repeatedValue: 0) 
DispatchQueue.concurrentPerform(iterations: array.count) { index in 
    let result = self.fibonacci(array[index]) 
    synchronize.update { results[index] = result }  // use whatever synchronization mechanism you want 
} 

Oppure, se si vuole una resa funzionale, è possibile utilizzare tale extension ho definito sopra:

let results = array.concurrentMap { fibonacci($0) } 

Per Swift 2 rendition, vedere previous revision of this answer.

+0

Come ho già detto nella risposta ora cancellata, ho fatto un po 'di benchmarking. È leggermente più veloce se dividi l'array in x slice dove x = 'NSProcessInfo(). ActiveProcessorCount * 4'. Ogni slice ha il proprio 'dispatch_apply' –

Problemi correlati