前言
今天在使用OpenMP与C++进行多线程编程的初次尝试时,出现了一个与预想相悖的现象:
在对一个for循环使用多线程运行时,随着线程数的增加,程序的运行时间反而在增加。
虽然可能老手一听这个说法就知道问题出在哪里,但是对于我这个刚接触多线程编程的菜鸟来说,我花费了许多的时间在思考和解决这个疑惑上,最终找到了问题所在。
因此我在这里总结一下自己的思路。
一、代码分析
首先先展示一下自己的问题代码。
#include<stdio.h>
#include<omp.h>
#include<time.h>
void comput(float* A,float* B,float* C)//两个矩阵相乘传统方法
{
int x,y;
for(y=0;y<4;y++)
for(x=0;x<4;x++)
C[4*y+x] = A[4*y+0]*B[4*0+x] + A[4*y+1]*B[4*1+x] + A[4*y+2]*B[4*2+x] + A[4*y+3]*B[4*3+x];
}
int main()
{
double duration;
clock_t s,f;
int x=0;
int y=0;
int n=0;
int k=0;
float A[]={1,2,3,4,
5,6,7,8,
9,10,11,12,
13,14,15,16};
float B[]={0.1f,0.2f,0.3f,0.4f,
0.5f,0.6f,0.7f,0.8f,
0.9f,0.10f,0.11f,0.12f,
0.13f,0.14f,0.15f,0.16f};
float C[16];
s= clock();
for(n=0;n<1000000;n++)
comput(A,B,C);
f=clock();
duration = (double)(f - s)/CLOCKS_PER_SEC;
printf("Serial :%f\n",duration);
//parallel 2
s = clock();
#pragma omp parallel for num_threads(2)
for(n=0;n<1000000;n++)
comput(A,B,C);
f = clock();
duration = (double)(f - s)/CLOCKS_PER_SEC;
printf("Parallel 2 :%f\n",duration);
//parallel 4
s = clock();
#pragma omp parallel for num_threads(4)
for(n=0;n<1000000;n++)
comput(A,B,C);
f = clock();
duration = (double)(f - s)/CLOCKS_PER_SEC;
printf("Parallel 4 :%f\n",duration);
s = clock();
#pragma omp parallel for num_threads(8)
for(n=0;n<1000000;n++)
comput(A,B,C);
f = clock();
duration = (double)(f - s)/CLOCKS_PER_SEC;
printf("Parallel 8 :%f\n",duration);
s = clock();
#pragma omp parallel for num_threads(16)
for(n=0;n<1000000;n++)
comput(A,B,C);
f = clock();
duration = (double)(f - s)/CLOCKS_PER_SEC;
printf("Parallel 16 :%f\n",duration);
return 0;
}
这个程序其实非常的简单,它由一个compute函数
作为核心运行代码组成,其用于实现两个矩阵的乘法。然后for循环循环执行 compute函数
100w次,并统计执行这个循环的时间。
这个循环共有五份,分别使用单线程,2线程,4线程,8线程,16线程执行,并输出执行时间
二、问题分析
1.运行结果
运行结果如下
Serial :0.051000
Parallel 2 :0.080000
Parallel 4 :0.082000
Parallel 8 :0.113000
Parallel 16 :0.164000
可以很明显的看到,随着线程数的增多,运行时间却也在增加,我也曾多次重新运行这个代码,最好的情况也不过是多线程的运行时间和单线程相同。并没有做到多线程的并行以减少时间的目标。
2.思考分析
其实明眼人一看代码就看得到代码本身是有漏洞的了,但是我当时并没有发现,还是后面做另一个实验发现的,这个代码其很大的线程安全问题。
它能够正常运行真的多亏了minGW的编译器能够自动优化上锁。
说到上锁,这也是为什么我的代码在多线程的时候效率下降的原因:
在上述代码中,所有的线程都会对同一个内存数组 C 进行写操作,
学过操作系统的都知道,这个写操作是不能并行的,因为对同一个地址进行无序的写会导致结果的不可控,所以在这类操作时我们需要上锁,只有一个线程或者进程访问完之后,下一个进程或者线程才能够再访问,并且这个上锁的过程都是要花费时间和资源的。
我道这里才发现了,由于这个无法并行的写操作,导致众多的线程在这里都是“顺序执行”,而不是并行的,并且由于频繁的上锁操作,导致了更多的时间与资源的浪费,这才导致了一开始的错误
总结
今天的这个问题让我记住了,在编写并行程序时,一定要注意操作是否能够并行,尽量不要让多个线程同时访问相同的内存。