c++ - 使用多线程时性能几乎没有提高

标签 c++ c linux multithreading pthreads

我正在实现求解线性系统的多线程 Jordan-Gauss 方法,我发现在两个线程上运行只比在单线程上运行少大约 15% 的时间,而不是理想的 50%。所以我写了一个简单的程序来复制这个。在这里,我创建了一个 2000x2000 矩阵,并为每个线程提供了 2000/THREADS_NUM 行,以便用它们进行一些计算。

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

#ifndef THREADS_NUM
#define THREADS_NUM 1
#endif

#define MATRIX_SIZE 2000


typedef struct {
    double *a;
    int row_length;
    int rows_number;
} TWorkerParams;

void *worker_thread(void *params_v)
{
    TWorkerParams *params = (TWorkerParams *)params_v;
    int row_length = params->row_length;
    int i, j, k;
    int rows_number = params->rows_number;
    double *a = params->a;

    for(i = 0; i < row_length; ++i) // row_length is always the same
    {
        for(j = 0; j < rows_number; ++j) // rows_number is inverse proportional
                                         // to the number of threads
        {
            for(k = i; k < row_length; ++k) // row_length is always the same
            {
                a[j*row_length + k] -= 2.;
            }
        }
    }
    return NULL;
}


int main(int argc, char *argv[])
{
    // The matrix is of size NxN
    double *a =
        (double *)malloc(MATRIX_SIZE * MATRIX_SIZE * sizeof(double));
    TWorkerParams *params =
        (TWorkerParams *)malloc(THREADS_NUM * sizeof(TWorkerParams));
    pthread_t *workers = (pthread_t *)malloc(THREADS_NUM * sizeof(pthread_t));
    struct timespec start_time, end_time;
    int rows_per_worker = MATRIX_SIZE / THREADS_NUM;
    int i;
    if(!a || !params || !workers)
    {
        fprintf(stderr, "Error allocating memory\n");
        return 1;
    }
    for(i = 0; i < MATRIX_SIZE*MATRIX_SIZE; ++i)
        a[i] = 4. * i; // just an example matrix
    // Initializtion of matrix is done, now initialize threads' params
    for(i = 0; i < THREADS_NUM; ++i)
    {
        params[i].a = a + i * rows_per_worker * MATRIX_SIZE;
        params[i].row_length = MATRIX_SIZE;
        params[i].rows_number = rows_per_worker;
    }
    // Get start time
    clock_gettime(CLOCK_MONOTONIC, &start_time);
    // Create threads
    for(i = 0; i < THREADS_NUM; ++i)
    {
        if(pthread_create(workers + i, NULL, worker_thread, params + i))
        {
            fprintf(stderr, "Error creating thread\n");
            return 1;
        }
    }
    // Join threads
    for(i = 0; i < THREADS_NUM; ++i)
    {
        if(pthread_join(workers[i], NULL))
        {
            fprintf(stderr, "Error creating thread\n");
            return 1;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &end_time);
    printf("Duration: %lf msec.\n", (end_time.tv_sec - start_time.tv_sec)*1e3 +
            (end_time.tv_nsec - start_time.tv_nsec)*1e-6);
    return 0;
}

这里是我如何编译它的:

gcc threads_test.c -o threads_test1 -lrt -pthread -DTHREADS_NUM=1 -Wall -Werror -Ofast
gcc threads_test.c -o threads_test2 -lrt -pthread -DTHREADS_NUM=2 -Wall -Werror -Ofast

现在,当我运行时,我得到:

./threads_test1
Duration: 3695.359552 msec.
./threads_test2
Duration: 3211.236612 msec.

这意味着双线程程序的运行速度比单线程快 13%,即使线程之间没有同步并且它们不共享任何内存也是如此。 我找到了这个答案:https://stackoverflow.com/a/14812411/5647501并认为这可能是处理器缓存的一些问题,所以我添加了填充,但结果仍然保持不变。我更改了我的代码如下:

typedef struct {
    double *a;
    int row_length;
    int rows_number;
    volatile char padding[64 - 2*sizeof(int)-sizeof(double)];
} TWorkerParams;

#define VAR_SIZE (sizeof(int)*5 + sizeof(double)*2)
#define MEM_SIZE ((VAR_SIZE / 64 + 1) * 64  )
void *worker_thread(void *params_v)
{
    TWorkerParams *params = (TWorkerParams *)params_v;
    volatile char memory[MEM_SIZE];
    int *row_length  =      (int *)(memory + 0);
    int *i           =      (int *)(memory + sizeof(int)*1);
    int *j           =      (int *)(memory + sizeof(int)*2);
    int *k           =      (int *)(memory + sizeof(int)*3);
    int *rows_number =      (int *)(memory + sizeof(int)*4);
    double **a        = (double **)(memory + sizeof(int)*5);

    *row_length = params->row_length;
    *rows_number = params->rows_number;
    *a = params->a;

    for(*i = 0; *i < *row_length; ++*i) // row_length is always the same
    {
        for(*j = 0; *j < *rows_number; ++*j) // rows_number is inverse proportional
                                         // to the number of threads
        {
            for(*k = 0; *k < *row_length; ++*k) // row_length is always the same
            {
                (*a + *j * *row_length)[*k] -= 2. * *k;
            }
        }
    }
    return NULL;
}

所以我的问题是:为什么我在这里使用两个线程时只能获得 15% 的加速而不是 50%?任何帮助或建议将不胜感激。 我正在运行 64 位 Ubuntu Linux,内核 3.19.0-39-generic,CPU Intel Core i5 4200M(具有多线程的两个物理内核),但我也在另外两台机器上进行了测试,结果相同。

编辑: 如果我用 a[0] -= 2.; 替换 a[j*row_length + k] -= 2.;,我得到预期的加速:

./threads_test1
Duration: 1823.689481 msec.
./threads_test2
Duration: 949.745232 msec.

编辑 2: 现在,当我用 a[k] -= 2.; 替换它时,我得到以下信息:

./threads_test1
Duration: 1039.666979 msec.
./threads_test2
Duration: 1323.460080 msec.

这个我根本买不到。

最佳答案

这是一个经典问题,交换 i 和 j for 循环。

您首先遍历列,然后在内部循环中处理行,这意味着您有比必要更多的缓存未命中。

我的原始代码结果(第一个没有填充的版本):

$ ./matrix_test1
Duration: 4620.799763 msec.
$ ./matrix_test2
Duration: 2800.486895 msec.

(实际上比你的改进更好)

在为 i 和 j 切换 for 循环之后:

$ ./matrix_test1
Duration: 1450.037651 msec.
$ ./matrix_test2
Duration: 728.690853 msec.

这里是 2 倍的加速。

编辑:事实上,原来的并不坏,因为 k 索引仍然遍历行迭代列,但在外循环中迭代行仍然要好得多。当 i 上升时,您在最内层循环中处理的项目越来越少,所以它仍然很重要。

EDIT2:(删除了 block 解决方案,因为它实际上产生了不同的结果)——但仍然应该可以利用 block 来提高缓存性能。

关于c++ - 使用多线程时性能几乎没有提高,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34137578/

相关文章:

linux - 是否可以基于 yum 或 rpm 在 Linux 上编译可移植的可执行文件?

linux - 如何使用 bash 脚本替换单引号中的值?

c++ - 删除 DirectShow 过滤器(未调用析构函数)

python - 在 Cython 中包装返回复杂类型 Vector 的函数

c++ - 如何在 GTK+ 中显示 PostScript 文件?

c++ - for() 和 while() 之间有什么性能差异吗?

c++ - 这个书上的例子不好吗?

C:如何一次将一个整数从 parent 传递给 child ?

objective-c - 将 UnsafeMutablePointer 传递给函数时出错

java - 如何使用 Java 在 JWindow 中捕获键盘输入?