Cache——让CPU更快地执行你的代码

news2025/2/4 11:36:51

02545132a8f944f0959305bf426efdb8.jpg


 概要

 

Cache对性能的影响

首先我们要知道,CPU访问内存时,不是直接去访问内存的,而是先访问缓存(cache)。

当缓存中已经有了我们要的数据时,CPU就会直接从缓存中读数据,而不是从内存中读。

CPU和缓存的关系如下:

42b947d35cb24fb1809cb0ec93ea5b56.png

 缓存分为一级、二级、三级,最靠近CPU的是一级缓存,最远的是内存,离CPU越近速度越快。

访问速度上,L1>L2>L3>内存,缓存比内存速度要快得非常多

如果CPU操作的数据在缓存中,则直接从缓存中读取,这个过程就叫缓存命中

因此提升性能的关键,就是要提高缓存命中率。下面来看如何提高缓存命中率。

提高数据缓存命中率

来看一个实例,有一个N*N的二维数组,例如:

int array[N][N];

现在用两个for循环遍历这个数组,访问每个元素的内容:

for(i = 0; i < N; i+=1) 
{ 
 for(j = 0; j < N; j+=1) 
 { 
  array[i][j] = 0;//速度快
        //array[j][i] = 0;//速度慢
 } 
}

有两种访问方式:array[i][j]array[j][i]

在性能上,array[i][j]会比array[j][i]执行地更快,并且速度相差8倍。

1、速度更快的原因

首先数组在内存上是连续的,假设N等于2,则array[2][2]在内存中的排布是:

array[0][0]、array[0][1]、array[1][0]、array[1][1]、

array[i][j]方式访问,即按内存中的顺序访问,当访问array[0][0]时,CPU就已经把数组的剩余三个数据(array[0][1]array[1][0]array[1][1])加载到了缓存当中。

当继续访问后三个元素时,CPU会直接从缓存中读取数据,而不需要从内存中读取(cache命中)。因此速度会很快。

如果以array[j][i]方式访问数组,则访问顺序为:

array[0][0]、array[1][0]、array[0][1]、array[1][1]

此时访问顺序是跳跃的,并不是按数组在内存中的的排布顺序来访问。如果N很大的话,那么执行array[j][i]时,array[j+1][i]的内容是没法读进缓存里的,等到要访问array[j+1][i]时就只能从内存中读取

所以array[j][i]的速度会慢于array[i][j]

2、速度相差8倍的原因

刚刚提到,如果这个二维数组的N很大,array[j+1][i]的内容是没法读到缓存里的,那CPU一次能够将多少数据加载进缓存里呢?

这个其实跟cache line有关,cache line代表缓存一次载入数据的大小。可以通过以下命令查看cache line为多大:

cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

1e842eaf95ab4e34ac8184d4f257ae20.png

cache line64,代表CPU缓存一次数据的大小为64字节。

当访问array[0][0]时,该元素所占用的字节数不到64字节,CPU就会按顺序补足后续元素,就会把后面的array[0][1]array[1][0]等内容一起读到缓存里,直到凑够64字节。

正因如此,按顺序访问的array[i][j]才会比不按顺序访问的array[j][i]速度快。

再看看为什么速度相差8。我们知道,二维数组中,第一维元素放的是地址,第二维元素才是数据。64位系统中,地址占用8个字节,cache line64的话,地址已经占用了8字节,那每个cache line最多能载入不到8个二维数组元素,N很大的情况下,他们的性能平均下来就会相差将近8倍。

结论:按内存布局顺序访问,可以提高数据缓存命中率。

提高指令缓存命中率

前面说的是数据缓存,现在看看指令缓存命中率该如何提高。

有一个数组array,数组元素内容为0-255之间的随机数:

int array[N];
for (i = 0; i < TESTN; i++) 
 array[i] = rand() % 256;

现在,要把数组中数字小于128的元素置为0,并且对数组排序。

大家应该都能想到,有两种方法:

  • 先遍历数组,把小于128的元素置为0,然后排序

  • 先对数组排序再遍历数组,把小于128的元素置为0。

for(i = 0; i < N; i++) {
 if (array [i] < 128) 
  array[i] = 0;
}
sort(array, array +N);

先排序后遍历的速度会比较快,为什么?

因为在for循环中会执行很多次if分支判断语句,而CPU拥有分支预测器。

如果分支预测器可以预测接下来要执行的分支(执行if还是执行else),那么就可以提前把这些指令放到缓存中,CPU执行的时候就会很快了。

如果一个数组的内容完全随机的话,那么分支预测器就很难进行正确的预测。但如果数组内容是有序的,它就会根据历史命中数据的情况对未来进行预测,那命中率就会很高,所以先排序后遍历的速度会比较快。

怎么验证指令缓存命中率的情况呢?

Linux下,可以使用Perf性能分析工具进行验证。通过-e选项,指定branch-loadsbranch-loads-misses事件,可以分别统计出分支预测成功的次数分支预测失败的次数,通过L1-icache-load-misses事件也能统计一级缓存中指令未命中的次数。但是,这些性能事件都属于硬件事件,perf工具能否统计这些事件取决于CPU是否支持以及芯片原厂是否去实现了该接口,我看很多都是不支持或者没实现的。

另外,在Linux内核中,可以看到大量的likelyunlikely宏,并且它们都出现if语句中,这两个宏的作用就是为了提高性能

这是显示预测概率的宏,如果你觉得CPU的分支预测不准,但if中条件为"真"的概率很高,那么你就可以使用likely()括起来,以此提升性能。

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
if (likely(a == 1)) …

提高多核CPU下的缓存命中率

首先要清楚,一级缓存、二级缓存是每颗核心独享的,三级缓存则面向所有核心。

但多核CPU下的系统有个特点,存在CPU核心迁移问题

例如,进程A在时间片内使用CPU核心1,自然填满了CPU核心1的一、二级缓存,但基于调度策略,时间片结束后,CPU核心1会被让出,防止某些进程饿死。如果此时CPU核心1很忙,那么进程A很可能就会被调度到CPU核心2上运行。这样的话,无论我们怎么优化代码,也只能在一个时间片内高效地使用CPU一、二级缓存,下一个时间片就会面临缓存效率问题。

这种情况下,可以考虑将进程绑定CPU运行

perf工具也提供了这类性能事件的统计,叫cpu-migrations,即CPU迁移次数。CPU迁移次数多的话,缓存效率就会低。

将进程绑定CPU运行,性能也会得到提升。

 

总结

这些是CPU缓存对性能的影响,这已经是很底层的性能优化了,不论什么编程语言都是有效的。

真正了解缓存,相信你对底层的认识会有很大帮助。

  • 提升数据缓存命中率:顺序地操作连续内存数据

  • 提升指令缓存命中率:有规律的条件分支

  • 提升多核CPU的缓存命中率:考虑将进程绑定CPU运行

 

欢迎点赞收藏转发,感谢🙏

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/761886.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python基础编程案例之编写交互式博客系统

文章目录 1、博客系统的需求描述2、面向用户层面各功能的设计思路与代码编写2.1.定义文章库2.2.文章的发布2.3.删除文章2.4.修改文章的标题以及内容2.5.在评论区添加评论2.6.删除文章中的某条评论2.7.阅读文章2.8.对文章进行点赞2.9.对文章进行收藏2.10.对文章进行打赏2.11.查询…

WorkPlus AI助理:结合ChatGPT对话能力与企业数据,助力企业级AI构建!

WorkPlus AI助理是基于GPT和私有数据构建智能知识库和个性化AI&#xff0c;能够帮助企业生成博客、白皮书、社交媒体帖子、新闻稿等等&#xff0c;这些内容可以用于推广产品、服务&#xff0c;增强品牌形象和知名度。此外&#xff0c;利用WorkPlus AI助理还可以生成电子邮件、利…

基于linux串口实现语音刷抖音

目录 1.开发逻辑图及模块 2.编程实现语音和开发板通信 3.手机接入Linux热拔插相关,打开手机开发者模式允许USB调试 4.用shell指令来操作手机屏幕&#xff0c;模拟手动滑屏幕 5.最终主程序代码 1.开发逻辑图及模块 逻辑图&#xff1a; 模块 &#xff08;1&#xff09;语音…

读kafka生产端源码,窥kafka设计之道(上)

1. kafka 高吞吐之道-------异步提交批量发送 简约的发送接口----后面隐藏着并不简单的设计 kafka发送消息的接口非常简约&#xff0c;在简约的表面上&#xff0c;其背后却并不简单。先看下发送接口 kafkaProducer.send(new ProducerRecord(topic,msg), new Callback() {Ove…

8、链路层以太网协议,ARP协议32

网络层IP协议描述了通信中的起点到终点&#xff0c;但是数据不是飞过去的&#xff0c;是经过了大量的中间节点转发完成的。 一、以太网协议 1、MAC地址 物理硬件地址&#xff0c;是每一块网卡在出厂时设定的地址&#xff0c;固定且不可修改&#xff08;早期&#xff0c;现在可…

当DevOps遇到AI,黑马迎来3.0时代丨IDCF

随着GhatGPT的爆火&#xff0c;人工智能和研发效能&#xff0c;无疑成为了2023的两个最重要的关键词。大规模语言模型LLM和相关应用的快速发展正在对研发团队的工作方式产生深远影响&#xff0c;这几乎象征着新的生产力革命的到来。 那么&#xff0c;作为一名工程师&#xff0…

Chat GPT是什么,初学者怎么使用Chat GPT,需要注意些什么

目录 Chat GPT是什么 初学者怎么使用Chat GPT 使用Chat GPT需要注意什么 一些简单的prompt示例 Chat GPT是什么 Chat GPT是由OpenAI开发的一种大型语言模型&#xff0c;它基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构。GPT是一种基于深度学习的…

【Matlab】智能优化算法_遗传算法GA

【Matlab】智能优化算法_遗传算法GA 1.背景介绍2.数学模型3.文件结构4.详细代码及注释4.1 crossover.m4.2 elitism.m4.3 GeneticAlgorithm.m4.4 initialization.m4.5 Main.m4.6 mutation.m4.7 selection.m4.8 Sphere.m 5.运行结果6.参考文献 1.背景介绍 遗传算法&#xff08;Ge…

(学习笔记)TCP 为什么是三次握手?不是两次、四次?

常规回答&#xff1a;“因为三次握手才能保证双方具有接收和发送的能力” 原因一&#xff1a;避免历史连接 三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。 假设&#xff1a;客户端先发送了SYN(seq90)报文&#xff0c;然后客户端宕机了&#xff0c;而且这个SYN报…

一种电动汽车智能充电及收费云平台管理方案

摘要&#xff1a;对于电动汽车来说&#xff0c;主要是借助电力作为能源&#xff0c;有着多方面的优点。但是也存在着一定的问题&#xff0c;尤其在续航能力上相对较差。因此&#xff0c;在实际工作中要正确利用现代科学技术&#xff0c;让电动汽车实现智能充电。在研究中所涉及…

JavaScript中的let、const和var

在 JavaScript 中&#xff0c;let、const 和 var 是用于声明变量的关键字&#xff0c;在使用时有以下区别&#xff1a; 作用域&#xff1a;let 和 const 声明的变量具有块级作用域&#xff0c;只能在声明它的块中访问。而 var 声明的变量则是函数作用域或全局作用域&#xff0…

MS31001低压 5V DC 电机驱动

MS31001 是一款低压 5V 直流电机驱动芯片&#xff0c;为摄像机、 消费类产品、玩具和其他低压或者电池供电的运动控制类应用 提供了集成的电机驱动解决方案。 MS31001 能提供高达 0.8A 的输出电流。可以工作在 2.0~5.5V 的电源电压上。 MS31001 具有 PWM &#xff08…

NSSCTF随机一题

[GXYCTF 2019]Ping Ping Ping 应该是命令注入的题目&#xff0c;直接先ping一下本地&#xff0c;发现url栏有ip传参变化 接着就是利用命令注入符&#xff0c;尝试注入 它好像真的在ping&#xff0c;执行得特别慢&#xff0c;利用ls&#xff0c;查询到了flag文件 发现空格过…

LeetCode·每日一题·415. 字符串相加·模拟

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/add-strings/solutions/2347085/mo-ni-zhu-shi-chao-ji-xiang-xi-by-xun-ge-fges/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商…

自适应巡航控制系统研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 据统计, 我国交通事故造成的伤亡人数每年超过10万人, 其中驾驶员人为原因 (疲劳、酒驾、误操作等) 所致事故逐渐升高.汽车交通…

Python多线程同步编程Event使用方法

一、Event简介 Event是Python多线程同步编程中的一种同步原语&#xff0c;它可以帮助我们协调多个线程的操作&#xff0c;以达到线程间传递信号、同步操作等目的。 Event主要有两种状态&#xff1a;已设置和未设置。当Event被设置时&#xff0c;所有等待该Event的线程都会被唤…

数据结构-Java逆天操作

本文章会对Java线性表的相关知识进行讲解&#xff0c;也会以Java代码示例来进行解释 这里写目录标题 本文章会对Java线性表的相关知识进行讲解&#xff0c;也会以Java代码示例来进行解释对线性表的讲解分析定义可以表示为线性表可以用多种方式来表示和实现&#xff0c;常见的实…

数据结构与算法——什么是单链表,链式存储结构详解

前面详细地介绍了顺序表&#xff0c;本节给大家介绍另外一种线性存储结构——链表。 链表&#xff0c;别名链式存储结构或单链表&#xff0c;用于存储逻辑关系为 "一对一" 的数据。与顺序表不同&#xff0c;链表不限制数据的物理存储状态&#xff0c;换句话说&#…

基于 SpringBoot 的高校宿舍管理系统设计与开发

1.引言 宿舍是大学生学习与生活的主要场所之一&#xff0c;宿舍管理是高校学工管理事务中 尤为重要的一项。随着我国高校招生规模的进一步扩大&#xff0c;学生总体人数的不断增加&#xff0c; 宿舍管理工作变得愈加沉重和琐碎&#xff0c;学生宿舍信息的采集、汇总、统计与分析…

火山引擎A/B测试“广告投放实验”基础能力重构实践 (DataFunTalk渠道)

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 企业在进行营销推广时&#xff0c;广告投放通常是必备环节之一。为了避免投放“乱烧钱”&#xff0c;在大规模投放前&#xff0c;企业和广告优化师都会希望在多种广…