5

私は C++ でスレッドをいじっています。特に、それらを使用してマップ操作を並列化しています。

コードは次のとおりです。

#include <thread>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <math.h>
#include <stdio.h>

double multByTwo(double x){
  return x*2;
}

double doJunk(double x){
  return cos(pow(sin(x*2),3));
}

template <typename T>
void map(T* data, int n, T (*ptr)(T)){
  for (int i=0; i<n; i++)
    data[i] = (*ptr)(data[i]);
}

template <typename T>
void parallelMap(T* data, int n, T (*ptr)(T)){
  int NUMCORES = 3;
  std::vector<std::thread> threads;
  for (int i=0; i<NUMCORES; i++)
    threads.push_back(std::thread(&map<T>, data + i*n/NUMCORES, n/NUMCORES, ptr));
  for (std::thread& t : threads)
    t.join();
}

int main()
{
  int n = 1000000000;
  double* nums = new double[n];
  for (int i=0; i<n; i++)
    nums[i] = i;

  std::cout<<"go"<<std::endl;

  clock_t c1 = clock();

  struct timespec start, finish;
  double elapsed;

  clock_gettime(CLOCK_MONOTONIC, &start);

  // also try with &doJunk
  //parallelMap(nums, n, &multByTwo);
  map(nums, n, &doJunk);

  std::cout << nums[342] << std::endl;

  clock_gettime(CLOCK_MONOTONIC, &finish);

  printf("CPU elapsed time is %f seconds\n", double(clock()-c1)/CLOCKS_PER_SEC);

  elapsed = (finish.tv_sec - start.tv_sec);
  elapsed += (finish.tv_nsec - start.tv_nsec) / 1000000000.0;

  printf("Actual elapsed time is %f seconds\n", elapsed);
}

並列バージョンでは実際にmultByTwoはわずかに遅く (1.01 秒対 .95 リアルタイム)、doJunk ではより高速です (51 対 136 リアルタイム)。これは私にそれを意味します

  1. 並列化が機能しており、
  2. 新しいスレッドを宣言すると、非常に大きなオーバーヘッドが発生します。オーバーヘッドが非常に大きい理由と、それを回避する方法について何か考えはありますか?
4

4 に答える 4

7

推測にすぎませんが、multByTwoコードが非常に高速であるため、メモリが飽和状態になっている可能性があります。コードは、RAM との間でビットを送受信できる速度で既に実行されているため、プロセッサの処理能力に関係なく、これ以上速く実行されることはありません。

于 2012-06-22T15:50:31.533 に答える
3

プログラムをテストするハードウェアも、コンパイラのバージョンとオペレーティング システムも指定していません。g++ソースからコンパイルされた 4.7を使用して、64 ビット Scientific Linux の 4 ソケット Intel Xeon システムでコードを試しました。

最初に、古い Xeon X7350 システムで次のタイミングを得ました。

multByTwomap

CPU elapsed time is 6.690000 seconds
Actual elapsed time is 6.691940 seconds

multByTwoparallelMap3コアで

CPU elapsed time is 7.330000 seconds
Actual elapsed time is 2.480294 seconds

並列スピードアップは 2.7 倍です。

doJunkmap

CPU elapsed time is 209.250000 seconds
Actual elapsed time is 209.289025 seconds

doJunkparallelMap3コアで

CPU elapsed time is 220.770000 seconds
Actual elapsed time is 73.900960 seconds

並列スピードアップは 2.83x です。

X7350 は、かなり古い Nehalem 以前の "Tigerton" ファミリーのものであり、FSB バスと共有メモリ コントローラーがノース ブリッジに配置されていることに注意してください。これは、NUMA 効果のない純粋な SMP システムです。

次に、4 ソケットの Intel X7550 でコードを実行します。これらは、メモリ コントローラが CPU に統合された Nehalem (「Beckton」) Xeon であり、したがって 4 ノード NUMA システムです。1 つのソケットで実行され、別のソケットにあるメモリにアクセスするスレッドは、実行速度が多少遅くなります。同じことは、愚かなスケジューラーの決定によって別のソケットに移行される可能性のあるシリアルプロセスにも当てはまります。タイミングからわかるように、このようなシステムでのバインドは非常に重要です。

multByTwomap

CPU elapsed time is 4.270000 seconds
Actual elapsed time is 4.264875 seconds

multByTwomapNUMA ノード 0にバインド

CPU elapsed time is 4.160000 seconds
Actual elapsed time is 4.160180 seconds

multByTwomapNUMA ノード 0 および CPU ソケット 1にバインド

CPU elapsed time is 5.910000 seconds
Actual elapsed time is 5.912319 seconds

mutlByTwoparallelMap3コアで

CPU elapsed time is 7.530000 seconds
Actual elapsed time is 3.696616 seconds

並列スピードアップはわずか 1.13 倍です (最速のノード バウンド シリアル実行と比較して)。今バインディングで:

multByTwoparallelMapNUMA ノード 0 にバインドされた 3 つのコアを使用

CPU elapsed time is 4.630000 seconds
Actual elapsed time is 1.548102 seconds

並列スピードアップは 2.69 倍で、Tigerton CPU と同程度です。

multByTwoparallelMapNUMA ノード 0 と CPU ソケット 1 にバインドされた 3 つのコア

CPU elapsed time is 5.190000 seconds
Actual elapsed time is 1.760623 seconds

並列速度は 2.36 倍 - 前のケースの 88% です。

(私はdoJunk比較的遅い Nehalems でコードが完了するのを待ちきれなかったが、Tigerton の場合のようにいくらかパフォーマンスが向上することを期待していた)

ただし、NUMA バインディングには注意点が 1 つあります。たとえば、これを使用して NUMA ノード 0 にバインドすると、numactl --cpubind=0 --membind=0 ./programメモリ割り当てがこのノードのみに制限され、特定のシステムでは CPU 0 に接続されたメモリが十分でない可能性があり、ランタイム エラーが発生する可能性が高くなります。

ご覧のとおり、スレッドの作成によるオーバーヘッド以外にも、コードの実行時間に大きな影響を与える要因があります。また、非常に高速なシステムでは、各スレッドが実行する計算作業に比べてオーバーヘッドが高すぎる可能性があります。そのため、並列パフォーマンスに関する質問をするときは、パフォーマンスの測定に使用されたハードウェアと環境についてできるだけ多くの詳細を常に含める必要があります。

于 2012-06-22T17:31:26.977 に答える
2

マルチコアマシンでは、複数のスレッドがより少ない時間でより多くの作業を実行できます。

それ以外の場合、彼らはラウンドロビン方式で交代しているだけです。

于 2012-06-22T15:49:40.027 に答える
0

プラットフォームによっては、新しいスレッドの生成はコストのかかる操作になる場合があります。このオーバーヘッドを回避する最も簡単な方法は、プログラムの起動時にいくつかのスレッドを生成し、何らかのジョブ キューを用意することです。std::async がこれを行うと思います。

于 2012-06-22T15:48:07.377 に答える