zoukankan      html  css  js  c++  java
  • 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 和 指针 -> 字段 修改 的 数据 就是  * 指针 和  指针 --> 字段  的 数据,    但是  * 指针 和  指针 --> 字段  并不知道 数据 被 修改   。

          这还只是  单线程 的 情况   。

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

          但 使用 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 里,  则  * 指针 一个 时钟周期 就可以 完成,  也就是说 读写 * 指针 一个 时钟周期 就可以 完成  。  读写 指针 -> 字段 也可以 一个 时钟周期 完成  。  当然 这是 理论上的,  实际上 还 有赖于 电路 的 精度  。

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

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

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

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

    Localize   指针访问 一个周期

    InitedLength  原子变量  Add() 时 改变   Local Agent

    map out  硬件电路 容易做到

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

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

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

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

  • 相关阅读:
    MPSOC之5——开发流程BOOT.BIN
    MPSOC之6——开发流程linux编译
    MPSOC之1——overview、开发板、工具[转载]
    IC设计的前端和后端(转)
    [转载]深入理解JavaScript系列 --汤姆大叔
    xml的特殊字符
    JavaScript 对象
    knockoutjs关于ko.bindingHandlers的updata订阅
    单元测试 unittest 读取文件 (CSV, XML)
    单元测试 unittest 将断言结果生成测试报告
  • 原文地址:https://www.cnblogs.com/KSongKing/p/14584657.html
Copyright © 2011-2022 走看看