使用多线程时性能几乎没有提升
Posted
技术标签:
【中文标题】使用多线程时性能几乎没有提升【英文标题】:Little performance increasing when using multiple threads 【发布时间】:2015-12-07 15:46:05 【问题描述】:我正在实现求解线性系统的多线程 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.
这意味着 2 线程程序的运行速度比单线程快 13%,即使线程之间没有同步并且它们不共享任何内存。 我找到了这个答案:https://***.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.
这个我根本买不到。
【问题讨论】:
我投票决定将此问题作为离题结束,因为这听起来更像是一个代码审查问题。 【参考方案1】:这是一个经典的问题,将 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:(删除了块解决方案,因为它实际上产生了不同的结果)-但仍然应该可以利用块来提高缓存性能。
【讨论】:
会不会是我们机器的区别?因为在切换 i 和 j 的循环后,我得到以下信息:./threads_test1 持续时间:1048.321083 毫秒。 ./threads_test2 持续时间:1012.153498 毫秒。 你真的只是像这样切换循环吗:for(j = 0; j 尝试将您的第一个代码放入问题中,并将“for (j ...”语句移到“for (i ...”语句上方,不要交换变量。 是的,我真的只是像你说的那样切换了循环,只是切换了两行“for(j...)”和“for(i...)”。交换索引变量是什么意思?我不认为我可以交换它们,因为它们的含义在我切换循环后保持不变,而且,切换循环的意义不在于留下索引吗? 嗯,那真的很奇怪,因为您现在 1 线程与 2 线程的时间非常接近(虽然比原来的时间好得多,但现在 1 与 2 线程的比率仍然要差得多,这真的很奇怪)。你也可以在其他机器上试试吗?【参考方案2】:您是否谈到了 13 % 的加速,但您的微积分功能所用的时间是多少,而不是程序的其余部分。
你可以开始只估计 calcul 方法传递的时间,而不考虑线程管理的时间。您可能会在线程管理中失去重要的部分时间。这可以解释您获得的小幅加速。
另一方面,50% 的速度用 2 个线程是不可能获得的。
【讨论】:
感谢您的回复。我尝试将 MATRIX_SIZE 增加到 3000,但我仍然有 24 和 21 秒。我不认为在这里管理线程(创建 2 个线程并加入它们)会花费这么多时间以上是关于使用多线程时性能几乎没有提升的主要内容,如果未能解决你的问题,请参考以下文章