第二章 精简指令集RISC:RISC设计哲学,对比CISC,为什么ARM选择了RISC?

大家好,我是你们的讲师。今天我们来聊聊一个芯片设计领域绕不开的话题——RISC与CISC。

很多同学刚接触ARM时,都会问一个问题:为什么ARM不用x86那种复杂指令集,非要搞个精简的?

这个问题问得好。它背后藏着整个处理器设计哲学的分水岭。

2.1 什么是RISC?什么是CISC?

先看两张图,我手绘的,凑合看。

RISC 设计哲学 指令长度固定(32位) 指令数量少(约50-100条) Load/Store 架构 大量通用寄存器(32个) 硬件简单,编译器复杂 CISC 设计哲学 指令长度可变(1-15字节) 指令数量多(数百条) 内存直接操作 寄存器数量少(8-16个) 硬件复杂,编译器简单

说白了,RISC就是「少而精」,CISC就是「多而全」。但别被名字骗了——精简不代表功能弱,复杂也不代表性能强。

2.2 RISC的核心设计哲学

我个人习惯把RISC的设计原则总结为五条,你记一下:

  1. 指令长度固定——每条指令都是32位,取指、译码都简单
  2. 指令数量精简——只保留最常用的指令,复杂的用组合实现
  3. Load/Store架构——只有load和store能访问内存,运算都在寄存器里
  4. 大量通用寄存器——ARM有16个通用寄存器,MIPS有32个
  5. 硬件设计简单——把复杂度扔给编译器

我在项目中遇到过一件事:有个同事非要用一条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,原因有三:

  1. 低功耗——RISC的硬件简单,晶体管少,功耗自然低。这对嵌入式设备来说就是命根子。
  2. 设计简单——我记得ARM7TDMI只有3万多个晶体管,而同时期的x86处理器动辄几十万。设计简单意味着开发周期短、成本低、容易迭代。
  3. 容易流水线化——固定指令长度让流水线设计变得极其简单。每条指令都在同一个位置取指、译码、执行,没有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