CPU 应该 搞 0 级 Cache , 而不是 大寄存器

CPU 应该 搞 0 级 Cache ,  而不是 大寄存器  。

 

具体的说,   是 CPU 应该 搞  精简指令集 RISC  和   0 级 Cache ,    而 不是 大寄存器   。

 

0 级 Cache 也可以称为   L0 Cache   。

 

0 级 Cache 是 离 CPU 最近 的 Cache,  访问 只需要 1 个 时钟周期,   和 寄存器 一样  。

 

那  0 级 Cache  和 寄存器 有 什么 区别  呢   ?

 

0 级 Cache  在 内存 地址编制 内,  和  一级 Cache 、二级 Cache 、三级 Cache 、内存 在 一个 统一 的 地址空间 里,  按 统一 的 地址管理  。

 

而 寄存器 是 不在 内存 地址编制 里 的    。

 

0 级 Cache  从 下级存储 (一级 Cache 、二级 Cache 、三级 Cache 、内存)   载入载出 哪些 数据 是 完全 由 程序员 控制 的,  具体的, 是 完全 由 程序员 用 汇编指令控制的  。

 

这是 和   一级 Cache 、二级 Cache 、三级 Cache   的 不同  。

 

一级 Cache 、二级 Cache 、三级 Cache   载入载出  哪些 数据 是由  CPU 自己决定的, 比如 根据 命中算法,  程序员 无权干涉  。

 

程序员 用 指令  map_in   将 内存(一级 Cache 、二级 Cache 、三级 Cache) 地址 和 数据 映射 进  0 级 Cache ,   如果 0 级 Cache 里 的 存储单元 原来已经 映射 了 地址 和 数据,  此时 将 新的 地址 映射 到 这个 存储单元,   则 旧 的 数据 将 替换 为 新 的 数据,  旧 的 映射地址 将 映射 成 新 的 地址,   如果 旧 的 数据 被 修改过,  则 要 先 写回 对应 的 内存(一级 Cache 、二级 Cache 、三级 Cache) 地址  ,  这 称为 map_out ,     也可以称为  载出  。

 

map_in  也可以 称为 载入  。

 

 

0 级 Cache 的 好处 是 :

 

1    指针取值( * 指针) 和 指针字段( 指针 -> 字段  ) 可以 享有 和 局部变量 一样 的 寄存器优化 的 待遇 

      寄存器优化 就是 把 常用 的 数据 存在 寄存器 里 反复使用  。

      在 寄存器 架构下,   指针取值( * 指针) 和 指针字段( 指针 -> 字段  )   不容易 做 寄存器优化,  因为 指针 会 改变,    * 指针 和  指针 -> 字段  会 随 指针 的 改变 而 改变,

      同时,   * 指针 和  指针 -> 字段   可能 被 其它 同样指向这个 地址 的 指针 修改,  比如 指针2 和 指针 相等,    * 指针2 和 指针2 -> 字段 修改 的 数据 就是  * 指针 和  指针 -> 字段  的 数据,    但是  * 指针 和  指针 -> 字段  并不知道 数据 被 修改   。

      这还只是  单线程 的 情况   。

      多线程 也会 造成 类似 的 数据 不一致 的 情况   。

      但 使用 0 级 Cache 的 话,   0 级 Cache 是 按 地址 访问 的,  和  一级 Cache 、二级 Cache 、三级 Cache 、内存  同在一个 地址编制 ,   对于 指针取值( * 指针) 和 指针字段( 指针 -> 字段  ),   都是 按 地址访问,   不用 担心 数据不一致 的 问题   。   而  访问  0 级 Cache 的 时间 是  1 个 时钟周期,   和 寄存器 一样快  。

 

2    多核 数据同步 和 单核多线程 并发 数据一致

      这 其实 是 第 1 点 里 说 的 多线程 的 情况,   对于 多核 的 共享数据,  修改 时 要 mutex 并 同步到 各 核 的 Cache,  在 寄存器 架构下,  对于 需要 实时同步 的 多核数据, 是 不能 做  寄存器优化 的,   也就是 要 禁用 寄存器优化,   比如 C++  里 的 atomic<T> 原子类型 是 禁用 寄存器 优化 的  。

      而 现在 用  0 级 Cache,   就不存在这个问题,    0 级 Cache 和 现在 的 1 级 Cache 一样,  修改 原子数据  时 直接 mutex 和 通知 其它 核 同步,

      这样 会不会 影响性能 ? 

      不会  。    读取 时 仍然 是   1 个 时钟周期 ,   修改 时 会 发起 mutex ,  mutex 要 通知 到 其它 核 ,   当然 需要 一些 的 时钟周期,    另外,  若 收到 其它 核 已 改写数据 的 通知,   要从 其它 核 的 Cache 里 把 数据 同步过来,    这 也要 一些 时钟周期   。

      当 收到 其它 核 发起 mutex 的 通知 时,   会 等待 其它 核 的 mutex 结束,  这 需要 等待一些 时钟周期  。

      除此以外,  读取 时 是   1 个 时钟周期   。   也就是说,   如果  自己 不改写,  也 没有 收到 其它 核 mutex 和 改写 的 通知,   读取 0 级 Cache 里 的 原子变量 是  1 个 时钟周期,   和 普通变量 一样   。

 

      对于 单核多线程 并发 共享数据,   要 保证 数据 在 并发中一致,  也要 禁用 寄存器优化,   同理,   用  0 级 Cache,    就不存在这个问题    。

 

3    编译器 / 程序员   不用 考虑 把 寄存器 里 的 数据 写回 Cache / 内存   

      在 寄存器 架构下,   常用 的 数据 存在 寄存器 里 反复使用  ,  用完后(比如 函数 结束时),  如果 数据 被 修改 过,  要 写回 Cache / 内存 ,

      用  0 级 Cache  就 不用 编译器 / 程序员 考虑 这件事 了   。

      0 级 Cache 会 记录 哪些 数据 被 修改过,  被 修改 的 才 写回 映射 的 内存地址(当然, 实际上 可能 是 写 Cache , 也可能写 内存),

      这 需要  0 级 Cache 的 硬件电路 将 被 修改过的 存储单元 标记 为  “被修改” ,

      对于 这一点,   硬件电路 很容易 做到  。

 

      事实上,  在 0 级 Cache 里,  程序员 也不用 考虑 在 什么 “时机” 把 数据 写回   一级 Cache ( 二级 Cache 、三级 Cache 、内存 ),

      因为  0 级 Cache 也是 Cache,  和   一级 Cache 、二级 Cache 、三级 Cache 、内存  本身 就是 一个 体系 ,

      就好像 程序员 不用 考虑   一级 Cache 的 数据  “写回”  二级 Cache 、三级 Cache 、内存   。

 

      “时机”  比如 上面说的  “用完后(比如 函数 结束时)”   ,     在 0 级 Cache 里,  程序员 也不用 考虑  这些   。

 

       程序员 只要 考虑 把 哪个 (需要的) 地址 映射 到  0 级 Cache 的 哪个 存储单元,   这个 存储单元 原来 的 数据 如果 修改过的话, 会 自动写回 映射 的 地址  ( 一级 Cache 、二级 Cache 、三级 Cache 、内存 )   。

 

4     访问 0 级 Cache  只要 一个 时钟周期, 和 寄存器 一样 。  如果  指针 和  * 指针 都 存 在 0 级 Cache 里,  则  * 指针 一个 时钟周期 就可以 完成,  也就是说 读写 * 指针 一个 时钟周期 就可以 完成  。  读写 指针 -> 字段 也可以 一个 时钟周期 完成  。

        实际上,  只要 电路 的 精度 可以,   * * 指针 ,  指针 -> 字段 -> 字段  也可以 一个 时钟周期 完成 ,    甚至,   * * * 指针 ,  指针 -> 字段 -> 字段 -> 字段  也可以 一个 时钟周期 完成  。

        * * * 指针 ,  指针 -> 字段 -> 字段 -> 字段 ,     要把 指针 的 多次连续访问 放到 一个 时钟周期(指令) 里,   则 指针 的 连续访问次数 越多,  指令 的 电路元件 越多,  元件 数量 随 访问次数 正比增加  。  而 电路精度 越高,   比如  5nm、7nm,   在 同样 的 芯片面积 上,   可以 设计制造 更多的 元件  。

 

5     在 寄存器 架构 下,   可以 将 对象 的 一些 字段(比如 数组 首地址  、Length)  复制 一个 副本 到 局部变量 里 (栈 里) ,   然后 再对 副本 对应 的 局部变量 进行 寄存器优化, 也就是 把 副本 对应的 局部变量 放到 寄存器 里 反复使用 ,   说白了,  就是 把 副本 放到 寄存器 里 反复使用 ,    这是一种 寄存器优化 ,    这种 优化 方式 叫做   “Local Agent”  ,  副本 就是 Local Agent  。

       Local Agent 的 方式 需要 注意一个 问题,  如果 副本 对应 的 对象字段  发生了 改变,   则 要 考虑 把 这个 改变 同步 到 副本,  或者 即使不同步,  仍然 接着 使用 副本,也不会 产生 程序逻辑问题  。

      在  0 级 Cache 架构 里 ,  如 第 4 点 所说,    * 指针 和 指针 -> 字段   都可以在 一个 时钟周期 完成,   也就 不需要  Local Agent 优化 了,   也就不存在 Local Agent (副本) 和 对象字段 的 同步  。

 

   

上面说了 大半天,   由 程序员 用 汇编指令 map_in 和 现在 的 用 汇编指令 把 数据 读入寄存器 是 类似的,  还是 一套做法  。   我后来想了一下,     这 完全 没必要  。   可以 由  CPU 直接将 指令 (比如 加法指令) 中 用到 的 数据  从 内存(一级 Cache)  读入(map_in)  到  0 级 Cache  就行 。   这个 做法 就 彻底 了,   相比 现有的 寄存器 架构,  这个 做法 改革 的 就 彻底 了 。     CPU 可以用 流水架构 提前 将 接下来 的 指令 里 要用到 的 多个 数据 (并行的) map_in  到 0 级 Cache  ,   这样 就 OK 了,  很 简单, 很 清楚  。

 

这个 设计 极大 的 简化了 编译器 的 工作 ,   极大 的 简化了 编译器 的 设计 和 编写 ,     因为 编译 的 一个 主要工作 寄存器布局 和 内存屏障 都 不需要 考虑 了,   这 非常爽,  轻松到 要  飞起来  。     真的 不得了 。

 

 

在  QQ 群 里 讨论 本文 时,  我说   “静态约束搞死人,  动态判断一句话  。”

 

静态约束 是指 编译时 通过 语法 约束 让 程序 满足 某些要求 ;    动态判断 是指 在 程序 运行时 判断 程序 是否 满足 某些要求,  若 不满足 则 抛出异常  。

 

动态判断 类比  CPU 用一些 统计算法 决定 哪些 数据块(Cache Line) 从 一级 Cache 载出到内存(三级 Cache),  哪些 数据块(Cache Line) 在 一级 Cache 多呆一些时间,  可能 比较少 用到 的 数据 优先 载出,  可能 比较多 用到 的 数据 在 一级 Cache 多呆一些时间 ;     从 0 级 Cache 载出 数据 到 一级 Cache 也是一样  。

 

静态约束 类比 编译器 构思 寄存器布局 和 内存屏障,    也就是 编译器  要 具体 的 给出 每一个 数据 的 读入 寄存器 和 从 寄存器 写回 一级 Cache 的 方案  。

 

显然,    编译器 构思 寄存器布局 和 内存屏障  的 做法 精准 到 每一个 数据 的 读入 写出,    但是 构思 这些 很费脑筋,  当然 你会说 这是 编译器 构思,  不用人去构思,  但 人 要 构思 能做 这个  构思 的 算法,   也就是 人 要 教会 让 编译器 如何 做 这个 构思,    这 也是 很费脑筋,  颇为艰深  的  。

 

而  CPU 动态 的 统计判断 数据 的 使用率 来 决定 载出 哪些 数据(块),   这 有 概率统计 的 成分,   但是 简单易行,   人 不需要 考虑 太多东西  。

 

 

我提倡 用 模块线路图 来 设计 硬件电路,     硬件电路 本来 就是 模块化 的 ,  用 模块线路图 设计 很适合 。   模块 的 规格,  包括 接口 和 电路参数 作为 模块 的 说明书 单独说明 就好   。

 

其实 设计 CPU 很简单,      主要 是  制造工艺  和   电路计算 比较难   。

 

精简指令集 RISC   和   0 级 Cache   的 架构 称为     RISC-L0-Cache 架构 ,   也称为  L0 Cache 架构 ,   简称  L0 架构   。

 

为什么 会 写 这篇 文章 ?         写 这篇 文章 的 原因 是 最近(2021-03-26) 一直 在 搞  K-GC / D++ ,   K-GC / D++  是 ILBC 的 一个 子项目 ,  里面 涉及 到 很多  多核数据同步 和 寄存器优化 的 问题 和 设计,   然后 前几天 又看到 民科吧 的 一个 帖 《民科们速速进来学习》  https://tieba.baidu.com/p/7273181457  ,      大致内容如下,     就想到了  0 级 Cache 的 想法  。

 

 

posted on 2021-03-26 23:46  凯特琳  阅读(342)  评论(0编辑  收藏  举报

导航