2016-10-09 2 views
1

je hve cycle suivant dans mon applicationblocs de long cycle d'application

var maxIterations: Int = 0 

func calculatePoint(cn: Complex) -> Int { 

    let threshold: Double = 2 
    var z: Complex = .init(re: 0, im: 0) 
    var z2: Complex = .init(re: 0, im: 0) 
    var iteration: Int = 0 

    repeat { 
     z2 = self.pow2ForComplex(cn: z) 
     z.re = z2.re + cn.re 
     z.im = z2.im + cn.im 
     iteration += 1 
    } while self.absForComplex(cn: z) <= threshold && iteration < self.maxIterations 

    return iteration 
} 

et la roue arc en ciel est montrant pendant l'exécution du cycle. Comment puis-je gérer cette application répond toujours aux actions de l'interface utilisateur? Remarque J'ai mis à jour NSProgressIndicator dans différentes parties du code qui ne sont pas mises à jour (la progression n'est pas affichée) pendant l'exécution du cycle. Je soupçonne que cela a quelque chose à voir avec la distribution, mais je suis assez "vert" avec ça. J'apprécie toute aide. Merci.

+1

Vous devez exécuter le calcul long en arrière-plan. – rmaddy

+0

Complètement sans rapport, mais je pourrais suggérer de déplacer certains des calculs 'Complex' dans votre' struct'/'class', et vous finiriez avec un algorithme de Mandelbrot plus intuitif: https://gist.github.com/robertmryan/ 91536bf75e46cbdaed92c37e99fdbe7d – Rob

Répondre

3

Pour envoyer de manière asynchrone, appelez async dans la file d'attente appropriée. Par exemple, vous pouvez modifier cette méthode pour effectuer le calcul dans une file d'attente globale, puis renvoyer le résultat dans la file d'attente principale. Soit dit en passant, quand vous faites cela, vous passez de retourner le résultat immédiatement à l'aide d'une fermeture de gestionnaire d'achèvement qui la méthode asynchrone fera appel lorsque le calcul est effectué:

func calculatePoint(_ cn: Complex, completionHandler: @escaping (Int) -> Void) { 
    DispatchQueue.global(qos: .userInitiated).async { 
     // do your complicated calculation here which calculates `iteration` 

     DispatchQueue.main.async { 
      completionHandler(iteration) 
     } 
    } 
} 

Et vous appeler comme ceci:

// start NSProgressIndicator here 

calculatePoint(point) { iterations in 
    // use iterations here, noting that this is called asynchronously (i.e. later) 

    // stop NSProgressIndicator here 
} 

// don't use iterations here, because the above closure is likely not yet done by the time we get here; 
// we'll get here almost immediately, but the above completion handler is called when the asynchronous 
// calculation is done. 

Martin a émis l'hypothèse que vous calculez un ensemble de Mandelbrot. Si c'est le cas, envoyer le calcul de chaque point dans une file d'attente globale n'est pas une bonne idée (car ces files d'attente globales envoient leurs blocs aux threads de travail, mais ces threads de travail sont assez limités). Si vous souhaitez éviter d'utiliser tous ces threads de travail de file d'attente globale, un choix simple consiste à supprimer l'appel async de votre routine qui calcule un point individuel et à distribuer la routine entière qui itère dans tous les threads. valeurs complexes à un fil de fond:

DispatchQueue.global(qos: .userInitiated).async { 
    for row in 0 ..< height { 
     for column in 0 ..< width { 
      let c = ... 
      let m = self.mandelbrotValue(c) 
      pixelBuffer[row * width + column] = self.color(for: m) 
     } 
    } 

    let outputCGImage = context.makeImage()! 

    DispatchQueue.main.async { 
     completionHandler(NSImage(cgImage: outputCGImage, size: NSSize(width: width, height: height))) 
    } 
} 

C'est résout les « obtenir sur le fil conducteur » et « ne pas utiliser les threads de travail » des problèmes, mais maintenant nous avons balancé d'utiliser trop de travailleurs threads, à utiliser uniquement un thread de travail, n'utilisant pas pleinement l'appareil. Nous voulons vraiment faire autant de calculs en parallèle (sans épuiser les threads de travail). Une approche, lorsque vous effectuez une boucle for pour des calculs complexes, consiste à utiliser dispatch_apply (maintenant appelé concurrentPerform dans Swift 3). Cela ressemble à une boucle for, mais elle exécute chacune des boucles simultanément les unes par rapport aux autres (mais, à la fin, attend que toutes ces boucles simultanées soient terminées). Pour ce faire, remplacez la boucle for extérieure avec concurrentPerform:

DispatchQueue.global(qos: .userInitiated).async { 
    DispatchQueue.concurrentPerform(iterations: height) { row in 
     for column in 0 ..< width { 
      let c = ... 
      let m = self.mandelbrotValue(c) 
      pixelBuffer[row * width + column] = self.color(for: m) 
     } 
    } 

    let outputCGImage = context.makeImage()! 

    DispatchQueue.main.async { 
     completionHandler(NSImage(cgImage: outputCGImage, size: NSSize(width: width, height: height))) 
    } 
} 

Le concurrentPerform (anciennement connu sous le nom dispatch_apply) effectuera les différentes itérations de cette boucle en même temps, mais il permettra d'optimiser automatiquement le nombre de threads simultanés pour les capacités de votre appareil. Sur mon MacBook Pro, cela a rendu le calcul 4,8 fois plus rapide que la simple boucle for. Remarque, je envoie toujours le tout dans une file d'attente globale (car concurrentPerform s'exécute de manière synchrone, et nous ne voulons jamais effectuer de calculs lents et synchrones sur le thread principal), mais concurrentPerform exécutera les calculs en parallèle. C'est une excellente façon de profiter de la simultanéité dans une boucle for de manière à ne pas épuiser les threads de travail GCD.

mandelbrotset


Par ailleurs, vous avez dit que vous mettez à jour NSProgressIndicator. Idéalement, vous voulez le mettre à jour au fur et à mesure que chaque pixel est traité, mais si vous le faites, l'interface utilisateur risque d'être retardée, incapable de suivre toutes ces mises à jour. Vous finirez par ralentir le résultat final pour permettre à l'interface utilisateur de rattraper toutes ces mises à jour des indicateurs de progression.

La solution consiste à découpler la mise à jour de l'interface utilisateur des mises à jour de progression. Vous voulez que les calculs d'arrière-plan vous indiquent que chaque pixel est mis à jour, mais que vous voulez que l'indicateur de progression soit mis à jour, en disant à chaque fois "ok, mettez à jour la progression avec le nombre de pixels calculé depuis la dernière vérification". Il y a des techniques manuelles lourdes pour le faire, mais GCD fournit une solution vraiment élégante, une source d'expédition, ou plus précisément, un DispatchSourceUserDataAdd.

donc définir les propriétés de la source d'expédition et un compteur de garder une trace de combien de pixels ont été traités jusqu'ici:

puis mis en place un gestionnaire d'événements pour la source d'envoi, qui met à jour les progrès indicateur:

source.setEventHandler() { [unowned self] in 
    self.pixelsProcessed += self.source.data 
    self.progressIndicator.doubleValue = Double(self.pixelsProcessed)/Double(width * height) 
} 
source.resume() 

Et puis, comme vous traitez les pixels, vous pouvez simplement add à votre source du fil de fond:

DispatchQueue.concurrentPerform(iterations: height) { row in 
    for column in 0 ..< width { 
     let c = ... 
     let m = self.mandelbrotValue(for: c) 
     pixelBuffer[row * width + column] = self.color(for: m) 
     self.source.add(data: 1) 
    } 
} 

Si vous faites cela, il mettra à jour l'interface utilisateur avec la plus grande fréquence possible, mais il ne sera jamais retardé avec une file d'attente de mises à jour. La source d'expédition fusionnera ces appels add pour vous.

+2

Il semble que OP souhaite calculer/dessiner le "jeu de Mandelbrot" M, et seulement le nombre d'itérations est nécessaire, pas la valeur finale. Un point c appartient à M si l'orbite de z = 0 sous itérations de f (z) = z^2 + c reste bornée. Le nombre d'itérations nécessaires pour laisser un certain disque est souvent utilisé pour colorer le complément de M. –

+0

@MartinR - Ah, je suis sûr que vous avez raison. Mais dans ce cas, l'envoi de chaque calcul à un thread global n'est probablement pas une bonne idée, car il peut épuiser tous les threads de travail disponibles. Il est probablement préférable de déplacer la concurrence vers le haut et d'utiliser 'dispatch_apply' (aka' concurrentPerform' dans Swift 3) pour profiter d'un degré de simultanéité. J'ai mis à jour ma réponse en conséquence. – Rob

+1

Très bien (mais je ne peux upvote qu'une seule fois :) - Btw, 'dispatch_apply' avait un paramètre' queue'. Dans quelle file d'attente 'concurrPerform' est-il envoyé? Ces questions http://stackoverflow.com/questions/39590128/what-is-equivalent-of-dispatch-apply-in-swift-3, http://stackoverflow.com/questions/39590897/swift-3-conversion faire Je ne réponds pas clairement (à mon avis), mais peut-être que je néglige quelque chose. –