第二章 精简指令集RISC:RISC设计哲学,对比CISC,为什么ARM选择了RISC?
大家好,我是你们的讲师。今天我们来聊聊一个芯片设计领域绕不开的话题——RISC与CISC。
很多同学刚接触ARM时,都会问一个问题:为什么ARM不用x86那种复杂指令集,非要搞个精简的?
这个问题问得好。它背后藏着整个处理器设计哲学的分水岭。
2.1 什么是RISC?什么是CISC?
先看两张图,我手绘的,凑合看。
说白了,RISC就是「少而精」,CISC就是「多而全」。但别被名字骗了——精简不代表功能弱,复杂也不代表性能强。
2.2 RISC的核心设计哲学
我个人习惯把RISC的设计原则总结为五条,你记一下:
- 指令长度固定——每条指令都是32位,取指、译码都简单
- 指令数量精简——只保留最常用的指令,复杂的用组合实现
- Load/Store架构——只有load和store能访问内存,运算都在寄存器里
- 大量通用寄存器——ARM有16个通用寄存器,MIPS有32个
- 硬件设计简单——把复杂度扔给编译器
我在项目中遇到过一件事:有个同事非要用一条ARM指令完成「从内存读数据、加1、再写回去」的操作。结果发现ARM根本做不到——你得先load,再add,再store。他当时就抱怨:「这什么破指令集,连个自增都做不了?」
嗯,这就是RISC的哲学。它逼着你把操作拆开,但换来的是每个操作都跑得飞快。
2.3 RISC vs CISC:一场关于「谁更聪明」的争论
你想想看,CISC为什么会出现?
上世纪70年代,内存贵得像黄金,编译器也笨得像木头。那时候的工程师想:与其让编译器生成一堆指令,不如让一条指令干更多的事。于是就有了「内存直接加1」这种复杂指令。
但到了80年代,情况变了。内存便宜了,编译器也聪明了。这时候RISC的倡导者站出来说:你们那些复杂指令,90%的程序根本用不上!
来看个对比:
| 对比项 | RISC(ARM) | CISC(x86) |
|---|---|---|
| 指令长度 | 固定32位 | 1~15字节可变 |
| 指令数量 | 约50~100条 | 数百条(含变种) |
| 寻址方式 | 简单(3~5种) | 复杂(10种以上) |
| 寄存器数量 | 16~32个通用寄存器 | 8个通用寄存器(x86-32) |
| 内存访问 | 仅Load/Store | 多数指令可直接操作内存 |
| 硬件复杂度 | 低(容易流水线化) | 高(需要微码控制) |
| 功耗 | 低(适合嵌入式) | 高(适合桌面/服务器) |
核心差异一句话:RISC用多条简单指令完成一件事,CISC用一条复杂指令完成同样的事。但RISC的每条指令执行得更快,整体性能反而可能更高。
2.4 为什么ARM选择了RISC?
这个问题其实有个历史背景。ARM诞生于1985年,那时候RISC运动正如火如荼。Acorn公司(ARM的前身)的工程师们研究了当时的处理器,发现一个现象:
「处理器80%的时间,只用了20%的指令。」
那剩下的80%指令呢?基本在吃灰。但为了支持这些吃灰的指令,芯片面积大了,功耗高了,设计周期也长了。
ARM的创始人之一Sophie Wilson当时就说:我们不需要一个什么都能干的处理器,我们需要一个「够用且高效」的处理器。
于是ARM选择了RISC,原因有三:
- 低功耗——RISC的硬件简单,晶体管少,功耗自然低。这对嵌入式设备来说就是命根子。
- 设计简单——我记得ARM7TDMI只有3万多个晶体管,而同时期的x86处理器动辄几十万。设计简单意味着开发周期短、成本低、容易迭代。
- 容易流水线化——固定指令长度让流水线设计变得极其简单。每条指令都在同一个位置取指、译码、执行,没有CISC那种「这条指令要取3个字节,那条要取7个字节」的麻烦。
一个小技巧:判断一个指令集是不是RISC,看它的指令长度是不是固定的就行。ARM是固定的32位,Thumb是固定的16位,都是RISC。x86的指令长度可变,所以是CISC。
2.5 一个实际的例子:加法操作
咱们用代码说话。假设我们要实现:a = a + b,其中a和b都在内存里。
CISC(x86)的做法:
ADD [a], [b] ; 一条指令搞定,直接操作内存
RISC(ARM)的做法:
LDR R0, [a] ; 先把a加载到寄存器
LDR R1, [b] ; 再把b加载到寄存器
ADD R0, R0, R1 ; 在寄存器里做加法
STR R0, [a] ; 把结果写回内存
你看,ARM用了4条指令。但每条指令都在一个时钟周期内完成。而x86那条ADD指令,因为要访问两次内存(读a、读b),加上微码控制,可能要花5~10个时钟周期。
所以最终谁更快?还真不一定。
避坑指南:我曾经在优化一个音频解码算法时,犯过一个错误。我试图用ARM的LDM(多寄存器加载)指令一次加载4个数据,以为这样能快。结果发现LDM指令执行时会打断流水线,反而比4次LDR更慢。后来我改成LDR单次加载,性能提升了15%。
所以记住:RISC的「精简」不代表「无脑用」,你得理解每条指令背后的硬件行为。
2.6 RISC的代价:编译器要更聪明
RISC把复杂度从硬件转移到了软件。这意味着编译器需要做更多的工作。
比如在CISC上,你可以直接写:
ADD [a], 1 ; 内存自增
但在RISC上,编译器必须生成:
LDR R0, [a]
ADD R0, R0, #1
STR R0, [a]
这还没完。编译器还得考虑寄存器分配——总共就16个通用寄存器,怎么安排才能避免频繁的load/store?这是个NP难问题。
所以你看,ARM的编译器(比如ARMCC、GCC)比x86的编译器要复杂得多。但好处是,一旦编译器优化好了,生成的代码质量非常高。
2.7 现代处理器的融合趋势
说到这里,我得坦白一件事:现在的ARM和x86,其实都在互相学习。
ARM从ARMv7开始加入了NEON指令集(SIMD),这其实是一种「复杂指令」。而x86从Pentium Pro开始,内部把CISC指令翻译成微操作(micro-ops),这些微操作本质上就是RISC指令。
所以到了今天,纯粹的RISC和纯粹的CISC已经很少见了。但ARM选择RISC作为起点,这个决策在1985年是非常有远见的——它让ARM在功耗和面积上占据了绝对优势,最终统治了移动市场。
我个人觉得,ARM的成功不是因为它选择了RISC,而是因为它选择了「在正确的时间做正确的取舍」。RISC给了它低功耗、低成本、短设计周期,这些恰好是嵌入式市场最需要的。
好,这一章就到这里。记住RISC的五条设计原则,下次看到ARM的指令集手册,你就知道它为什么这么设计了。
公众号:蓝海资料掘金营,微信deep3321