2016-10-09 8 views
1

は、私は私のアプリ長いサイクルブロックアプリケーション

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 
} 

に次のサイクルをHVEと虹のホイールは、サイクル実行中に表示されます。そのアプリをどのように管理できるかは、まだUIアクションに応答していますか? 注サイクルが実行されている間に、更新されていないコードの別の部分でNSProgressIndicatorが更新されました(進行状況は表示されません)。 私はそれがディスパッチと何か関係があると疑っていますが、私はそれにはかなり "グリーン"です。私はどんな助けにも感謝します。おかげさまで

+1

長い計算をバックグラウンドで実行する必要があります。 – rmaddy

+0

完全には無関係ですが、 'Complex'計算のいくつかを' struct'/'class'に移すことをお勧めします。そして、より直感的なマンデルブロアルゴリズムになります:https://gist.github.com/robertmryan/ 91536bf75e46cbdaed92c37e99fdbe7d – Rob

答えて

3

何かを非同期でディスパッチするには、該当するキューのasyncを呼び出します。たとえば、このメソッドを変更してグローバルバックグラウンドキューで計算を行い、その結果をメインキューに戻すことができます。あなたは、あなたが計算が行われたときに、非同期メソッドを呼び出します完了ハンドラのクロージャを使用して、すぐに結果を返すからシフトすることを行う方法、で:

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) 
     } 
    } 
} 

そして、あなたはそうのようにそれを呼びたいです:

// 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. 

マーティンはあなたがマンデルブロ集合を計算していると推測しています。その場合、各点の計算をグローバルキューにディスパッチすることはお勧めできません(これらのグローバルキューはブロックをワーカースレッドにディスパッチしますが、それらのワーカースレッドはかなり制限されているため)。

これらのグローバルキューワーカースレッドをすべて使い果たしないようにするには、簡単な選択肢の1つは、個々のポイントを計算するルーチンから呼び出すことです。すべてのルーチンを繰り返し実行するルーチン全体をディスパッチします。

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))) 
    } 
} 

「メインスレッドをそれを降りる」と「ワーカースレッドを使用していない」の問題を解決しますが、今、私たちはあまりにも多くの労働者を使用してから振ってきました:バックグラウンドスレッドへの複雑な値1つのワーカースレッドのみを使用し、デバイスを完全に利用しないようにします。私たちは本当に多くの計算を並行して(ワーカースレッドを使い果たしていなくても)実行したいと思っています。

複雑な計算のためにforループを実行する場合、dispatch_apply(現在はSwift 3ではconcurrentPerformと呼ばれています)を使用することです。これはループのようなものですが、それぞれのループを同時に実行します(ただし、最後にすべての並列ループが終了するのを待ちます)。これを行うには、concurrentPerformと外側のforループを置き換える:

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))) 
    } 
} 

concurrentPerform(旧dispatch_applyとして知られている)、同時にそのループの様々な反復を実行しますが、それは自動的に機能の同時実行スレッド数を最適化しますあなたのデバイスの私のMacBook Proでは、単純なforループよりも4.8倍高速でした。ただし、私はまだ全体をグローバルキューに送ります(concurrentPerformは同期的に実行されるため、メインスレッドでは遅い同期計算を実行したくないため)。concurrentPerformは計算を並行して実行します。 forループで同時実行性を楽しむのに最適な方法です.GCDワーカースレッドを使い切ることはありません。ところで

mandelbrotset


、あなたがNSProgressIndicatorを更新していると述べました。理想的には、すべてのピクセルが処理されるたびに更新したいのですが、そうするとUIがバックログになり、これらの更新すべてに追いつくことができません。最終的な結果が遅くなり、UIがすべての進行状況インジケータの更新を把握できるようになります。

解決策は、進行状況の更新からUI更新を切り離すことです。各ピクセルが更新されたときにバックグラウンド計算で通知する必要がありますが、「OK、多くのピクセルが前回チェックした後に進捗状況を更新する」と効果的に表示されるたびに、進行状況インジケーターを更新する必要があります。それを行う手作業のテクニックは面倒ですが、GCDは非常に洗練されたソリューション、ディスパッチソース、具体的にはDispatchSourceUserDataAddを提供しています。

そして、進捗状況を更新ディスパッチソース用のイベントハンドラを設定します。

だから、派遣元とこれまでに処理されたどのように多くのピクセルを追跡するカウンタのプロパティを定義インジケーター:

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

そして、あなたはピクセルを処理として、次のことができ、単にaddバックグラウンドスレッドからあなたの元へ:

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) 
    } 
} 

これを行うと、できるだけ頻度の高いUIが更新されますが、更新キューではバックログに記録されることはありません。ディスパッチソースはこれらのメッセージを統合します。

+2

OPは "マンデルブロセット" Mを計算/描画したいと思っていますし、最後の値ではなく、反復回数だけ必要です。 f(z)= z^2 + cの繰り返しの下で、z = 0の軌道が有界のままであれば、点cはMに属します。特定のディスクを残すために必要な反復回数は、Mの補色に色を付けるためによく使用されます。 –

+0

@ MartinR - ああ、あなたが正しいと確信しています。しかし、その場合、各計算をグローバルスレッドにディスパッチすることは、おそらく良い考えではありません。なぜなら、彼は利用可能なワーカースレッドをすべて使い果たしてしまうからです。並行性をレベルアップさせ、 'dispatch_apply'(Swift 3では' concurrentPerform'とも呼ばれます)を使用してある程度の並行性を楽しむ方が良いでしょう。私はそれに応じて私の答えを更新しました。 – Rob

+1

非常にいいですが(私は一度だけアップアップできます:) - Btw、 'dispatch_apply'には' queue'パラメータがあります。 'concurrentPerform'がどのキューにディスパッチしますか?これらの質問http://stackoverflow.com/questions/39590128/what-is-equivalent-of-dispatch-apply-in-swift-3、http://stackoverflow.com/questions/39590897/swift-3-conversion doそれは私の意見ではっきりと答えることはできませんが、おそらく私は何かを見落としています。 –