你不好奇 CPU 是如何执行任务的?


你不好奇 CPU 是如何执行任务的?

文章插图
作者 | 小林coding
来源 | 小林coding(ID:CodingLin)
你不好奇 CPU 是如何执行任务的?

文章插图
 
前言你清楚下面这几个问题吗?
  • 有了内存 , 为什么还需要 CPU Cache?
  • CPU 是怎么读写数据的?
  • 如何让 CPU 能读取数据更快一些?
  • CPU 伪共享是如何发生的?又该如何避免?
  • CPU 是如何调度任务的?如果你的任务对响应要求很高 , 你希望它总是能被先调度 , 这该怎么办?

这篇 , 我们就来回答这些问题 。
你不好奇 CPU 是如何执行任务的?

文章插图
你不好奇 CPU 是如何执行任务的?

文章插图
 
CPU 如何读写数据的?先来认识 CPU 的架构 , 只有理解了 CPU 的 架构 , 才能更好地理解 CPU 是如何读写数据的 , 对于现代 CPU 的架构图如下:
你不好奇 CPU 是如何执行任务的?

文章插图
可以看到 , 一个 CPU 里通常会有多个 CPU 核心 , 比如上图中的 1 号和 2 号 CPU 核心 , 并且每个 CPU 核心都有自己的 L1 Cache 和 L2 Cache , 而 L1 Cache 通常分为 dCache(数据缓存) 和 iCache(指令缓存) , L3 Cache 则是多个核心共享的 , 这就是 CPU 典型的缓存层次 。
上面提到的都是 CPU 内部的 Cache , 放眼外部的话 , 还会有内存和硬盘 , 这些存储设备共同构成了金字塔存储层次 。如下图所示:
你不好奇 CPU 是如何执行任务的?

文章插图
从上图也可以看到 , 从上往下 , 存储设备的容量会越大 , 而访问速度会越慢 。至于每个存储设备的访问延时 , 你可以看下图的表格:
你不好奇 CPU 是如何执行任务的?

文章插图
你可以看到 ,  CPU 访问 L1 Cache 速度比访问内存快 100 倍 , 这就是为什么 CPU 里会有 L1~L3 Cache 的原因 , 目的就是把 Cache 作为 CPU 与内存之间的缓存层 , 以减少对内存的访问频率 。
CPU 从内存中读取数据到 Cache 的时候 , 并不是一个字节一个字节读取 , 而是一块一块的方式来读取数据的 , 这一块一块的数据被称为 CPU Line(缓存行) , 所以 CPU Line 是 CPU 从内存读取数据到 Cache 的单位 。
至于 CPU Line 大小 , 在 linux 系统可以用下面的方式查看到 , 你可以看我服务器的 L1 Cache Line 大小是 64 字节 , 也就意味着 L1 Cache 一次载入数据的大小是 64 字节 。
你不好奇 CPU 是如何执行任务的?

文章插图
那么对数组的加载 ,  CPU 就会加载数组里面连续的多个数据到 Cache 里 , 因此我们应该按照物理内存地址分布的顺序去访问元素 , 这样访问数组元素的时候 , Cache 命中率就会很高 , 于是就能减少从内存读取数据的频率 ,  从而可提高程序的性能 。
但是 , 在我们不使用数组 , 而是使用单独的变量的时候 , 则会有 Cache 伪共享的问题 , Cache 伪共享问题上是一个性能杀手 , 我们应该要规避它 。
接下来 , 就来看看 Cache 伪共享是什么?又如何避免这个问题?
现在假设有一个双核心的 CPU , 这两个 CPU 核心并行运行着两个不同的线程 , 它们同时从内存中读取两个不同的数据 , 分别是类型为 long 的变量 A 和 B , 这个两个数据的地址在物理内存上是连续的 , 如果 Cahce Line 的大小是 64 字节 , 并且变量 A 在 Cahce Line 的开头位置 , 那么这两个数据是位于同一个 Cache Line 中 , 又因为 CPU Line 是 CPU 从内存读取数据到 Cache 的单位 , 所以这两个数据会被同时读入到了两个 CPU 核心中各自 Cache 中 。
你不好奇 CPU 是如何执行任务的?

文章插图
我们来思考一个问题 , 如果这两个不同核心的线程分别修改不同的数据 , 比如 1 号 CPU 核心的线程只修改了 变量 A , 或 2 号 CPU 核心的线程的线程只修改了变量 B , 会发生什么呢?


推荐阅读