我正在实现解决线性系统的多线程Jordan-Gauss方法,我发现在两个线程上运行所花费的时间比在单线程上运行的时间少约15%而不是理想的50%.所以我写了一个复制这个的简单程序.在这里,我创建一个矩阵2000x2000,并为每个线程提供2000/THREADS_NUM行,以便对它们进行一些计算.
#include#include #include #include #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://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通用,CPU Intel Core i5 4200M(两个带有多线程的物理内核),但我也在其他两台机器上测试了它,结果相同.
编辑:
如果我更换a[j*row_length + k] -= 2.;
有a[0] -= 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 :(删除了块解决方案,因为它实际上产生了不同的结果) - 但仍然应该可以利用块来提高缓存性能.