首页 > 其他分享 >从单核到多核:揭秘多核CPU的高效利用策略

从单核到多核:揭秘多核CPU的高效利用策略

时间:2024-03-16 11:58:36浏览次数:25  
标签:缓存 单核 访问 多核 内存 L2 array CPU

        任何代码的执行都依赖 CPU,通常使用好 CPU 是操作系统的工作。然而当我们编写计算密集型程序时,CPU 的执行效率变得至关重要。由于 CPU 缓存由更快的 SRAM 构成(内存由 DRAM 构成),而且离 CPU 核心更近,如果运算时需要的输入数据是从 CPU 缓存读取,而不是内存中读取的,运算速度会变得更快。所以,了解CPU缓存对性能的影响,便能够有效的编写我们的代码,优化程序性能。

一、CPU 架构

        一个 CPU 处理器中一般有多个运行核心,我们把一个运行核心称为一个物理核,每个物理核都可以运行应用程序。每个物理核都拥有私有的一级缓存(L1 cache),包括一级指令缓存和一级数据缓存,以及私有的二级缓存(L2 cache)。这里提到一个概念,就是物理核的私有缓存。它其实是指缓存空间只能被当前这个物理核使用,其他物理核无法对这个缓存空间进行数据存取。

        程序执行时,会先将内存中的数据载入到共享的三级缓存中,再进入每颗核心独有的二级缓存中,最后进入最快的一级缓存,之后才会被 CPU 使用。

        因为 L1 和 L2 是每个物理核私有的,所以当数据或指令保存在 L1、L2 缓存时,物理核访问它们的延迟不超过 10 纳秒,速度非常快。如果把要运行的指令或存取的数据保存在 L1 和 L2 缓存的话,就能高速访问这些指令和数据了。

        但 L1 和 L2 缓存的大小受限于处理器的制造技术,一般只有 KB 级别,存不下太多的数据。如果 L1、L2 缓存中没有所需的数据,应用程序就需要访问内存来获取数据。而应用程序的访问延迟一般在百纳秒级别,是访问 L1、L2 缓存延迟的近 10 倍,不可避免地对性能造成影响。

        所以,不同的物理核会共享一个共同的三级缓存(L3),L3 缓存能够使用的存储资源比较多,所以一般比较大,能达到几 MB 到几十 MB,这就能让应用程序缓存更多的数据。当 L1、L2没有数据时,可以访问 L3,尽可能的避免访问内存。

        现在主流的 CPU 处理器,每个物理核通常都会运行两个超线程,也叫做逻辑核,同一个物理核共享 L1、L2 缓存。

        在多 CPU 架构上,应用程序可以在不同的处理器上运行,比如现在物理核1上运行,然后又切换到物理核2上运行。

        如果一个应用程序先在一个核上运行,并且把数据保存到了内存,然后又被调度到了另一个核上运行,此时应用程序在进行内存访问时,就需要访问之前核上的链接内存,这种访问属于远端访问。和访问自身直接的内存相比,远端访问会增加应用程序的延迟。

        如果能从自己的内存中获取到数据,称为缓存命中。那如何能提高缓存命中率呢?

二、提升 CPU 缓存命中率

        2.1 数据访问顺序对命中率的影响

        比如现在要遍历二维数组,其定义如下:

int[][] array;

        先提一个问题思考下,用 array[j][i] 和 array[i][j] 访问数组元素,哪一种性能更快?

for(i = 0; i < N; i+=1) {
   for(j = 0; j < N; j+=1) {
       array[i][j] = 0;
   }
}

        前者 array[j][i] 执行的时间是后者 array[i][j] 的 8 倍之多。

        为什么会有这么大的差距呢?这是因为二维数组 array 所占用的内存是连续的,比如若长度 N 的值为 2,那么内存中从前至后各元素的顺序是:

array[0][0],array[0][1],array[1][0],array[1][1]

        如果用 array[i][j] 访问数组元素,则完全与上述内存中元素顺序一致,因此访问 array[0][0]时,缓存已经把紧随其后的 3 个元素也载入了,CPU 通过快速的缓存来读取后续 3 个元素就可以。如果用 array[j][i] 来访问,访问的顺序就是:

array[0][0],array[1][0],array[0][1],array[1][1]

        此时内存是跳跃访问的,如果 N 的数值很大,那么操作 array[j][i] 时,是没有办法把 array[j+1][i] 也读入缓存的。

        为什么两者的执行时间有 7 8 倍的差距呢?

        其实这与 CPU Cache Line 相关,它定义了一次缓存数据的大小,Linux 可以通过coherency_line_size 配置查看它,通常是 64 字节。

        因此,当载入 array[0][0] 时,若他们占用的内存不足 64 字节,CPU 就会顺序的补足后续元素。顺序访问 array[i][j] 因为利用了这一特点。所以要比 arrary[j][i] 快。也正因为这样,当元素类型是 4 个字节的整数时,性能就会比 8 个字节的高精度浮点数时速度更快,因为缓存一次载入的元素会更多。

        因此,遇到这种遍历访问数组的情况时,按照内存布局顺序访问将带来很大的性能提升。

        在二维数组中,其实第一维元素存放的是地址,第二维存放的才是目标元素。由于 64 位操作系统的地址占用 8 字节(32位操作系统是4字节),因此,每批 Cache Line 最多也就能载入不到 8 个二维数组元素,所以性能差距大约接近 8 倍。

        2.2 提升指令缓存命中率

        加入有这样一个场景,有一个数组,数组内装的事随机数,接下来对他做两件事:一是循环遍历数组,判断每个数字是否小于 128,如果小于则把元素的值置为 0;二是将数组排序。那么,先排序再遍历速度快,还是先遍历再排序速度快呢?

        先给出答案:先排序的遍历时间只有后排序的三分之一。为什么会这样呢?这是因为循环中有大量的 if 条件分支,而 CPU含有分支预测器。

        当代码中出现 if、switch 等语句时,意味着此时至少可以选择跳转到两端不同的指令中去执行。如果分支预测期可以预测接下来要在哪段代码执行,就可以提前把这些执行放在缓存中,CPU 执行时就会很快。当数组中的数据完全随机时,分支预测期无法有效的工作,而数组有序时,分支预测期会动态地根据历史命中数据对未来进行预测,命中率就会非常高。

        2.3 提升多核 CPU 下的缓存命中率 

        前面都是面向一个 CPU 核心谈数据及指令缓存的,然而现代 CPU 几乎都是多核的。虽然三级缓存面向所有核心,但一、二级缓存是每颗核心独享的。我们知道,即使只有一个 CPU 核心,现代分时操作系统都支持许多进程同时运行。这是因为操作系统把时间切成了许多片,微观上各进程按时间片交替地占用 CPU,这造成宏观上看起来各程序同时在执行。

        因此,若进程 A 在时间片 1 里使用 CPU 核心 1,自然也填满了核心 1 的一、二级缓存,当时间片 1 结束后,操作系统会让进程 A 让出 CPU,基于效率并兼顾公平的策略重新调度 CPU 核心 1,以防止某些进程饿死。如果此时 CPU 核心 1 繁忙,而 CPU 核心 2 空闲,则进程 A 很可能会被调度到 CPU 核心 2 上运行,这样,即使我们对代码优化得再好,也只能在一个时间片内高效地使用 CPU 一、二级缓存了,下一个时间片便面临着缓存效率的问题。

        因此,操作系统提供了将进程或者线程绑定到某一颗 CPU 上运行的能力。如 Linux 上提供了 sched_setaffinity 方法实现这一功能,其他操作系统也有类似功能的 API 可用。

三、实战应用

        关于 CPU Cache Line 的应用其实非常广泛,如果你用过 Nginx,会发现它是用哈希表来存放域名、HTTP 头部等数据的,这样访问速度非常快,而哈希表里桶的大小如 server_names_hash_bucket_size,它默认就等于 CPU Cache Line 的值。由于所存放的字符串长度不能大于桶的大小,所以当需要存放更长的字符串时,就需要修改桶大小,但 Nginx 官网上明确建议它应该是 CPU Cache Line 的整数倍。

        如果对 Redis 的使用有非常苛刻的性能要求,在多核 CPU 场景下,一旦应用程序需要在一个新 CPU 核上运行,那么运行时信息就需要重新加载到新的 CPU 核上。而且,新的 CPU 核的L1、L2 缓存也需要重新加载数据核指令,这会导致程序的运行时间增加。这时我们可以将Redis实例绑定在某一个核上执行,避免多核间的切换。

        总结来说,更好的了解 CPU 的多核架构,高效利用多核 CPU 需要从系统架构、编程模型、数据结构、算法设计等多个角度综合考虑,确保工作负载能够在多个核心上均衡分布,有效减少串行执行和等待时间,从而提升整体系统的并发处理能力和执行效率。

        欢迎留言讨论并关注。

往期经典推荐

Synchronized同步锁的全方位剖析与实战运用-CSDN博客

揭秘操作系统内核:深入浅出进程阻塞与唤醒-CSDN博客

Spring Cloud + Nacos 引领服务治理新航向-CSDN博客

你真的了解Tomcat一键启停吗?-CSDN博客

Redis高性能的秘密原来在这,超越想象-CSDN博客

标签:缓存,单核,访问,多核,内存,L2,array,CPU
From: https://blog.csdn.net/qq_39209927/article/details/136699674

相关文章

  • Android 获取设备的CPU型号和设备型号
    原文:Android获取设备的CPU型号和设备型号-Stars-One的杂货小窝之前整的项目的总结信息,可能不太全,凑合着用吧,代码在最下面一节CPU型号数据华为:ro.mediatek.platformvivo:ro.vivo.product.platformoppo:ro.board.platform或ro.product.board三星:ro.board.pla......
  • Intel Lunar Lake失去超线程!但多核性能飙升1.5倍
    Intel将在今年晚些时候推出ArrowLake、LunarLake两套平台,工艺、架构基本相同,分别面向高性能和低功耗,一个意外变化就是不支持超线程。尽管如此,大家也不必为多核性能担忧。据最新曝料,17W功耗的LunarLake对比15W功耗的MeteorLake-U,CineBench23/5.4.5测试中,多核性能可提升几乎......
  • 在Docker中,如何控制容器占用系统资源(CPU,内存)的份额?
    在Docker中,你可以通过多种方式来控制容器对系统资源(如CPU和内存)的使用份额,以确保容器不会过度消耗宿主机的资源,并与其他容器公平地共享资源。以下是一些常用的方法:一.控制CPU资源CPU份额(CPUShares):Docker使用CPU份额来分配CPU时间。默认情况下,所有容器具有相同的CPU份额,这意......
  • 在Linux中,发现CPU负载过大,接下来怎么办?
    在Linux系统中,如果发现CPU负载过高,遵循以下步骤进行故障排查和解决:1.监控与确认问题使用top或htop命令实时查看当前CPU使用情况,并找出占用CPU较高的进程:top或者htop#需要先安装htop工具在top中按1可以看到每个单独的CPU核心的负载情况。使用ps或psaux......
  • 在Linux中,如何获取CPU的总核心数?
    在Linux中,可以通过几种不同的命令来获取CPU的总核心数。具体如下:查看物理CPU个数:可以通过查看/proc/cpuinfo文件来获取物理CPU的个数。使用以下命令:cat/proc/cpuinfo|grep"physicalid"|sort-u|wc-l这个命令会显示物理CPU的个数。查看每个物理CPU的核数:同样,可......
  • 在Linux中,如何查看占用CPU最多的进程?
    在Linux中,有多种方法可以查看占用CPU最多的进程。以下是几种常用的方法:使用top命令:top命令是Linux中常用的性能分析工具,可以实时显示系统中各个进程的资源占用情况,包括CPU使用率。打开终端,输入top命令,按下回车键。top命令会显示出当前系统中CPU使用率最高的进程列表。默认情......
  • CPU型号后面的字母分别是什么含义?
    笔记本CPU的处理器型号后缀U、P、H各自代表着不同的性能和用途。 U系列代表超低功耗(Ultra-LowPower),专为轻薄型笔记本电脑和长续航时间设备设计,适用于日常办公和轻度应用。P系列则代表高性能(Performance),具备更高的时钟频率和计算能力,适用于复杂应用程序、多任务处理和图形设......
  • CPU基础知识全面讲解,小白必备
    CPU是电脑中一个最重要的硬件之一,就像人类的大脑一样,它决定电脑的运算速度。无论是台式机还是笔记本,CPU的选购都至关重要。但是,众所周知,很多人对CPU的知识还不是很了解,为此笔记本电脑网精心准备了最新的CPU知识科普全面讲解,旨在帮助电脑小白更好地学习CPU知识,这篇内容涵盖了所有基......
  • 随笔-perf off-cpu 原理
    off-cpu:view#testincentos7echo1|sudotee/proc/sys/kernel/sched_schedstatsperfrecord-esched:sched_stat_sleep-esched:sched_switch-esched:sched_process_exit-p$(cat/var/run/cs.pid)-g-operf.data.rawsleep10perfinject-v-s-iperf.......
  • Docker之CPU和内存磁盘设置(八)
    前言Docker基本掌握使用方式,但是在限制其资源方面还没有接触。本篇就简单介绍一下有关CPU、内存等配置。让每个容器的资源分配更加合理,也能够避免一些容器遇到内存泄漏,CPU过载等问题影响其他容器的正常运行。一、配置文件Docker中,配置文件可以用来调整守护进程的行为,包......