2009-03-25 6 views
3

Intel PentiumデュアルコアプロセッサT2370(Acer Extensa)を搭載したノートパソコンでは、単純なマルチスレッドスピードアップテストを実行しました。私はLinuxを使用しています。コードは下に貼り付けられます。私は2〜3倍のスピードアップを期待していましたが、私は減速を2倍に驚かせました。gccの最適化レベル-O0 ... -O3でも同じことを試みましたが、毎回同じ結果が得られました。私はpthreadsを使用しています。私はまた、(コード内の3つのスレッドの代わりに)2つのスレッドで同じことを試みましたが、パフォーマンスは似ていました。マルチスレッドのマイナススピードプログラム

何故その理由が考えられますか?より速いバージョンは約20秒という比較的長い時間がかかったので、起動時のオーバーヘッドの問題ではないようです。

注:このコードはバグが多いです(シリアルとパラレルバージョンの出力が異なるため実際にはあまり意味がありません)。その意図は、同じ数の命令に対する高速化の比較を「取得」することであった。

#include <stdio.h> 
#include <time.h> 
#include <unistd.h> 
#include <pthread.h> 

class Thread{ 
    private: 
      pthread_t thread; 
      static void *thread_func(void *d){((Thread *)d)->run();} 
    public: 
      Thread(){} 
      virtual ~Thread(){} 

      virtual void run(){} 
      int start(){return pthread_create(&thread, NULL, Thread::thread_func, (void*)this);} 
      int wait(){return pthread_join(thread, NULL);} 
}; 


#include <iostream> 

const int ARR_SIZE = 100000000; 
const int N = 20; 
int arr[ARR_SIZE]; 

int main(void) 
{ 

    class Thread_a:public Thread{ 
      public: 
        Thread_a(int* a): arr_(a) {} 
        void run() 
        { 
          for(int n = 0; n<N; n++) 
          for(int i=0; i<ARR_SIZE/3; i++){ arr_[i] += arr_[i-1];} 
        } 
      private: 
        int* arr_; 
    }; 
    class Thread_b:public Thread{ 
      public: 
        Thread_b(int* a): arr_(a) {} 
        void run() 
        { 
          for(int n = 0; n<N; n++) 
          for(int i=ARR_SIZE/3; i<2*ARR_SIZE/3; i++){ arr_[i] += arr_[i-1];} 
        } 
      private: 
        int* arr_; 
    }; 

    class Thread_c:public Thread{ 
      public: 
        Thread_c(int* a): arr_(a) {} 
        void run() 
        { 
          for(int n = 0; n<N; n++) 
          for(int i=2*ARR_SIZE/3; i<ARR_SIZE; i++){ arr_[i] += arr_[i-1];} 
        } 
      private: 
        int* arr_; 
    }; 

    { 
      Thread *a=new Thread_a(arr); 
      Thread *b=new Thread_b(arr); 
      Thread *c=new Thread_c(arr); 

      clock_t start = clock(); 

      if (a->start() != 0) { 
        return 1; 
      } 

      if (b->start() != 0) { 
        return 1; 
      } 
      if (c->start() != 0) { 
        return 1; 
      } 

      if (a->wait() != 0) { 
        return 1; 
      } 

      if (b->wait() != 0) { 
        return 1; 
      } 

      if (c->wait() != 0) { 
        return 1; 
      } 

      clock_t end = clock(); 
      double duration = (double)(end - start)/CLOCKS_PER_SEC; 

      std::cout << duration << "seconds\n"; 
      delete a; 
      delete b; 

    } 
    { 
      clock_t start = clock(); 
      for(int n = 0; n<N; n++) 
      for(int i=0; i<ARR_SIZE; i++){ arr[i] += arr[i-1];} 
      clock_t end = clock(); 
      double duration = (double)(end - start)/CLOCKS_PER_SEC; 

      std::cout << "serial: " << duration << "seconds\n"; 
    } 

    return 0; 
    } 

も参照してください:アプリケーションがIO-バインドする必要がありますので、あなたのスレッドはありませんWhat can make a program run slower when using more threads?

+1

あなたはこれをどうアーキテクチャ上で実行されていますか?いくつのCPUコアが関与していますか?キャッシュレイアウトとは何ですか? (これらはマルチスレッドコードのパフォーマンスに影響を与えるすべての要因ですので、可能な限り多くの関連情報を提供してください) – Marcin

+0

私はOSも大きな違いをもたらすと思います。 Linuxのスケジューラは、他のほとんどのものと比べて非常に高速です。 – rmeador

+0

最適化されたバージョンはSSEを使用していますか? – tstenner

答えて

16

for(int i=0; i<ARR_SIZE; i++){ arr[i] += arr[i-1];} 

ことになるだろう。

clock()関数が近似値を返します。プログラムによって使用されるプロセッサ時間の

$ time bin/amit_kumar_threads.cpp 
6.62seconds 
serial: 2.7seconds 

real 0m5.247s 
user 0m9.025s 
sys 0m0.304s 

リアルタイムでマルチタスクに少なくなりますが、プロセッサ時間は、一般的に大きくなります。

複数のスレッドを使用する場合、複数のプロセッサで作業を行うことができますが、作業量は同じですが、限られたリソースの競合などのオーバーヘッドが発生する可能性があります。 clock()は、総プロセッサ時間を測定します。これは、作業+競合オーバーヘッドになります。したがって、単一のスレッドで作業を行うためのプロセッサ時間よりも短くする必要はありません。

あなたがこれを知っていたかどうかを知ることは少し難しく、clock()によって返された値が、ほんの少しだけではなく、単一のスレッドの2倍であることに驚いた、 。代わりにclock_gettime()を使用して

(あなたは、リアルタイムライブラリlibrtが必要になります、g++ -lrtなど)が得られます。

まだ1のために期待しているよりもスピードアップが少ないですが、少なくとも数字
$ time bin/amit_kumar_threads.cpp 
2.524 seconds 
serial: 2.761 seconds 

real 0m5.326s 
user 0m9.057s 
sys 0m0.344s 

何か意味がある。

100000000 * 20/2.5s = 800Hz、バスの周波数は1600MHzです。したがって、私は、各繰り返し(読み込みと書き込み)に疑問があります。 clock()値は、ほとんどの場合、一部のプロセッサがデータを待っていることを示しています。 (clock()に時間がかかることは誰にも知られていますか?)

+0

> clock()の値は、ほとんどの場合、プロセッサの一部がデータを待っていることを示しています。私の推測では、プロセッサがデータを待つ間にno-opsで忙しいということです。これはclock()の戻り値に現れます。しかし、ちょうど推測。 –

6

唯一のことは、いくつかの要素を追加しています。余分なスレッドを追加すると、メモリバスを共有する2つのCPUがあるため、高速化されず、キャッシュミスなどが発生する。

+0

私はI/Oバウンドなものを得ることはありません。唯一のI/Oは、タイミング情報を書き出します。あなたは記憶に束縛されているのですか?それで、私は同意します。 – tvanfosson

+0

私はここでメモリをIOとして扱っています。主要な計算がないからです。 – tstenner

0

スレッドは、高速ブーストを約束したに移動します。 (TM)を使用しています。これはあなたが持っている必要があることを意味:

  • あなたのアルゴリズムの適切な並列化
  • 知っていると
  • 並列手続きなどのハードウェア上で並列
のためのハードウェアサポートをごアルゴリズムを広げることができますコンパイラ

最初に出くわすのは難しいです。あなたは冗長性を持つことができ、それがあなたのパフォーマンス、データの次のバッチを処理するためのデータの適切なマージなどを食べていないことを確認する必要があります。

しかしこれは理論上の見解です。

複数のスレッドを実行しても、プロセッサが1つしかなくアルゴリズムが悪い場合はあまり効果がありません。覚えておいてください - プロセッサは1つしかないので、スレッドはタイムスライスを待たなければなりません。本質的にはです。

+0

アルゴリズムは1つですか?私はあなたが1つのプロセッサーを意味すると仮定します – jalf

+0

タイプミス。ありがとう。 – dirkgently

+0

>複数のスレッドを実行しても、プロセッサが1つしかなくアルゴリズムが悪い場合はあまり効果がありません。 デュアルコア= 2 CPU。 –

0

他の人が指摘しているように、スレッドは必ずしも速度を改善するとは限りません。この特定の例では、各スレッドで費やされる時間は、コンテキストスイッチおよび同期を実行するのに必要な時間よりもかなり短い。

4

あなたのアルゴリズムは基本的にあなたのキャッシュメモリを無駄にしてくれると信じています。

あなたが見ているのはおそらく、3つのスレッド間の(非)参照の局所性の影響です。基本的に、各スレッドは、キャッシュ内の別のスレッドのデータセクションに置き換えられるため、キャッシュミスの原因となっている他のセクションとは大きく異なるデータセクションで動作しているためです。あなたのプログラムが、(全てのスレッドが同じイン・キャッシュ・ページを使用できるように)より小さい(全部がメモリに保持されるように)、より近くにあるデータ・セクションで動作するようにプログラムが構築されていれば、パフォーマンスの向上。それは私があなたのキャッシュからではなく、メインメモリから多くのメモリ参照を満たす必要があるため、減速していると思われます。

1

スレッドの問題とは関係ありませんが、コードに境界エラーがあります。あなたは持っている :iがゼロのときに、あなたが報告されている時間は、時計機能を用いて測定される

arr[0] += arr[-1]; 
+0

は、 "arr"での書き込みの同意を制御せず、cスレッドを忘れてしまった。 – lsalamon

+0

ええ、このおもちゃのコードの私の焦点は、単にパフォーマンスを比較していた。 –

+0

うん、正しいコードを評価する方が良いです。 – lsalamon

0

tstennerはほとんど正しいです。

これは主にOSの「新しいページの割り当てとマップ」アルゴリズムのベンチマークです。その配列割り当ては800MBの仮想メモリを割り当てます。 OSは実際の物理メモリを必要になるまで実際に割り当てません。 "新しいページの割り当てとマップ"は通常、ミューテックスによって保護されているので、より多くのコアは役に立ちません。

ベンチマークでは、メモリバスに負荷がかかっています(最小800MBが転送されますが、メモリがゼロになると最悪の場合は800MB * 7転送)。ボトルネックがメモリバスの場合、コアを追加することは実際には役に立たないでしょう。

あなたは3つのスレッドが同じメモリ上にいたずらしています。キャッシュラインは異なるスレッドによって読み書きされているため、2つのCPUコアのL1キャッシュ間でピンポンギングが発生します。 (書き込まれるキャッシュラインは、1つのL1キャッシュにしかなければならず、それは書き込みを行っているCPUコードに接続されたL1キャッシュでなければならない)。これはあまり効率的ではありません。 CPUコアはおそらく、キャッシュラインが転送されるのを待っている時間の大部分を費やしている可能性があります。なぜなら、これはシングルスレッドの場合よりもスレッドの方が遅いからです。同じアレイをロックせずに&が異なるCPUから書き込まれ読み出されるので

なお、コードもバグがあります。適切なロックがパフォーマンスに影響します。

+0

>同じ配列が異なるCPUからロックなしで読み書きされるため、コードもバグです しかし、異なるメモリ位置が書き込まれています。それは大丈夫だと思います。 –

+0

>あなたは3つのスレッドが同じメモリ上にいたずらしています。本当かどうかは分かりません。 –

1

また、マルチスレッドコードでマルチCPU、キャッシュラインの干渉が特別セクションでは、 `すべての共有が悪い方法にherb's articleを参照してください - でも、「共有されない」オブジェクトの...」