指令执行
指令是处理器按指令集约定执行的基本操作,例如读内存、做加法或跳转。高级语言中的一行代码可能对应多条指令,也可能被优化掉;一条机器指令在处理器内部还可能分解为多个操作。理解这些层次,才能解释寄存器、并发、自增原子性,以及为什么不能只用“ARM”或“x86”判断速度。
1. 面试核心
先记住这几条,再读正文- 指令集规定软件可见的操作与行为,微架构规定处理器如何实现它,同一指令集可以有不同性能和功耗的实现。卡 · 指令集、微架构与性能
- 寄存器保存当前执行所需的值与状态,程序计数器参与确定指令位置;寄存器不是线程私有内存的另一种名字。卡 · 寄存器与执行位置
- x86-64 的寄存器名称要连同用途、位宽和调用约定一起理解;
RAX、EAX、AX不是三份独立存储。卡 · x86-64 的寄存器名称 - 冯诺依曼模型把程序指令与数据放入统一存储体系,处理器读取指令决定执行什么,再按指令访问和处理数据。存储程序说明程序本身也以可读取的编码形式保存。卡 · 一次读取、计算与写回
- 取指、译码、执行、访存和提交是理解执行过程的步骤,流水线能让不同指令的阶段重叠,不等于每条指令都在一个周期完成。
++value可能包含读取、计算与写回,一行代码不自动具备原子性,也不能单凭机器指令条数推断完整并发保证。- RISC/CISC 描述指令集设计思路,不能直接推出某类处理器必定更快或更省电。卡 · RISC、CISC 与 CPU/GPU
- CPU 与 GPU 的并行组织和资源取舍不同,GPU 适合有足够并行度的工作,收益还要扣除提交、同步与数据搬运成本。卡 · RISC、CISC 与 CPU/GPU
2. 寄存器与执行位置
寄存器是处理器直接使用的一组执行状态。不同架构提供不同的寄存器集合,常见责任包括:
| 类型 | 用途 |
|---|---|
| 通用寄存器 | 运算数、地址、临时结果及调用参数等 |
| 程序计数器 PC | 与指令位置相关的架构状态,具体读写语义由指令集规定 |
| 栈指针 SP | 按 ABI 指示当前栈相关位置 |
| 状态或标志寄存器 | 条件码、控制状态等;并非每条指令都更新 |
| 浮点/向量寄存器 | 保存浮点值或供向量操作使用的数据 |
变量不必一直待在某个寄存器里。编译器可能把它放入寄存器、写入栈内存,或通过常量传播消除它。函数参数和返回地址也不一定全都经栈传递:某些 ABI 使用寄存器传参,AArch64 的普通调用还可以先把返回位置保存在链接寄存器中,需要保留时再保存到栈等位置。
栈帧是一次函数调用为保存相关状态而使用的一段栈空间,可能包含放不进寄存器的局部值、需要保留的寄存器、部分参数和返回信息。具体内容与布局由 ABI、函数实现和编译优化共同决定,不是所有语言、架构都使用一张固定的表。
例如 main 调用 parse,parse 又调用 read:调用方按约定准备参数,被调用方按需要调整栈指针、保存必须恢复的状态,再执行函数体。返回前恢复约定要求的状态和栈位置,并跳到返回位置。递归调用必须保留各次调用继续执行所需的信息,不能让新一次调用随意覆盖尚未返回的那次状态。
但源码中的每次调用不一定真的分配一个独立栈帧。内联可以消除调用,叶子函数可能只使用寄存器,满足条件的尾调用也可能复用原有执行空间。栈指针与帧指针的使用同样取决于工具链和优化;不能仅数源码中的函数层数,就断言实际栈占用或回溯一定呈现同样的层次。
操作系统切换任务时,需要保存和恢复足以让任务继续执行的状态,包括相关寄存器和执行位置。硬件可能有多组执行状态,内核保存的任务上下文也不是复制整台处理器的所有缓存;哪些资源共享,见进程与线程。
2.1 x86-64 的寄存器名称
x86-64 是 x86 的 64 位扩展。先掌握下面这些基础寄存器,就能把“通用寄存器、栈指针、执行位置”对应到具体名称;这里不罗列全部扩展和系统控制寄存器。
| 名称 | 主要作用 |
|---|---|
RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP,以及 R8 到 R15 | 通用寄存器集合,可参与运算、寻址;部分指令和调用约定会指定用途 |
RSP | 栈指针;它也属于上面的通用寄存器集合 |
RBP | 可以用作栈帧基址,但编译器不一定保留帧指针 |
RIP | 指令指针,表示执行位置相关状态 |
RFLAGS | 保存条件标志与控制标志,例如运算结果是否为零 |
XMM0 到 XMM15 | 基础 SSE 寄存器集合,每个 128 位,用于相应的浮点或向量操作 |
子寄存器名称表示同一个寄存器的部分位。 例如 RAX 是 64 位,EAX 是它的低 32 位,AX 是低 16 位,AL 是低 8 位;RDX 对应 EDX、DX、DL。新增的 R8 则对应 R8D、R8W、R8B。32 位 x86 中还会看到 ESP、EBP、EIP、EFLAGS,分别对应栈指针、可选帧指针、指令指针和标志状态。
位宽还影响写入结果:在 x86-64 的 64 位模式下,写 EAX 会清零 RAX 的高 32 位,写 AX 则不会自动清零其余高位。因此读取这些名称时,不能把它们当作互不影响的变量。
寄存器的硬件名称不决定所有函数怎样传参。调用约定是调用双方约定参数、返回值和寄存器保存责任的规则,属于 ABI 的一部分。例如 Windows 标准 x64 调用约定中,前四个参数位置若为整数或指针,分别使用 RCX、RDX、R8、R9,不超过 64 位的标量整数返回值经 RAX 返回。换成其他平台或语言的内部 ABI,应查对应约定;不能把这个规则套给所有 x86 程序,也不能仅凭某个 Go 编译器使用寄存器的细节,断言该寄存器在硬件层专门负责闭包。
3. 一次读取、计算与写回
存储程序是把程序指令编码后存入存储器,由机器读取并执行,而不是每换一个计算任务就重新连接一套专用运算线路。冯诺依曼结构的经典模型将指令和数据放在统一的存储体系中,并用运算、控制、存储、输入和输出这些职责描述计算机。
以计算一个数加一为例,存储器中既有“读取、加一、写回”的指令编码,也有待处理的数。控制部分按执行位置取出指令并译码,运算部分按指令处理操作数,结果写回寄存器或存储器;跳转指令可以改变后续执行位置。因此“程序与数据一起存储”不表示二者用途相同,而是指它们都能以编码形式存放和传送,执行时按相应规则解释。
这个模型帮助我们理解取指和访存的关系,不规定现代处理器内部必须只有一份缓存、一条通路或一次只能做一件事。下面的流水线、缓存与乱序执行属于具体实现,不能仅凭“冯诺依曼”这个名称推断它们不存在。
从程序的逻辑视角,可以按取指、译码、取得操作数、执行、写回结果理解一次操作。现代处理器会使用流水线、预测和乱序执行等技术,让多个阶段重叠,但必须满足对应架构的可观察行为与内存顺序要求。
下面的 C 函数要求传入一个有效、可修改的 int 对象,且自增结果不发生有符号溢出:
void increment(int* value) {
++*value;
}
在 Apple Clang 21、AArch64、-O2 的本地编译结果中,函数主体是:
ldr w8, [x0]
add w8, w8, #1
str w8, [x0]
ret
x0 保存传入的地址,ldr 将该地址处的 32 位值读入 w8,add 计算加一,str 写回,ret 返回。这是指定工具链的例子,不保证所有编译器、类型和架构都生成同样代码。
两个线程若分别读到旧值 7,各自加一后都写入 8,最终就可能少一次更新。C/C++ 对普通共享变量的无同步并发读写还涉及语言层的数据竞争,不能把这类程序当作合法的交错模拟来依赖结果;应使用适当原子类型或锁。即使某架构能用一条指令表达某种更新,也仍需核对原子性、可见性和内存顺序。
4. 指令集、微架构与性能
指令集架构(ISA)规定软件可使用的指令、寄存器、异常与内存模型等接口。微架构是实现这个接口的具体组织,包括流水线、执行单元、缓存、预测与资源数量。
相同机器码在兼容指令集实现上可以表达相同语义,但不同处理器的吞吐、延迟和缓存行为可能不同。反过来,面向某些额外指令扩展编译的程序,也不保证能在同系列所有处理器上运行。
流水线把不同指令的处理阶段重叠起来,提高持续处理的能力。它不消除数据依赖:后一个加法需要前一个结果时,仍要等待或利用转发。分支预测失败、缓存未命中和资源冲突都会影响有效进度。
乱序执行允许在满足依赖等约束的情况下先处理已经就绪的操作,提高资源利用率;它不授权处理器任意改变程序语义。多核还引入跨核通信与共享状态的约束,不能因为单线程看起来按顺序运行,就假定其他线程总能按同样顺序观察内存操作。
在固定工作量和相对稳定频率的简化模型里,CPU 时间可近似理解为“指令数 × 平均每条指令周期数 ÷ 时钟频率”。所以更高主频不必然更快,指令更少也不必然更快;还要看指令成本、等待、并行度和内存访问。
5. RISC、CISC 与 CPU/GPU
RISC 与 CISC 分别来自精简指令集和复杂指令集的设计取向。RISC 强调较规则、易于组合的操作,CISC 可以提供语义更丰富的指令;现代实现已经采用大量相似的内部技术,不能靠这两个标签替代具体分析。
Arm 架构通常归于 RISC,x86 通常归于 CISC。AArch64 的 A64 指令使用固定 32 位编码,但 Arm 其他指令集包含不同编码安排,因此“所有 ARM 指令都同样长”也不准确。某些复杂指令可能在内部拆成多个操作,源级简单与硬件执行成本并非一一对应。
CPU 通常重视少量活跃执行流的响应和复杂控制处理;GPU 则组织大量轻量执行工作,追求并行吞吐。例如图片中许多像素能按相同规则处理,适合并行;依赖前一步结果的短小串行任务,搬到 GPU 后可能被提交和同步成本抵消。
这不意味着 GPU 只能画图,也不意味着 CPU 不能并行。选择时要检查任务可分解程度、分支与访存模式、工作量、数据是否已经在目标设备可用,以及同步频率。共享物理内存的平台仍可能存在访问、同步和调度成本,不能把“统一内存”当作零成本转换。
6 · 面试题卡片
本页题目与参考回答
6.1 指令集架构与微架构有什么区别?
考察重点:软件可见契约与硬件实现。
相关内容:指令集、微架构与性能。
参考回答
指令集定义软件可以依赖的指令与架构行为,微架构定义处理器如何实现这些要求。 同一指令集可以对应不同缓存、流水线和执行单元设计,因此兼容不等于性能相同。分析性能还要结合实际代码、内存访问和扩展指令要求,不能仅看指令集名称。
6.2 寄存器与线程执行有什么关系?
考察重点:当前执行状态、编译器分配与上下文保存。
相关内容:寄存器与执行位置。
参考回答
寄存器保存当前计算与控制状态,线程切换需要保存和恢复相关状态,使其能从原位置继续。 源码变量不固定对应某个寄存器,编译器可能分配寄存器、访问栈或直接优化掉变量。程序计数器、栈指针和通用寄存器有不同责任,也不能把寄存器组等同于一个线程的全部内存。
6.3 ARM、x86 与 RISC、CISC 有什么关系,能据此比较性能吗?
考察重点:指令集设计取向、具体编码与实现条件。
相关内容:RISC、CISC 与 CPU/GPU、指令集、微架构与性能。
参考回答
Arm 通常属于 RISC 取向,x86 属于 CISC,但标签本身不能决定速度或功耗。 它们描述指令集设计思路,实际性能由微架构、工艺、频率、内存和工作负载共同影响。现代处理器会使用流水线、预测、内部操作分解等技术,不能简单用“指令少”或“单条指令复杂”推出优劣。
6.4 CPU 与 GPU 的主要区别是什么?
考察重点:延迟与吞吐、并行度、数据和同步成本。
相关内容:RISC、CISC 与 CPU/GPU。
参考回答
CPU 更侧重复杂控制和少量执行流的响应,GPU 更侧重大量并行工作的吞吐。 GPU 不只用于图形,但需要足够可并行的任务来利用执行资源;提交、同步、搬运和不规则访存都会影响收益。应根据工作量和数据位置选择,不能认为所有计算搬到 GPU 都更快。
6.5 冯诺依曼结构中的存储程序是什么意思?
考察重点:程序与数据的存储、计算机职责划分与取指执行。
相关内容:一次读取、计算与写回。
参考回答
程序指令也以编码形式存入存储器,处理器取出指令、解释操作,再访问和处理相应数据。 经典冯诺依曼模型把指令与数据放在统一存储体系中,用运算、控制、存储、输入和输出描述主要职责;程序改变后可以执行不同任务。
指令和数据的作用不同,执行顺序也可以由跳转等指令改变。这个抽象模型不等于现代处理器必须串行完成每个内部阶段,也不排除流水线、缓存或多条执行通路。
6.6 x86 系统有哪些常见寄存器,位宽名称有什么关系?
考察重点:具体寄存器、子寄存器关系与 ABI 边界。
相关内容:x86-64 的寄存器名称。
参考回答
以 x86-64 为例,通用寄存器包括 RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP 和 R8 到 R15,还要区分 RIP、RFLAGS 与浮点/向量寄存器。 RSP 是栈指针,RBP 可作帧指针;RIP 关联指令位置,RFLAGS 保存标志。
EAX、AX、AL 分别访问 RAX 的低 32、16、8 位,不是独立寄存器。在 64 位模式下,写 EAX 还会清零 RAX 的高 32 位。哪些寄存器用于传参、返回或需要保存,要结合具体 ABI;不能把某个平台的调用约定说成所有 x86 程序的共同规则。
7. 参考资料
- John von Neumann:First Draft of a Report on the EDVAC,1945,§2.2–2.6
- Arm:CPU architecture 与 microarchitecture
- Arm AAPCS64 2023Q3:寄存器、栈与过程调用约定
- Arm:Instruction Set Reference Guide
- NVIDIA:CUDA Best Practices,Host and Device
- Clang:Command Guide
- Microsoft:x64 architecture,寄存器及子寄存器
- Microsoft:x64 calling convention,Windows 参数与返回值约定
来源:Coding 101 原文 · 免费阅读,引用请保留出处。内容使用说明