修正された回答
以下の私の最初の回答では、2 種類のパフォーマンスの改善に取り組んでいます。(2)複雑な計算をマルチスレッド化することで、より高速に実行できます(ただし、これは少し複雑なので注意してください)。
振り返ってみると、移動平均を行っていることがわかりました。そのため、ネストされたfor
ループを実行することによるパフォーマンスの低下は完全に排除され、ゴーディアン ノットが切断されます。擬似コードを使用すると、次のようなことができます。これはsum
、最初のポイントを削除し、次のポイントを追加することで を更新します (ここn
で、移動平均で平均しているポイントの数を表します。たとえば、30 ポイントの移動平均)あなたの大きなセットから、n
30です):
double sum = 0.0;
for (NSInteger i = 0; i < n; i++)
{
sum += originalDataPoints[i];
}
movingAverageResult[n - 1] = sum / n;
for (NSInteger i = n; i < totalNumberOfPointsInOriginalDataSet; i++)
{
sum = sum - originalDataPoints[i - n] + originalDataPoints[i];
movingAverageResult[i] = sum / n;
}
これにより、これは線形の複雑さの問題になり、はるかに高速になるはずです。アルゴリズムをマルチスレッドで実行するために、これをいくつかのキューに追加する複数の操作に分割する必要はありません(たとえば、以下のポイント 2 で警告する複雑さを回避できるため、これは素晴らしいことです)。ただし、必要に応じて、このアルゴリズム全体をディスパッチ/操作キューに追加する単一の操作としてラップして、ユーザー インターフェイスとは非同期で実行することができます (以下のポイント 1)。
元の答え
あなたの質問から、パフォーマンスの問題が何であるかは完全には明らかではありません。パフォーマンスの問題には、次の 2 つのクラスがあります。
ユーザー インターフェイスの応答性: UI の応答性が気になる場合はwaitUntilAllOperationsAreFinished
、1 日の終わりに UI に対して計算を同期させるため、絶対に削除する必要があります。ユーザー インターフェイスの応答性に対処しようとしている場合は、(a)for
ループ内の操作ブロックを削除します。ただし、(b) これら 2 つのネストされたfor
ループを、バックグラウンド キューに追加する 1 つのブロック内にラップします。これを大まかに見ると、コードは次のようになります。
[queue addOperationWithBlock:^{
// do all of your time consuming stuff here with
// your nested for loops, no operations dispatched
// inside the for loop
// when all done
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// now update your UI
}];
}];
ここには電話をかけないwaitUntilAllOperationsAreFinished
でください。レスポンシブ ユーザー インターフェイスの目標は、非同期で実行するwaitUntil...
ことです。メソッドを効果的に使用すると、レスポンシブ UI の敵である同期になります。
または、同等の GCD を使用できます。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do all of your time consuming stuff here
// when all done
dispatch_async(dispatch_get_main_queue(), ^{
// now update your UI
});
});
繰り返しますが、このコードをバックグラウンドに確実にディスパッチするためにdispatch_async
呼び出しています (これは、 を呼び出さないことを確認するのと同じです)。waitUntilAllOperationsAreFinished
これを行うと、これを行うメソッドはほぼ瞬時に戻り、この操作中に UI が途切れたりフリーズしたりしなくなります。この操作が完了すると、それに応じて UI が更新されます。
これは、一連の個別のバックグラウンド操作を送信するのではなく、単一の操作でこれをすべて実行していることを前提としていることに注意してください。この単一の操作をバックグラウンドに送信するだけで、複雑な計算が実行され、完了するとユーザー インターフェイスが更新されます。それまでの間、ユーザー インターフェイスは応答性を維持できます (ユーザーに他のことをさせるか、それが意味をなさない場合は、ユーザーUIActivityIndicatorView
にスピナーを表示して、アプリが特別なことを行っていることをユーザーに知らせます)。すぐに戻ってきます)。
ただし、UI が (一時的であっても) フリーズするようなものは、優れたデザインではないということを忘れてはなりません。また、既存のプロセスに十分な時間がかかる場合は、ウォッチドッグ プロセスによってアプリが強制終了される可能性があることに注意してください。Apple の助言は、少なくとも数百ミリ秒以上かかる場合は、非同期で行うべきだということです。また、UI が同時に他の処理を実行しようとしている場合 (たとえば、アニメーション、スクロール ビューなど)、数百ミリ秒でも長すぎます。
計算自体をマルチスレッド化してパフォーマンスを最適化する: マルチスレッド化することで、より根本的なパフォーマンスの問題に取り組もうとしている場合は、その方法についてさらに注意を払う必要があります。
まず、並行操作の数を合理的な数に制限したいと思うでしょう (利用可能なスレッドをすべて使い果たす危険を冒したくはありません)。maxConcurrentOperationCount
小さくて妥当な数 (たとえば、4 または 6 など) に設定することをお勧めします。デバイスで使用できるコアの数が限られているため、とにかくその時点で収益が減少します。
minY
2 つ目は、同じように重要なことですが、操作外の変数 ( 、maxY
など)の更新の同期に特別な注意を払う必要があります。maxY
現在100
があり、2 つの同時操作があるとします。1 つは に設定しようとしており300
、もう 1 つは に設定しようとしてい200
ます。しかし、両者が現在の値 よりも大きいことを確認し、100
値の設定に進むと、設定している方が300
たまたまレースに勝った場合、もう一方の操作で値を にリセットして、値200
を吹き飛ばす可能性があります。 300
.
同じ変数を更新する別々の操作で並行コードを書きたい場合は、これらの外部変数の同期について非常に慎重に考える必要があります。この問題に対処するためのさまざまなロック メカニズムについては、「スレッド プログラミング ガイド」の「同期」セクションを参照してください。または、Concurrency Programming GuideのEliminating Lock-Based Codeで説明されているように、値を同期するための別の専用シリアル キューを定義することもできます。
最後に、同期について考えるときは、いつでも一歩下がって、これらの変数のすべての同期を実行するコストが本当に必要かどうかを自問することができます (競合の問題がなくても、同期時にパフォーマンスが低下するため)。 . たとえば、直観に反するように思えるかもしれませんが、これらの操作中に更新を試行しない方が高速であり、同期の必要がなくなる場合がありますminY
。maxY
の範囲のこれらの 2 つの変数の計算を忘れることができます。y
ただし、すべての操作が完了するまで待ってから、結果セット全体に対して最後の反復を 1 回実行し、最小値と最大値を計算します。これは経験的に検証できるアプローチであり、ロック (または他の同期方法) の両方で試してから、ロックが不要な最後の単一の操作として値の範囲を計算することをお勧めします。 . 驚くべきことに、最後に追加のループを追加すると (同期の必要がなくなるため)、処理が速くなることがあります。
肝心なのは、一般に、これらの両方の考慮事項に特別な注意を払わずに、一連のコードを取得して同時実行することはできず、消費するスレッド数を制限し、同じものを更新するかどうかを制限することです。複数の操作から変数を取得する場合は、値を同期する方法を検討してください。そして、この 2 番目の問題であるマルチスレッド計算自体に取り組むことにしたとしても、最初の問題であるレスポンシブ UI について考え、おそらく両方の方法を組み合わせる必要があります。