嵌入式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 分钟。 效率极高!

相关推荐
正在学习前端的---小方同学5 小时前
Harbor部署教程
linux·运维
byxdaz5 小时前
C++内存序
c++
优雅的潮叭5 小时前
c++ 学习笔记之 malloc
c++·笔记·学习
Sean X6 小时前
Ubuntu24.04安装向日葵
linux·ubuntu
IT 乔峰7 小时前
脚本部署MHA集群
linux·shell
dz小伟7 小时前
execve() 系统调用深度解析:从用户空间到内核的完整加载过程
linux
苦藤新鸡7 小时前
8.最长的无重复字符的子串
c++·力扣
Mr_Xuhhh8 小时前
博客标题:深入理解Shell:从进程控制到自主实现一个微型Shell
linux·运维·服务器
JoyCheung-8 小时前
Free底层是怎么释放内存的
linux·c语言
世洋Blog8 小时前
面经-CPU、内存、GPU的性能优化
unity·性能优化