pthreads没有真正的加速度

时间:2016-09-10 12:47:15

标签: c multithreading pthreads c99

我正在尝试实现Monte-Carlo算法的多线程版本。这是我的代码:

#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()函数无关紧要)。我试图在我的笔记本电脑上使用Intel 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

我认为它应该是执行时间和工作线程数之间至少有四个第一个值的某种线性相关性(线程越多,执行时间就越少),但是这里的时间值非常相等。这是我的代码中的真正问题还是我要求太高了?

2 个答案:

答案 0 :(得分:0)

我能够通过对代码进行两次更改来收集您希望的时序/缩放度量。

首先,rand()不是线程安全的。在高级check_dots中调用rand_r(seed)替换调用显示随着线程增加而不断扩展。我认为rand可能有一个内部锁,它会序列化执行并阻止任何加速。仅此变化显示了一些缩放,从1.23s到&gt; 0.55秒(5个线程)。

其次,我在核心执行区域引入了障碍,因此不包括串行创建/加入线程和malloc调用的成本。核心执行区域显示出良好的缩放比例,从1.23秒开始 - > 0.18秒(8个线程)。

代码是使用gcc -O3 -pthread mcp.c -std=c11 -lm编译的,运行在Intel E3-1240 v5(4核,HT),Linux 3.19.0-68-generic上。报告单次测量。

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);

答案 1 :(得分:0)

您遇到的问题是rand()的内部状态是所有线程之间的共享资源,因此线程将在访问rand()时序列化。

您需要使用具有每线程状态的伪随机数生成器 - rand_r()函数(尽管在最新版本的POSIX中标记为已过时)可以这样使用。对于认真的工作,您最好导入一些特定的PRNG算法的实现,如Mersenne Twister。