1. 背景介绍

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

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

∫ 0 1 4 1 + x 2 d x

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. 随着线程数量的增加,并行效率的变化是剧烈波动的。

高性能计算-openmp-多线程缓存一致性(9)的更多相关文章

  1. Java的多线程机制系列:(二)缓存一致性和CAS

    一.总线锁定和缓存一致性 这是两个操作系统层面的概念.随着多核时代的到来,并发操作已经成了很正常的现象,操作系统必须要有一些机制和原语,以保证某些基本操作的原子性.首先处理器需要保证读一个字节或写一个 ...

  2. redis,缓存雪崩,粗粒度锁,缓存一致性

    1, redis单线程为什么快 io多路复用技术 单线程避免多线程的频繁切换问题 memcache缺点 kv形式数据 没有持久化mongodb 海量数据的访问效率 mr的计算模型文档就是类似json的 ...

  3. 缓存一致性协议 mesi

    m : modified e : exlusive s : shared i : invalid 四种状态的转换略过,现在讨论为什么有了这个协议,i++在多线程上还不是安全的. 两个cpu A B同时 ...

  4. C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI

    目录 1. 前言2 2. 结论2 3. volatile应用场景3 4. 内存屏障(Memory Barrier)4 5. setjmp和longjmp4 1) 结果1(非优化编译:g++ -g -o ...

  5. java并发编程(三)cpu cache & 缓存一致性

    一 cpu cache 1. cache的意义    为什么需要CPU cache?因为CPU的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源.所以cache的出 ...

  6. 缓存一致性协议(MESI)

    在目前主流的计算机中,cpu执行计算的主要流程如图所示: 数据加载的流程如下: 1.将程序和数据从硬盘加载到内存中 2.将程序和数据从内存加载到缓存中(目前多三级缓存,数据加载顺序:L3->L2 ...

  7. Volatile如何保证线程可见性之总线锁、缓存一致性协议

    基础知识回顾 下图给出了假想机的基本设计.中央处理单元(CPU)是进行算术和逻辑操作的部件,包含了有限数量的存储位置--寄存器(register),一个高频时钟.一个控制单元和一个算术逻辑单元. 时钟 ...

  8. 【Java虚拟机4】Java内存模型(硬件层面的并发优化基础知识--缓存一致性问题)

    前言 今天学习了Java内存模型第一课的视频,讲了硬件层面的知识,还是和大学时一样,醍醐灌顶.老师讲得太好了. Java内存模型,感觉以前学得比较抽象.很繁杂,抽象. 这次试着系统一点跟着2个老师学习 ...

  9. 简述伪共享和缓存一致性MESI

    什么是伪共享 计算机系统中为了解决主内存与CPU运行速度的差距,在CPU与主内存之间添加了一级或者多级高速缓冲存储器(Cache),这个Cache一般是集成到CPU内部的,所以也叫 CPU Cache ...

  10. 缓存一致性性协议MESI笔记

    概述 今天的笔记只是讲解一下MESI的概念和使用场景的介绍,MESI(Modified Exclusive Shared Or Invalid)也称为伊利诺斯协议,是一种广泛使用的支持协会策略的缓存一 ...

随机推荐

  1. 全网最适合入门的面向对象编程教程:41 Python 常用复合数据类型-队列(FIFO、LIFO、优先级队列、双端队列和环形队列)

    全网最适合入门的面向对象编程教程:41 Python 常用复合数据类型-队列(FIFO.LIFO.优先级队列.双端队列和环形队列) 摘要: 在 Python 中,队列(Queue)是一种常用的数据结构 ...

  2. 怎样在局域网中给网站作ssl认证,使其能以https协议访问(转)

    如果要在局域网达到效果需要满足以下几点要求: 1. 证书由可信任的CA机构颁发 2. 证书在有效期 3. 访问地址和证书的认证地址一致 说明: 1. 需要在局域网内构建CA机构 2. 证书的有效期建议 ...

  3. SimpleTranslationAIAgent:基于C#与LLM的翻译AI Agent

    基于C#与LLM通过简单对话即可实现文件到文件的翻译任务 该软件是MIT协议完全开源免费的,但是调用LLM的API可能需要费用,但是没关系,赛博菩萨硅基流动与智谱AI等都有免费的模型可调了. 这个Tr ...

  4. vue打包项目版本号自加

    原因 项目每次打包后都需要改动项目版本号,这个改动每次都需要在package.json中修改version,比较麻烦,到底有没有一种打包后版本号自加的办法. 方案 版本号自加其实可以使用fs修改文件来 ...

  5. Centos7.9安装Docker和Docker compose

    什么是docker环境 Docker环境是指在计算机中安装和配置了Docker引擎的运行环境.Docker是一种容器化平台,它提供了一种轻量级的虚拟化技术,能够将应用程序及其依赖项打包成一个独立的容器 ...

  6. 如何使用ChatGPT自带插件

    OpenAI的插件将ChatGPT连接到第三方应用程序.这些插件使ChatGPT能够与开发者定义的API进行交互,增强ChatGPT的能力,并使其能够执行广泛的操作.插件使ChatGPT能够做如下事情 ...

  7. 深度学习学习率(Learning Rate)lr理解

    现在是2024年4月23日13:54,在看代码了,嗯,不能逃避,逃避可耻,会痛苦,看不懂多看几遍多写一下就好了,不能逃避了哈,一点一点来就是了,我还有救. 如何理解深度学习中的学习率(Learning ...

  8. JavaScript – Rest Parameters & Spread Operator

    介绍 Rest 和 Spread 的共同点是语法都是 ... (点点点). 但它们的概念是相反的. 看例子体会: Rest Parameters 参考: 阮一峰 – rest 参数 rest para ...

  9. I found that CTH has no RP when i tried to reduce his RP

  10. mmap映射类型

    文件映射和匿名映射都是操作系统中对于内存映射的两种类型,主要应用于进程间的通信或者大量数据的处理. 文件映射,也就是内存映射文件,是把一个文件或者文件的一部分映射到进程的地址空间,它允许对文件进行随机 ...