1

長い計算 (ネストされた 2 つの for ループを使用) を高速化する方法を探しています。その結果はプロットに表示されます。内側の for ループのそれぞれが同時に実行されると考えて、NSOperationQueue を試しました。しかし、少なくとも私の実装では、そうではないようです。NSOperationQueue 呼び出しを削除すると、プロットに結果が表示されるので、計算が適切に行われていることがわかります。

コード スニペットを次に示します。

    NSInteger half_window, len;

    len = [myArray length];

    if (!len)
        return;

    NSOperationQueue    *queue = [[NSOperationQueue alloc] init];

    half_window = 0.5 * (self.slidingWindowSize - 1);
    numberOfPoints = len - 2 * half_window;

    double __block minY = 0;
    double __block maxY = 0;
    double __block sum, y;

    xPoints = (double *) malloc (numberOfPoints * sizeof(double));
    yPoints = (double *) malloc (numberOfPoints * sizeof(double));

    for ( NSUInteger i = half_window; i < (len - half_window); i++ )
    {
        [queue addOperationWithBlock: ^{

        sum = 0.0;

        for ( NSInteger j = -half_window; j <= half_window; j++ )
        {
            MyObject *mo = [myArray objectAtIndex: (i+j)];
            sum += mo.floatValue;
        }

        xPoints[i - half_window] = (double) i+1;

        y = (double) (sum / self.slidingWindowSize);
        yPoints[i - half_window] = y;

        if (y > maxY)
            maxY = y;

        if (y < minY)
            minY = y;
        }];

        [queue waitUntilAllOperationsAreFinished];
    }

    // update my core-plot
    self.maximumValueForXAxis = len;
    self.minimumValueForYAxis = floor(minY);
    self.maximumValueForYAxis = ceil(maxY);

    [self setUpPlotSpaceAndAxes];
    [graph reloadData];

    // cleanup
    free(xPoints);
    free(yPoints);

これをより速く実行する方法はありますか?

4

3 に答える 3

4

各アイテムを追加した後、キュー内のすべての操作が完了するのを待っています。

[queue waitUntilAllOperationsAreFinished];
}

// update my core-plot
self.maximumValueForXAxis = len;

する必要があります

}
[queue waitUntilAllOperationsAreFinished];


// update my core-plot
self.maximumValueForXAxis = len;

sumまた、各オペレーション キュー ブロックで variable を 0.0 に設定しています。

于 2013-05-06T22:42:33.393 に答える
2

修正された回答

以下の私の最初の回答では、2 種類のパフォーマンスの改善に取り組んでいます。(2)複雑な計算をマルチスレッド化することで、より高速に実行できます(ただし、これは少し複雑なので注意してください)。

振り返ってみると、移動平均を行っていることがわかりました。そのため、ネストされたforループを実行することによるパフォーマンスの低下は完全に排除され、ゴーディアン ノットが切断されます。擬似コードを使用すると、次のようなことができます。これはsum、最初のポイントを削除し、次のポイントを追加することで を更新します (ここnで、移動平均で平均しているポイントの数を表します。たとえば、30 ポイントの移動平均)あなたの大きなセットから、n30です):

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 つのクラスがあります。

  1. ユーザー インターフェイスの応答性: 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 が同時に他の処理を実行しようとしている場合 (たとえば、アニメーション、スクロール ビューなど)、数百ミリ秒でも長すぎます。

  2. 計算自体をマルチスレッド化してパフォーマンスを最適化する: マルチスレッド化することで、より根本的なパフォーマンスの問題に取り組もうとしている場合は、その方法についてさらに注意を払う必要があります。

    • まず、並行操作の数を合理的な数に制限したいと思うでしょう (利用可能なスレッドをすべて使い果たす危険を冒したくはありません)。maxConcurrentOperationCount小さくて妥当な数 (たとえば、4 または 6 など) に設定することをお勧めします。デバイスで使用できるコアの数が限られているため、とにかくその時点で収益が減少します。

    • minY2 つ目は、同じように重要なことですが、操作外の変数 ( 、maxYなど)の更新の同期に特別な注意を払う必要があります。maxY現在100があり、2 つの同時操作があるとします。1 つは に設定しようとしており300、もう 1 つは に設定しようとしてい200ます。しかし、両者が現在の値 よりも大きいことを確認し、100値の設定に進むと、設定している方が300たまたまレースに勝った場合、もう一方の操作で値を にリセットして、値200を吹き飛ばす可能性があります。 300.

      同じ変数を更新する別々の操作で並行コードを書きたい場合は、これらの外部変数の同期について非常に慎重に考える必要があります。この問題に対処するためのさまざまなロック メカニズムについては、「スレッド プログラミング ガイド」の「同期」セクションを参照してください。または、Concurrency Programming GuideのEliminating Lock-Based Codeで説明されているように、値を同期するための別の専用シリアル キューを定義することもできます。

      最後に、同期について考えるときは、いつでも一歩下がって、これらの変数のすべての同期を実行するコストが本当に必要かどうかを自問することができます (競合の問題がなくても、同期時にパフォーマンスが低下するため)。 . たとえば、直観に反するように思えるかもしれませんが、これらの操作中に更新を試行しない方が高速であり、同期の必要がなくなる場合がありますminYmaxYの範囲のこれらの 2 つの変数の計算を忘れることができます。yただし、すべての操作が完了するまで待ってから、結果セット全体に対して最後の反復を 1 回実行し、最小値と最大値を計算します。これは経験的に検証できるアプローチであり、ロック (または他の同期方法) の両方で試してから、ロックが不要な最後の単一の操作として値の範囲を計算することをお勧めします。 . 驚くべきことに、最後に追加のループを追加すると (同期の必要がなくなるため)、処理が速くなることがあります。

    肝心なのは、一般に、これらの両方の考慮事項に特別な注意を払わずに、一連のコードを取得して同時実行することはできず、消費するスレッド数を制限し、同じものを更新するかどうかを制限することです。複数の操作から変数を取得する場合は、値を同期する方法を検討してください。そして、この 2 番目の問題であるマルチスレッド計算自体に取り組むことにしたとしても、最初の問題であるレスポンシブ UI について考え、おそらく両方の方法を組み合わせる必要があります。

于 2013-05-06T23:49:25.110 に答える
2

これは奇妙に見えます:

for ( NSUInteger j = -half_window; j <= half_window; j++ )

half_window が正であると仮定すると、 unsigned int を負の数に設定しています。これにより、このループが計算されないことを意味する条件に失敗する巨大な unsigned int が生成されると思われます。

ただし、これは速度低下の原因ではありません。

于 2013-05-06T22:36:48.967 に答える