2016-09-10 13 views
-1

モンテカルロアルゴリズムのマルチスレッド版を実装しようとしています。pthreadsによる実際のアクセラレーションがありません

#define _POSIX_C_SOURCE 200112L 

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <time.h> 
#include <math.h> 
#include <semaphore.h> 
#include <errno.h> 
#include <stdbool.h> 
#include <string.h> 

#define MAX_THREADS 12 
#define MAX_DOTS 10000000 

double sum = 0.0; 
sem_t sem; 

void reset() { 
    sum = 0.0; 
} 

void* check_dot(void* _iterations) { 
    int* iterations = (int*)_iterations; 
    for(int i = 0; i < *iterations; ++i) { 
     double x = (double)(rand() % 314)/100; 
     double y = (double)(rand() % 100)/100; 
     if(y <= sin(x)) { 
      sem_wait(&sem); 
      sum += x * y; 
      sem_post(&sem); 
     } 
    } 
    return NULL; 
} 

void* check_dots_advanced(void* _iterations) { 
    int* iterations = (int*)_iterations; 
    double* res = (double*)malloc(sizeof(double)); 
    for(int i = 0; i < *iterations; ++i) { 
     double x = (double)(rand() % 314)/100; 
     double y = (double)(rand() % 100)/100; 
     if(y <= sin(x)) *res += x * y; 
    } 
    pthread_exit((void*)res); 
} 

double run(int threads_num, bool advanced) { 
    if(!advanced) sem_init(&sem, 0, 1); 
    struct timespec begin, end; 
    double elapsed; 
    pthread_t threads[threads_num]; 
    int iters = MAX_DOTS/threads_num; 
    for(int i = 0; i < threads_num; ++i) { 
     if(!advanced) pthread_create(&threads[i], NULL, &check_dot, (void*)&iters); 
     else pthread_create(&threads[i], NULL, &check_dots_advanced, (void*)&iters); 
    } 
    if(clock_gettime(CLOCK_REALTIME, &begin) == -1) { 
     perror("Unable to get time"); 
     exit(-1); 
    } 
    for(int i = 0; i < threads_num; ++i) { 
     if(!advanced) pthread_join(threads[i], NULL); 
     else { 
      void* tmp; 
      pthread_join(threads[i], &tmp); 
      sum += *((double*)tmp); 
      free(tmp); 
     } 
    } 
    if(clock_gettime(CLOCK_REALTIME, &end) == -1) { 
     perror("Unable to get time"); 
     exit(-1); 
    } 
    if(!advanced) sem_destroy(&sem); 
    elapsed = end.tv_sec - begin.tv_sec; 
    elapsed += (end.tv_nsec - begin.tv_nsec)/1000000000.0; 
    return elapsed; 
} 

int main(int argc, char** argv) { 
    bool advanced = false; 
    char* filename = NULL; 
    for(int i = 1; i < argc; ++i) { 
     if(strcmp(argv[i], "-o") == 0 && argc > i + 1) { 
      filename = argv[i + 1]; 
      ++i; 
     } 
     else if(strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--advanced") == 0) { 
      advanced = true; 
     } 
    } 
    if(!filename) { 
     fprintf(stderr, "You should provide the name of the output file.\n"); 
     exit(-1); 
    } 
    FILE* fd = fopen(filename, "w"); 
    if(!fd) { 
     perror("Unable to open file"); 
     exit(-1); 
    } 
    srand(time(NULL)); 
    double worst_time = run(1, advanced); 
    double result = (3.14/MAX_DOTS) * sum; 
    reset(); 
    fprintf(fd, "Result: %f\n", result); 
    for(int i = 2; i <= MAX_THREADS; ++i) { 
     double time = run(i, advanced); 
     double accel = time/worst_time; 
     fprintf(fd, "%d:%f\n", i, accel); 
     reset(); 
    } 
    fclose(fd); 
    return 0; 
} 

しかし、私は、スレッドの数を増やすことで任意の実際の加速を参照することはできません(と、私が使用しています何check_dot()関数は関係ありません):ここに私のコードです。私はインテルCore i7-3517uで私のラップトップ上でこのコードを実行しようとした(lscpuが、それは4つの独立したCPUを持っていることを言います)とスレッドの数は実際に私のプログラムの実行時間に影響を与えないように見えます:

Number of threads: 1, working time: 0.847277 s 
Number of threads: 2, working time: 3.133838 s 
Number of threads: 3, working time: 2.331216 s 
Number of threads: 4, working time: 3.011819 s 
Number of threads: 5, working time: 3.086003 s 
Number of threads: 6, working time: 3.118296 s 
Number of threads: 7, working time: 3.058180 s 
Number of threads: 8, working time: 3.114867 s 
Number of threads: 9, working time: 3.179515 s 
Number of threads: 10, working time: 3.025266 s 
Number of threads: 11, working time: 3.142141 s 
Number of threads: 12, working time: 3.064318 s 

私は、少なくとも4つの最初の値(より多くのスレッドがより少ない実行時間で動作している)の実行時間と作業スレッドの数の間に線形依存関係があるはずですが、ここではかなり等しい時間値。それは私のコードで本当の問題か、あまりにも厳しいですか?

+1

マルチスレッドは非常に高価です。並行処理が利益をコストより上回る程度まで処理をスピードアップできると信じる深いアルゴリズム的および数学的理由があれば、それは価値があります。 –

+0

コアi7-3517uはデュアルコア(ハイパースレッディング付き)です。つまり、2つのスレッドでは2つのコアがビジー状態になり、4つのスレッドではコアのリソースを共有する「コアあたり2つのスレッド」が得られます。 4つ以上のスレッドの場合、それは時間の純粋な無駄です(スレッドスイッチングオーバーヘッドだけです)。 – Brendan

+0

@ EOF、なぜ 'sum'へのアクセスが非同期であるのか説明していただければ幸いです。私はここでセマフォを使うのですか? – kasom

答えて

0

私はあなたのコードに2つの変更を加えることで望むタイミング/スケーリングの測定値を収集することができました。

まず、rand()はスレッドセーフではありません。高度なチェックドットでrand_r(シード)を呼び出して呼び出しを置き換えると、スレッドが増加するにつれて連続的なスケーリングが行われました。私は、randは実行をシリアライズし、スピードアップを防ぐ内部ロックを持っていると思う。この変更だけでも、1.23秒から0.55秒(5スレッド)までのスケーリングが示されています。

第2に、スレッドとmalloc呼び出しをシリアルで作成/結合するコストが含まれないように、コア実行領域の周囲にバリアを導入しました。コア実行領域は、1.23秒 - > 0.18秒(8スレッド)の優れたスケーリングを示します。

コードはIntel E3-1240 v5(4コア、HT)、Linux 3.19.0-68-genericで実行され、gcc -O3 -pthread mcp.c -std=c11 -lmでコンパイルされました。単一の測定値が報告された。

pthread_barrier_t bar; 

void* check_dots_advanced(void* _iterations) { 
    int* iterations = (int*)_iterations; 
    double* res = (double*)malloc(sizeof(double)); 
    sem_wait(&sem); 
    unsigned int seed = rand(); 
    sem_post(&sem); 
    pthread_barrier_wait(&bar); 
    for(int i = 0; i < *iterations; ++i) { 
     double x = (double)(rand_r(&seed) % 314)/100; 
     double y = (double)(rand_r(&seed) % 100)/100; 
     if(y <= sin(x)) *res += x * y; 
    } 
    pthread_barrier_wait(&bar); 
    pthread_exit((void*)res); 
} 

double run(int threads_num, bool advanced) { 
    sem_init(&sem, 0, 1); 
    struct timespec begin, end; 
    double elapsed; 
    pthread_t threads[threads_num]; 
    int iters = MAX_DOTS/threads_num; 
    pthread_barrier_init(&bar, NULL, threads_num + 1); // barrier init 
    for(int i = 0; i < threads_num; ++i) { 
     if(!advanced) pthread_create(&threads[i], NULL, &check_dot, (void*)&iters); 
     else pthread_create(&threads[i], NULL, &check_dots_advanced, (void*)&iters); 
    } 
    pthread_barrier_wait(&bar); // wait until threads are ready 
    if(clock_gettime(CLOCK_REALTIME, &begin) == -1) { // begin time 
     perror("Unable to get time"); 
     exit(-1); 
    } 

    pthread_barrier_wait(&bar); // wait until threads finish 
    if(clock_gettime(CLOCK_REALTIME, &end) == -1) { // end time 
     perror("Unable to get time"); 
     exit(-1); 
    } 
    for(int i = 0; i < threads_num; ++i) { 
     if(!advanced) pthread_join(threads[i], NULL); 
     else { 
      void* tmp; 
      pthread_join(threads[i], &tmp); 
      sum += *((double*)tmp); 
      free(tmp); 
     } 
    } 
    pthread_barrier_destroy(&bar); 
+0

'rand()'がスレッドセーフではないことを(正確に!)指摘して、それを 'check_dots_advanced()'で同期させずに使うのはちょっと難解です。 – EOF

+0

私は、rand_r()を簡単に作成するために使用しました。各スレッドにシード値を設定する必要があったため、基本的なスケーラビリティの問題を判断/デモンストレーションする目的で、より簡単な変更でした。私はこの単一呼び出しをrand()に保護するためにセマフォを使うコードを編集しました。 – Brian

0

発生している問題は、rand()の内部状態は、すべてのスレッド間で共有リソースであるため、スレッドはrand()へのアクセスにシリアライズしようとしているということです。

スレッドごとの状態で擬似乱数ジェネレータを使用する必要があります。rand_r()関数(POSIXの最新バージョンでは廃止されていますが)をそのまま使用できます。深刻な作業のためには、メルセンヌ・ツイスターのような特定のPRNGアルゴリズムの実装をインポートすることをお勧めします。

関連する問題