高性能计算-openmp-多线程缓存一致性(9)

1. 背景介绍

L1 L2 cache是单核独享,L3是多核共享。如果多线程访问共享一维数组的连续元素,先读入第一个线程的L1 缓存中,其他线程访问缓存不命中需要加载,并且数据的更改后,标记为脏数据,其他线程访问cacheline中相邻地址需要先写回内存,再读入目标L1 cache,效率低。使用三份代码,测试多线程计算效率。

2. 测试:定积分划分矩形求π,迭代次数越高精度越高。定积分如下:

0141+x2dx

2.1一维数组测试代码:

// 目标:openmp 定积分划分矩形求π,多线程交替计算,使用一维数组sumH,
// 缺点:多线程会读写数组,缓存不一致,性能下降
#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); //划分区间的宽度
// 设置线程数量,根据线程id交替计算矩形长度之和
omp_set_num_threads(NT);
gettimeofday(&start,NULL); //开始时间
#pragma omp parallel
{
double x=0; //x 坐标
int tid = omp_get_thread_num(); //线程id
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。

// 目标:openmp 定积分划分矩形求π,多线程交替计算,使用一维数组sumH,
// 使用二维数组填充cacheline
#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]; //存储各个线程矩形的高之和,使用二维数组每行存储到一个cacheline
memset(sumH,0,sizeof(double)*NT*8);
double step = 1.0/(double)(N); //划分区间的宽度
// 设置线程数量,根据线程id交替计算矩形长度之和
omp_set_num_threads(NT);
gettimeofday(&start,NULL); //开始时间
#pragma omp parallel
{
double x=0; //x 坐标
int tid = omp_get_thread_num(); //线程id
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使用私有变量:多线程使用局部变量,最后再赋值给共享数组的元素,减少每个线程对共享变量数组的访问次数。

// 目标:openmp 定积分划分矩形求π,多线程交替计算,使用一维数组sumH,
// 使用局部变量
#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); //划分区间的宽度
// 设置线程数量,根据线程id交替计算矩形长度之和
omp_set_num_threads(NT);
gettimeofday(&start,NULL); //开始时间
#pragma omp parallel
{
double sum=0; //使用局部变量
double x=0; //x 坐标
int tid = omp_get_thread_num(); //线程id
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. 随着线程数量的增加,并行效率的变化是剧烈波动的。

posted @   安洛8  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
点击右上角即可分享
微信分享提示