1. 背景介绍
L1 L2 cache是单核独享,L3是多核共享。如果多线程访问共享一维数组的连续元素,先读入第一个线程的L1 缓存中,其他线程访问缓存不命中需要加载,并且数据的更改后,标记为脏数据,其他线程访问cacheline中相邻地址需要先写回内存,再读入目标L1 cache,效率低。使用三份代码,测试多线程计算效率。
2. 测试:定积分划分矩形求π,迭代次数越高精度越高。定积分如下:
2.1一维数组测试代码:
| |
| |
| |
| #include <stdio.h> |
| #include <omp.h> |
| #include <string.h> |
| #include <sys/time.h> |
| |
| #define NT 16 |
| #define N 50000000 |
| |
| void main() |
| { |
| struct timeval start,end; |
| float time; |
| double pi=0; |
| double sumH[NT]; |
| memset(sumH,0,sizeof(double)*NT); |
| double step = 1.0/(double)(N); |
| |
| omp_set_num_threads(NT); |
| gettimeofday(&start,NULL); |
| #pragma omp parallel |
| { |
| double x=0; |
| int tid = omp_get_thread_num(); |
| int nts = omp_get_num_threads(); |
| for(int i=tid;i<N;i+=nts) |
| { |
| x = (i+0.5)*step; |
| sumH[tid] += 4.0/(1+x*x); |
| } |
| } |
| for(int i=0;i<NT;i++) |
| pi += sumH[i]; |
| pi = step * pi; |
| gettimeofday(&end,NULL); |
| time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6; |
| printf("ths %d useSec %f pi %18.15f\n",NT,time,pi); |
| return; |
| } |
2.2二维数组测试代码:查询 L1cache line 大小。将数组变为二维数组,每个cache line对应一个元素并0填充,每个线程访问一个 cache line。
| |
| |
| |
| #include <stdio.h> |
| #include <omp.h> |
| #include <string.h> |
| #include <sys/time.h> |
| |
| #define NT 16 |
| #define N 50000000 |
| |
| void main() |
| { |
| struct timeval start,end; |
| float time; |
| double pi=0; |
| double sumH[NT][8]; |
| memset(sumH,0,sizeof(double)*NT*8); |
| double step = 1.0/(double)(N); |
| |
| omp_set_num_threads(NT); |
| gettimeofday(&start,NULL); |
| #pragma omp parallel |
| { |
| double x=0; |
| int tid = omp_get_thread_num(); |
| int nts = omp_get_num_threads(); |
| for(int i=tid;i<N;i+=nts) |
| { |
| x = (i+0.5)*step; |
| sumH[tid][0] += 4.0/(1+x*x); |
| } |
| } |
| for(int i=0;i<NT;i++) |
| pi += sumH[i][0]; |
| pi = step * pi; |
| gettimeofday(&end,NULL); |
| time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6; |
| printf("ths %d useSec %f pi %18.15f\n",NT,time,pi); |
| return; |
| } |
2.3使用私有变量:多线程使用局部变量,最后再赋值给共享数组的元素,减少每个线程对共享变量数组的访问次数。
| |
| |
| |
| #include <stdio.h> |
| #include <omp.h> |
| #include <string.h> |
| #include <sys/time.h> |
| |
| #define NT 16 |
| #define N 50000000 |
| |
| void main() |
| { |
| struct timeval start,end; |
| float time; |
| double pi=0; |
| double sumH[NT]; |
| memset(sumH,0,sizeof(double)*NT); |
| double step = 1.0/(double)(N); |
| |
| omp_set_num_threads(NT); |
| gettimeofday(&start,NULL); |
| #pragma omp parallel |
| { |
| double sum=0; |
| double x=0; |
| int tid = omp_get_thread_num(); |
| int nts = omp_get_num_threads(); |
| for(int i=tid;i<N;i+=nts) |
| { |
| x = (i+0.5)*step; |
| sum += 4.0/(1+x*x); |
| } |
| sumH[tid] = sum; |
| } |
| for(int i=0;i<NT;i++) |
| pi += sumH[i]; |
| pi = step * pi; |
| gettimeofday(&end,NULL); |
| time = end.tv_sec-start.tv_sec+(end.tv_usec-start.tv_usec)/1e6; |
| printf("ths %d useSec %f pi %18.15f\n",NT,time,pi); |
| return; |
| } |
3. 测试数据
3.1 耗时:单位为秒,每组参数测试四次取均值
线程数 |
1 |
2 |
4 |
8 |
16 |
访问共享一维数组元素 |
0.59455275 |
0.83000775 |
0.55785925 |
0.08166325 |
0.11506225 |
数组元素填充为二维,每个元素放在一个cacheline大小的一维数组中 |
0.61837 |
0.54942225 |
0.37650275 |
0.19830225 |
0.09455275 |
使用线程私有局部变量 |
0.4499215 |
0.22541475 |
0.113015 |
0.0571045 |
0.037793 |
3.2 加速比
线程数 |
1 |
2 |
4 |
8 |
16 |
访问共享一维数组元素 |
|
0.716321926 |
1.065775552 |
7.280542349 |
5.167226871 |
数组元素填充为二维,每个元素放在一个cacheline大小的一维数组中 |
|
1.125491368 |
1.642405002 |
3.118320644 |
6.539947278 |
使用线程私有局部变量 |
|
1.99597187 |
3.981077733 |
7.878914972 |
11.90488979 |
3.3 并行效率
线程数 |
1 |
2 |
4 |
8 |
16 |
访问共享一维数组元素 |
|
0.358160963 |
0.266443888 |
0.910067794 |
0.322951679 |
数组元素填充为二维,每个元素放在一个cacheline大小的一维数组中 |
|
0.562745684 |
0.410601251 |
0.389790081 |
0.408746705 |
使用线程私有局部变量 |
|
0.997985935 |
0.995269433 |
0.984864371 |
0.744055612 |
4. 数据分析
a. 随着线程数的增加除了原始代码效率会降低,优化代码效率均有明显提升,使用线程私有变量的效率最高。
b. 加速比并不会随着线程数的增加而线性增加。
c. 随着线程数量的增加,并行效率的变化是剧烈波动的。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂