嵌入式Linux——解密 ARM 性能优化:LDR 未命中时,为何 STR 还能“插队”?

问题理解

  • ARM v6/v7 处理器会对以下指令顺序进行优化

    复制代码
    LDR r0, [r1] ;
    STR r2, [r3] ;
  • 假如说第一条LDR指令导致缓存未命中,这样缓存就会填充行,并需要较多的时钟周期才能完成。老的ARM处理器会等待这个动作完成,再执行下一条STR指令。而ARM v6/v7 及以后处理器会识别出下一条指令(STR)且不需要等待第一条指令完成(并不依赖r0的值),即先执行STR指令,而不是等待LDR指令完成。

什么叫缓存未命中

  • 错误理解:为什么还能LDR指令缓存未命中,那没命中岂不是没能从内存拿到数据?
  • "缓存未命中" 不等于 "获取数据失败"
  • "缓存未命中" 等于 "在最快的地方(缓存)没找到,必须去下一级更慢的地方(内存)找"

"缓存未命中"时,CPU 的完整处理流程

  • 这个流程是自动由硬件(CPU 和内存控制器)完成的,你的程序(LDR指令)不需要操心如何去做,只需要等待它完成。
  • 下述流程先抛开多级缓存和指令顺序优化。
  1. 尝试在"办公桌"(L1 Cache)上寻找

    • CPU 拿到地址 [r1]。
    • 它首先在最快的存储------你的**办公桌(L1 缓存)**上,寻找这个地址的数据。
    • 结果:没找到!这就是**"缓存未命中"(Cache Miss)**。
  2. "那怎么办?" ------ CPU 停顿 (Stall)

    • 你的反应(CPU 停顿):你(CPU)不能继续执行下一条指令 STR r2, [r3],因为你必须先完成 LDR。
    • 你"哎呀"一声,停下手中的工作,开始等待。
    • 这就是"需要较多的时钟周期"的开始。 CPU 在原地空转,等待数据送达。
  3. 去"大书柜"(Main Memory / 内存)拿数据

    • 因为办公桌(Cache)上没有,你(的内存控制器)只好站起来,走到**大书柜(主内存 RAM)**那里。
    • 根据地址 [r1],你在书柜中找到了那份文件(数据)。
    • 数据肯定是在"书柜(内存)"里的。(如果连内存里都没有,那就是"缺页异常/Page Fault",那是另一个更慢的故事了,但 LDR 指令本身假定数据在内存中)。
  4. "缓存就会填充行"(Cache Line Fill)

    • 这是最关键的一步!
    • 你(CPU)非常聪明,你不会只把 r0 需要的那 4 个字节(假设是32位ARM)从书柜拿到办公桌。
    • 你猜测:"既然我拿了这份文件,我待会儿很可能也会看它旁边的文件。"(这就是空间局部性原理)
    • 于是,你把包含 [r1] 数据的一整"行"(比如一整个文件夹,技术上称为一个 Cache Line,例如 64 字节)全部从"书柜(内存)"搬运到了"办公桌(缓存)"上。
    • 这个动作就叫做**"缓存填充行"(Cache Line Fill)**。
  5. 完成 LDR 指令

    • 现在,数据已经成功地被你从"书柜(内存)"复制到了"办公桌(缓存)"上。
    • CPU 再从"办公桌(缓存)"上,把 r0 真正需要的那 4 个字节的数据,抓取到"0号抽屉(r0 寄存器)"中。
    • LDR r0, [r1] 指令终于完成。
  6. CPU 恢复执行

    • "等待"结束。
    • CPU 继续执行下一条指令 STR r2, [r3]

什么叫指令顺序优化

  • 简单来说,就是 CPU 不严格按照你写的代码顺序来执行指令,而是在保证程序最终结果正确的前提下,打乱顺序,谁的条件先满足,谁就先执行,以此来"压榨"CPU的性能。

  • 想象一下,你(CPU)早上起床做早餐,你的"指令列表"如下:

    • 烧水(需要10分钟)
    • 烤面包(需要3分钟)
    • 从冰箱拿果酱(需要30秒)
  1. 场景一:老的ARM处理器(按序执行 In-Order Execution)

    • 这种处理器是个"死脑筋",它严格按照指令顺序来:
      • 执行 烧水:打开水壶。
      • 等待:站在原地干等10分钟,直到水烧开。(这就像LDR缓存未命中,CPU在"停顿/Stall")。
      • 执行 烤面包:水烧开后,把面包放进烤面包机。
      • 等待:再等3分钟。
      • 执行 拿果酱:面包烤好后,跑去冰箱拿果酱。
    • 总耗时:10分钟 + 3分钟 + 30秒 = 13.5 分钟。 效率极低!
  2. 场景二:ARM v6/v7 处理器(乱序执行 Out-of-Order Execution)

    • 这种处理器非常聪明,它会"预读"整个指令列表,并分析它们之间的依赖关系:

    • 烤面包 需要依赖 烧水 吗?不需要。

    • 拿果酱 需要依赖 烧水 或 烤面包 吗?也不需要。

    • 于是,它的执行流程变成:

      • 执行 烧水:打开水壶。(这是一个耗时操作,CPU知道它需要等,于是把它丢给"水壶"去处理)。
      • 立即执行 烤面包:在水壶烧水的同时,把面包放进烤面包机。(这就是你例子中的 STR 指令)。
      • 立即执行 拿果酱:在水壶烧水和面包机烤面包的同时,跑去冰箱拿果酱。
      • 等待:现在,所有能"偷跑"的指令都执行了,CPU只需等待最长的那个任务(烧水)完成即可。
    • 总耗时:取决于最长的那个任务 = 10 分钟。 效率极高!

相关推荐
散峰而望1 小时前
C++数组(二)(算法竞赛)
开发语言·c++·算法·github
python百炼成钢1 小时前
28.嵌入式 Linux LED 驱动开发实验
linux·运维·驱动开发
利刃大大1 小时前
【动态规划:背包问题】完全平方数
c++·算法·动态规划·背包问题·完全背包
笑非不退2 小时前
C# c++ 实现程序开机自启动
开发语言·c++·c#
想搞艺术的程序员3 小时前
深入 NSQ 延迟消息实现原理:设计巧思与性能优化
性能优化·golang·nsq
J***79393 小时前
C在Unity3D中的渲染性能优化
性能优化
AA陈超3 小时前
从0开始学习 **Lyra Starter Game** 项目
c++·笔记·学习·游戏·ue5·lyra
西风未眠3 小时前
高效编辑之vi/vim常用快捷键汇总
linux·编辑器·vim
_Stellar3 小时前
Linux 服务器管理 根目录文件夹权限设置 基于用户组实现安全共享
linux·服务器·安全
q***T5833 小时前
C++在游戏中的Unreal Engine
c++·游戏·虚幻