1

私はPthreadでEratosthenesプログラムの並列篩を実装しようとしています。私は私のコーディングを終了し、プログラムが正常に動作し、期待通りに処理されます。つまり、複数のスレッドを使用すると、計算時間はシーケンシャルプログラム(1スレッドのみ使用)よりも少なくなります。しかし、私が何回余分なスレッドを使用しても、計算時間は基本的に同じになります。例えば、私が1から10億の計算をすると、逐次プログラムは約21秒を使用し、2スレッドの並列プログラムは約14秒を使用しました。しかし、私が試したように3,4,5,10,20,50のスレッドを使用すると、常に約14秒かかります。私はこの問題の原因と解決方法を知りたい。私のコードは以下の通りです:エラトステネスのふるいPthreadの実装:スレッド番号は計算時間に影響しません

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <stdbool.h> 
//The group of arguments passed to thread 
struct thrd_data{ 
    long id; 
    long start; 
    long end; /* the sub-range is from start to end */ 
}; 
typedef struct { 
    pthread_mutex_t  count_lock;  /* mutex semaphore for the barrier */ 
    pthread_cond_t  ok_to_proceed; /* condition variable for leaving */ 
    long    count;  /* count of the number of threads who have arrived */ 
} mylib_barrier_t; 

//global variable 
bool *GlobalList;//The list of nature number 
long Num_Threads; 
mylib_barrier_t barrier;/* barrier */ 

void mylib_barrier_init(mylib_barrier_t *b) 
{ 
    b -> count = 0; 
    pthread_mutex_init(&(b -> count_lock), NULL); 
    pthread_cond_init(&(b -> ok_to_proceed), NULL); 
} 

void mylib_barrier(mylib_barrier_t *b, long id) 
{ 
    pthread_mutex_lock(&(b -> count_lock)); 
    b -> count ++; 
    if (b -> count == Num_Threads) 
    { 
    b -> count = 0; /* must be reset for future re-use */ 
    pthread_cond_broadcast(&(b -> ok_to_proceed)); 
    } 
    else 
    { 
    while (pthread_cond_wait(&(b -> ok_to_proceed), &(b -> count_lock)) != 0); 

    } 
    pthread_mutex_unlock(&(b -> count_lock)); 
} 

void mylib_barrier_destroy(mylib_barrier_t *b) 
{ 
    pthread_mutex_destroy(&(b -> count_lock)); 
    pthread_cond_destroy(&(b -> ok_to_proceed)); 
} 

void *DoSieve(void *thrd_arg) 
{ 

    struct thrd_data *t_data; 
    long i,start, end; 
    long k=2;//The current prime number in first loop 
    long myid; 

    /* Initialize my part of the global array */ 
    t_data = (struct thrd_data *) thrd_arg; 
    myid = t_data->id; 
    start = t_data->start; 
    end = t_data->end; 

    printf ("Thread %ld doing look-up from %ld to %ld\n", myid,start,end); 
    //First loop: find all prime numbers that's less than sqrt(n) 
    while (k*k<=end) 
    { 
     int flag; 
     if(k*k>=start) 
     flag=0; 
     else 
     flag=1; 
     //Second loop: mark all multiples of current prime number 
     for (i = !flag? k*k-1:start+k-start%k-1; i <= end; i += k) 
     GlobalList[i] = 1; 
     i=k; 
     //wait for other threads to finish the second loop for current prime number 
     mylib_barrier(&barrier,myid); 
     //find next prime number that's greater than current one 
     while (GlobalList[i] == 1) 
      i++; 
     k = i+1; 

    } 
    //decrement the counter of threads before exit 
    pthread_mutex_lock (&barrier.count_lock); 
    Num_Threads--; 
    if (barrier.count == Num_Threads) 
    { 
    barrier.count = 0; /* must be reset for future re-use */ 
    pthread_cond_broadcast(&(barrier.ok_to_proceed)); 
    } 
    pthread_mutex_unlock (&barrier.count_lock); 
    pthread_exit(NULL); 
} 


int main(int argc, char *argv[]) 
{ 
    long i, n,n_threads; 
    long k, nq, nr; 
    FILE *results; 
    struct thrd_data *t_arg; 
    pthread_t *thread_id; 
    pthread_attr_t attr; 

    /* Pthreads setup: initialize barrier and explicitly create 
    threads in a joinable state (for portability) */ 
    mylib_barrier_init(&barrier); 
    pthread_attr_init(&attr); 
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 

    /* ask to enter n and n_threads from the user */ 
    printf ("enter the range n = "); 
    scanf ("%ld", &n); 
    printf ("enter the number of threads n_threads = "); 
    scanf ("%ld", &n_threads); 
    time_t start = time(0);//set initial time 
    //Initialize global list 
    GlobalList=(bool *)malloc(sizeof(bool)*n); 
    for(i=0;i<n;i++) 
    GlobalList[i]=0; 
    /* create arrays of thread ids and thread args */ 
    thread_id = (pthread_t *)malloc(sizeof(pthread_t)*n_threads); 
    t_arg = (struct thrd_data *)malloc(sizeof(struct thrd_data)*n_threads); 

    /* distribute load and create threads for computation */ 
    nq = n/n_threads; 
    nr = n % n_threads; 

    k = 1; 
    Num_Threads=n_threads; 
    for (i=0; i<n_threads; i++){ 
    t_arg[i].id = i; 
    t_arg[i].start = k; 
    if (i < nr) 
     k = k + nq + 1; 
    else 
     k = k + nq; 
    t_arg[i].end = k-1; 
    pthread_create(&thread_id[i], &attr, DoSieve, (void *) &t_arg[i]); 
    } 

    /* Wait for all threads to complete then print all prime numbers */ 
    for (i=0; i<n_threads; i++) { 
    pthread_join(thread_id[i], NULL); 
    } 
    int j=1; 
    //Get the spent time for the computation works by all participanting threads 
    time_t stop = time(0); 
    printf("Time to do everything except print = %lu seconds\n", (unsigned long) (stop-start)); 
    //print the result of prime numbers 
    printf("The prime numbers are listed below:\n"); 
    for (i = 1; i < n; i++) 
    { 
    if (GlobalList[i] == 0) 
    { 
     printf("%ld ", i + 1); 
     j++; 
    } 
    if (j% 15 == 0) 
     printf("\n"); 
    } 
    printf("\n"); 
    // Clean up and exit 
    free(GlobalList); 
    pthread_attr_destroy(&attr); 
    mylib_barrier_destroy(&barrier); // destroy barrier object 
    pthread_exit (NULL); 
} 
+0

@EOF私のプログラムの唯一の共有変数はグローバル配列ですが、各スレッドは指定された部分範囲を計算するだけで、他の部分範囲とは重複しないため、並行処理の問題は発生しません。あなたは懸念している。 – Ivan

+0

@EOF詳細を説明できますか?私は、あなたが懸念している状況につながる可能性があるとは思わない。 – Ivan

+0

ミューテックスがどのようなデータを保護すると予想されているかを文書化しておくと役に立ちます。また、視差のある仕事に共通する可能性のある問題は、すでに除外されていますか? –

答えて

1

あなたは有効な観察を行います。スレッド数が増えても、それ以上の作業が行われるわけではありません。

デュアルコアCPUでプログラムを実行しています。すでに2つのスレッドでシステムを飽和させています。

1つのスレッドのみで1つのコアが使用されます。 2つのスレッドで2つのコアが使用されます。 4つのスレッドを言うと、2つのスレッドと同じパフォーマンスが表示されます。ハイパースレッディングは、論理コア(HTコア)が物理的なコアとメモリシステムを共有するため、役立たない。ここで

これはi5-4460 CPUのハードウェア・パフォーマンス・モニターの出力である

 23879.553188  task-clock (msec)   # 1.191 CPUs utilized   
      3,666  context-switches   # 0.154 K/sec     
      1,470  cpu-migrations   # 0.062 K/sec     
      219,177  page-faults    # 0.009 M/sec     
    76,070,790,848  cycles     # 3.186 GHz      
    <not supported>  stalled-cycles-frontend 
    <not supported>  stalled-cycles-backend 
    34,500,622,236  instructions    # 0.45 insns per cycle   
    4,172,395,541  branches     # 174.727 M/sec     
     1,020,010  branch-misses    # 0.02% of all branches   
    21,065,385,093  L1-dcache-loads   # 882.152 M/sec     
    1,223,920,596  L1-dcache-load-misses  # 5.81% of all L1-dcache hits 
     69,357,488  LLC-loads     # 2.904 M/sec     
    <not supported>  LLC-load-misses:HG 

ふるい-d

PERFスタットを実行しているの出力です。それはいくつかの興味深い統計を追跡します。

サイクルカウントごとに低い命令に注目してください。 CPUは1サイクルにつき0.45命令を実行しています。通常、この値が1より大きい値を表示したいとします。

更新:ここで重要な点は、スレッド数を増やすことは役に立ちません。 CPUは、限られた量の分岐とメモリアクセスしか行うことができません。

+0

相関は因果関係を意味するものではありません。原因を特定するために、実際のプログラム全体を検査する必要があります。低い命令速度は必ずしも高いメモリ使用量を意味しません。 – specializt

+0

あなたは正しいです。プロファイルされます。 –

+0

特定のマシンで自分のプログラムを実行するために使用される最適なスレッドは、そのマシンに搭載されているコアの数だけであると言えますか? – Ivan

-1

2つの所見。

まず、シーブコードを修正すると、現在のコードの約25倍の速さで、現在のコードを32コア以上で正常に配布できると予想されます。

は、私はすべての言語の C#で1.25秒で2,000,000,000までの数字をふるいする方法を示しました prime number summing still slow after using sieveを見てください。この記事では、各ステップ/テクニックを個別に説明(およびベンチマーク)して、好きなものを選び、ニーズにあった完璧なバング/バックの比率をもたらすソリューションをロールアップします。 C/C++では、小さなものを汗ばむコンパイラ(少なくともgccやVC++のような優れたコンパイラでは)を数えることができるので、状況はさらに速くなります。

第2回:ふるい分けするときは、が最も重要なリソースは、CPUのレベル1キャッシュです。他のすべてが第2フィドルを演じる。私の記事のベンチマークからもこれを見ることができます。複数のCPUにわたってふるい分け作業を分散するには、システムのL1キャッシュを数え、各キャッシュにふるい分けのジョブを割り当てます(kはL1キャッシュの数です)。通常、作業項目のサイズをより細かく選択するため、これは単純化されていますが、一般的な考え方を示しています。

「コア」、「バーチャルコア」、「スレッド」ではなく「キャッシュ」と言いました。これは、あなたが行う必要があるためです。各ジョブに独自のL1キャッシュがあるように割り当てます。どのように動作するかは、オペレーティングシステムだけでなく、システム内の特定のCPUによっても左右されます。 2つの 'whater'がL1キャッシュを共有している場合は、2つのうちの1つのみにジョブを与え、もう一方を無視します(または、ジョブの親和性を2つのいずれかで実行できるように設定します。

これは、オペレーティングシステムのAPI(Win32など)では簡単に行うことができますが、必要な精度を提供するかどうかを判断するのに十分なpthreadについてはわかりません。最初の近似として、疑わしいL1キャッシュの数にスレッドの数を一致させることができます。

+0

ちょうど2つの注意点:まず、あなたの怒りの声を調整します。第二に、あなたの最後の文はちょうどFUDであり、あなたの答えはそれなしでより良いでしょう。私はpthreadsについてあまり知らないためにあなたを責めることはありませんが、あなたはそのような発言をしてはいけません。 –

+0

@Ulrich:「怒っている」とは見えない、私は2つの重要なことを強調しているに過ぎない。とにかく、あなたが不適切であると感じるものは自由に編集してください(英語は私の母国語ではなく、私の個人的な誤りです。 「フォームオーバー機能」)。 – DarthGizka

関連する問題