1

Grand Central Dispatch (GCD) を学習し、次のコードを使用してテストします。

GCD の場合:

#include <dispatch/dispatch.h>
#include <vector>
#include <cstdlib>
#include <iostream>

int main(int argc, char *argv[])  
{
   const int N = atoi(argv[1]);
   __block std::vector<int> a(N, 0);
   dispatch_apply(N, 
     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
     ^(size_t i) 
     { 
       a[i] = i;
#ifdef DEBUG           
       if ( i % atoi(argv[2]) == 0)
         std::cout << a[i] <<  std::endl;
#endif
     });
  return 0;
}

GCD なし:

#include <vector>
#include <cstdlib>
#include <iostream> 

int main(int argc, char *argv[]) 
{
  const int N = atoi(argv[1]);
  std::vector<int> a(N, 0);
  for (int i = 0; i < N; i++)   
    {
      a[i] = i;
#ifdef DEBUG
      if ( i % atoi(argv[2]) == 0)
    std::cout << a[i] <<  std::endl;
#endif
    }
 return 0;
}

GCD でのテスト結果:

$ time ./testgcd 100000000 10000000
4.254 secs

GCD なしのテスト:

$ time ./nogcd 100000000 10000000
1.462 secs

GCD は実行時間を短縮するはずだと思っていましたが、結果は逆です。GCD を誤用しているかどうかはわかりません。OS環境はMac OS X 10.8 with Xcode 4.5です。コンパイラは Clang++ 3.1 です。ハードウェアは2コアのi5 CPUを搭載したMacbook Pro。

比較のために、OpenMP を使用します (同じラップトップで Xcode 4.5 に同梱されている GCC も使用します)。

#include <vector> 
#include <cstdlib>

int main(int argc, char *argv[])  
{
  const int N = atoi(argv[1]);
  std::vector <int> a(N, 0);
  #pragma omp parallel for
  for (int i = 0; i < N; i++)
    a[i] = i;
  return 0;
}

そして w/wo (-fopenmp)、テストする実行可能ファイルが 2 つあります。

-fopenmpコンパイル中のフラグ付き:

$ time ./testopenmp 100000000
1.280 secs

-fopenmpコンパイル中のフラグなし:

$ time ./testnoopenmp 100000000
1.626 secs

OpenMP を使用すると、実行時間が短縮されます。

4

2 に答える 2

7

GCD は必ずしも実行時間を増やす必要はありません。あなたの場合にそうする理由は、あなたが間違っているからです。そもそもアプリケーションが遅い理由を理解することが重要です。そこで、マルチコア プロファイラー (Instruments.app) でコードを実行したところ、次のようになりました。

マルチコア プロファイリングのスクリーンショット

ご覧のとおり、グラフはほとんど黄色です。黄色は、スレッドが何もせず、何らかのタスクの実行を待機していることを意味します。緑色は、タスクを実行していることを意味します。つまり、コードを書いた方法では、アプリケーションはその時間の 99% をタスクの受け渡しに費やし、各タスクの実行にはほとんど時間がかからず、オーバーヘッドが多すぎます。では、なぜこれが起こるのですか?

約 100000000 のタスクを実行するようにスケジュールしたためです。各タスクを実行すると、配列に整数を割り当てるよりもはるかに大きなオーバーヘッドが発生します。経験則では、タスクの複雑さがスレッド間通信の複雑さよりも小さい場合は、タスクをスケジュールしないことです。

では、これをどのように修正しますか?より少ないタスクをスケジュールし、各タスクでより多くのことを行います。例えば:

int main(int argc, char *argv[])
{
   const int N = atoi(argv[1]);
   __block std::vector<int> a(N, 0);
   dispatch_apply(4,
     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
     ^(size_t iN)
     {
         size_t s = a.size()/4;
         size_t i = (s*iN);
         size_t n = i + s;
         //printf("Iteration #%lu [%lu, %lu]\n", iN, i, n);
         while (i < n) {
             a[i] = i++;
         }
     });
  return 0;
}

これで、プロファイラーには次のように表示されます。

それほど悪くない

テストを再度実行すると、GCD が少し速くなります。

$ time ./test_nogcd 100000000 10000000

real    0m0.516s
user    0m0.378s
sys 0m0.138s
$ time ./test_gcd 100000000 10000000

real    0m0.507s
user    0m0.556s
sys 0m0.138s

おそらく、より少ないタスクを実行すると、より良くなるでしょうか? やってみて。このような単純なワークフローでは、シングルスレッドの SIMD 実装を使用する方がはるかに優れている可能性があります。またはそうでないかもしれません :)

場合によっては、特に注意が必要な場合があります。たとえば、合計サイズを N 個の等しい部分に分割できない場合などです。簡単にするために、すべてのエラー チェックを省略しました。

また、今日のコモディティ ハードウェアでのタスクの並列化に関しては、さまざまなニュアンスがあります。MESI、偽共有、メモリ バリア、CPU キャッシュ、キャッシュ無視アルゴリズムなどに慣れることをお勧めします。そして覚えておいてください - 常にプロファイラーを使用してください!

それが役に立てば幸い。幸運を!

于 2013-01-13T22:03:34.533 に答える
2

GCD は全体的な実行時間を魔法のように短縮するわけではなく、その使用には間違いなくコストがdispatch_apply_*かかります。(今、2.5 秒はそのような管理には長すぎるように思えますが、結果の妥当性を評価することはできません)。最終的には、GCD を正しく (適切なシナリオで) 使用し、ハードウェアで許可されている場合、GCD によってパフォーマンスが向上する可能性があります。

おそらく、別のスレッドで非同期にタスクを実行する GCD の機能であると思わせる GCD の機能です。これは、それ自体で、またどちらの場合も、必ずしも全体的な実行時間の短縮につながるわけではありませんが、たとえば UI がフリーズしないようにすることで、アプリの応答性を向上させるのに役立ちます。

それに加えて、CPU がより多くのコアを持っている場合、またはマルチ CPU システムを使用していてスレッドが異なるコア/CPU でスケジュールされている場合、GCD は全体の実行時間を改善する可能性があります。タスクは並行して実行されます。このような場合、2 つのタスクの合計期間は、より長いタスク期間 (+ 管理コスト) に等しくなります。

これを明確にし、例についてさらに詳しく説明すると、次のことにも気付くことができます。

  1. 同じセカンダリ スレッドで N 個のタスクをスケジュールしています。これらのタスクは、マルチコア システムでも順次実行されます。

  2. メインを実行している他の唯一のスレッドは、長いことは何もしていないため、プログラムの全体的な期間は、ポイント 1 のタスクの期間によって一意に決定されます。

  3. 最後に、タスクの性質を考慮に入れると、N 回実行する割り当てに過ぎないことがわかります。ここで、GCD の場合、そのような割り当てごとに、タスクをキューに入れ、後でそれをセカンダリ スレッドで実行します。非 GCD の場合、単純に for ループを反復して N 個の割り当てを実行するだけで、最速の時間が得られます。前者の場合、割り当てごとに、タスクのキューイングとスケジューリングにも料金がかかります。

おそらく、これは GCD の利点を測定する最も重要なシナリオではありませんが、パフォーマンスの観点から GCD のコストを測定するのには適しています (私には最悪のシナリオのように見えます)。

于 2013-01-13T21:23:42.933 に答える