使用多个线程时性能几乎没有提高
Little performance increasing when using multiple threads
我正在实现求解线性系统的多线程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 memoryn");
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 threadn");
return 1;
}
}
// Join threads
for(i = 0; i < THREADS_NUM; ++i)
{
if(pthread_join(workers[i], NULL))
{
fprintf(stderr, "Error creating threadn");
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。
您首先遍历列,在内部循环中处理行,这意味着您的缓存未命中量远远超过了需要的数量。
我的原始代码结果(第一个没有填充的版本):
$ ./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上升时,你在最内部的循环中处理的项目越来越少,所以这仍然很重要。
第2版:(删除了块解决方案,因为它实际上产生了不同的结果)-但仍然可以利用块来提高缓存性能。
你是否谈到了13%的速度,但你的微积分运算所花费的时间是多少,而不是程序的其余部分。
您可以开始只估计在calcul方法上传递的时间,而不估计线程管理的时间。在线程管理中,您可能会损失重要的一部分时间。这可以解释你获得的小速度。
在另一部分,使用2个线程可以获得50%的速度,这是完全不可能获得的。
- 对减去uint16_t值几乎没有困惑
- write() 和 read() 中几乎没有混淆
- 有没有办法组合多个几乎相同的功能?
- 单精度矩阵运算的特征性能 AVX 与 SSE 没有区别?
- 如何在没有性能命中的情况下抽象SIMD代码来处理不同的数据类型
- 从旧的 C 样式指针移动到C++智能指针,代码几乎没有变化
- 几乎所有形式的字符串比较都在C 11中没有起作用
- OpenMP:嵌套的 for 循环,执行时间几乎没有任何差异
- 编译带有活动霓虹灯标志的代码几乎没有任何改进(甚至恶化)
- 为什么我遇到此代码的分段错误?此代码几乎没有运行,因此构造函数或复制构造函数中可能存在错误
- 在C++中使用 I/O 文件几乎没有问题(战略客户没有帮助)
- 为什么这种令人尴尬的并行算法的性能没有随着多线程而提高
- 几乎平均拆分浮点数,没有损失
- 使用多个线程时性能几乎没有提高
- 哪些集合方法具有更好的性能:有或没有"const&"?
- 类的类设计几乎没有什么不同
- 批处理模式在Caffe -没有性能增益
- 两个几乎相同循环的性能差异
- 对于编译时的1.constructor和2.array定义,几乎没有什么疑问
- 线程数越多(没有同步),性能就越低