CISC(Complex Instruction Set Computer)とRISC(Reduced Instruction Set Computer)は、CPUの命令セットアーキテクチャ(ISA)の2つの設計思想です。
RISCは1980年以降に登場した設計方針で、できるだけ簡単な回路で1つの命令を処理するように設計されています。簡単な回路を用いるため、1つ1つの命令自体は複雑にできないものの、CPUの動作周波数を向上させながら消費電力を抑えることが可能です。
現在のコンピュータのアーキテクチャは、ほとんどがRISC的な設計思想を取り入れています。一方、広く使われているx86の命令セットはCISCとして設計されていますが、内部的にはマイクロオペレーション(micro-ops)に分解し、実質的にRISCとして処理しています。
本記事の内容
- CISCとRISCの設計思想の違い
- 命令セットアーキテクチャ(ISA)の基礎
- 各アーキテクチャの性能特性と数理モデル
- Pythonでの簡易命令セットシミュレーション
命令セットアーキテクチャ(ISA)とは
ISAは、ソフトウェアとハードウェアの間のインターフェースを定義するものです。具体的には以下を規定します。
- 命令の種類: 演算、メモリアクセス、分岐など
- レジスタ: 数、サイズ、用途
- アドレッシングモード: メモリ上のデータの指定方法
- データ型: 整数、浮動小数点数のビット幅
CISCの設計思想
CISCは、1命令でできるだけ複雑な処理を実行できるように設計されています。
特徴
- 命令長が可変(1バイトから15バイト以上)
- 豊富なアドレッシングモード
- メモリ上のデータに直接演算可能(Memory-to-Memory)
- 命令数が多い(x86は数千命令)
代表的なISA
- x86/x64: Intel/AMDのPC向けプロセッサ
- VAX: DEC社の歴史的なアーキテクチャ
RISCの設計思想
RISCは、命令を単純化して1クロックサイクルで実行できるように設計されています。
特徴
- 命令長が固定(32ビットが多い)
- ロード・ストアアーキテクチャ(演算はレジスタ間のみ)
- レジスタ数が多い(32本以上)
- 命令数が少ない(基本命令は数十から百数十)
代表的なISA
- ARM: スマートフォン、組込み機器、Apple M1/M2
- RISC-V: オープンソースISA
- MIPS: ネットワーク機器、教育用
性能のモデル
CPUの性能はCPU時間で評価されます。CPU時間は次の式で表されます。
$$ T_{\text{CPU}} = N_{\text{inst}} \times \text{CPI} \times T_{\text{clk}} $$
ここで、 * $N_{\text{inst}}$: 実行命令数 * CPI (Cycles Per Instruction): 1命令あたりの平均クロック数 * $T_{\text{clk}}$: クロック周期($= 1 / f_{\text{clk}}$)
CISCとRISCの比較
| 指標 | CISC | RISC |
|---|---|---|
| $N_{\text{inst}}$ | 少ない(複合命令) | 多い(単純命令) |
| CPI | 大きい(数サイクル) | 小さい(理想は1) |
| $T_{\text{clk}}$ | 長い(複雑な回路) | 短い(単純な回路) |
結局、$T_{\text{CPU}}$ の総合的な値で評価されるため、どちらが優れているかは単純には決められません。
命令のエンコーディング
RISC(ARM)の固定長命令
ARM(AArch64)のデータ処理命令は32ビット固定長です。
$$ \underbrace{\text{31-21}}_{\text{opcode}} \underbrace{\text{20-16}}_{\text{Rm}} \underbrace{\text{15-10}}_{\text{shift}} \underbrace{\text{9-5}}_{\text{Rn}} \underbrace{\text{4-0}}_{\text{Rd}} $$
固定長のため、命令のデコードが高速かつ単純に実装できます。
CISC(x86)の可変長命令
x86の命令は1バイトから15バイトまで可変長で、デコードが複雑です。しかし、コードサイズが小さくなるメリットがあります。
Pythonでの簡易プロセッサシミュレーション
import numpy as np
import matplotlib.pyplot as plt
class SimpleRISCSimulator:
"""簡易RISCプロセッサシミュレーター"""
def __init__(self, n_registers=8):
self.registers = np.zeros(n_registers, dtype=np.int64)
self.pc = 0 # プログラムカウンタ
self.cycles = 0
self.instructions_executed = 0
def execute_program(self, program):
"""プログラムの実行
命令形式: (opcode, operands...)
"""
self.pc = 0
self.cycles = 0
self.instructions_executed = 0
while self.pc < len(program):
inst = program[self.pc]
opcode = inst[0]
if opcode == 'ADD':
rd, rs1, rs2 = inst[1], inst[2], inst[3]
self.registers[rd] = self.registers[rs1] + self.registers[rs2]
self.cycles += 1
elif opcode == 'ADDI':
rd, rs1, imm = inst[1], inst[2], inst[3]
self.registers[rd] = self.registers[rs1] + imm
self.cycles += 1
elif opcode == 'MUL':
rd, rs1, rs2 = inst[1], inst[2], inst[3]
self.registers[rd] = self.registers[rs1] * self.registers[rs2]
self.cycles += 3 # 乗算は3サイクル
elif opcode == 'LOAD':
rd, imm = inst[1], inst[2]
self.registers[rd] = imm # 即値ロード(簡略化)
self.cycles += 1
elif opcode == 'BNE':
rs1, rs2, target = inst[1], inst[2], inst[3]
self.cycles += 1
if self.registers[rs1] != self.registers[rs2]:
self.pc = target
continue
elif opcode == 'NOP':
self.cycles += 1
self.pc += 1
self.instructions_executed += 1
return self.cycles, self.instructions_executed
# 内積計算のプログラム(RISC風)
# R0: ループカウンタ, R1: 累積和, R2,R3: 一時変数
# 10要素の内積を計算
risc_program = [
('LOAD', 0, 10), # R0 = 10 (ループ回数)
('LOAD', 1, 0), # R1 = 0 (累積和)
('LOAD', 2, 3), # R2 = 3 (ベクトルaの要素: 簡略化)
('LOAD', 3, 5), # R3 = 5 (ベクトルbの要素: 簡略化)
# ループ開始 (PC=4)
('MUL', 4, 2, 3), # R4 = R2 * R3
('ADD', 1, 1, 4), # R1 = R1 + R4
('ADDI', 0, 0, -1), # R0 = R0 - 1
('LOAD', 5, 0), # R5 = 0 (比較用)
('BNE', 0, 5, 4), # if R0 != 0, goto 4
]
sim = SimpleRISCSimulator()
cycles, n_inst = sim.execute_program(risc_program)
cpi = cycles / n_inst
print(f"=== RISC シミュレーション結果 ===")
print(f"実行命令数: {n_inst}")
print(f"総サイクル数: {cycles}")
print(f"CPI: {cpi:.2f}")
print(f"結果 (R1): {sim.registers[1]}")
# CISCとRISCの性能比較モデル
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 命令数と CPI のトレードオフ
n_inst_cisc = np.linspace(100, 500, 50)
n_inst_risc = np.linspace(200, 1000, 50)
cpi_cisc = 3.5 # CISC の平均CPI
cpi_risc = 1.2 # RISC の平均CPI
f_cisc = 2.0e9 # 2 GHz
f_risc = 3.0e9 # 3 GHz
time_cisc = n_inst_cisc * cpi_cisc / f_cisc * 1e9 # ns
time_risc = n_inst_risc * cpi_risc / f_risc * 1e9 # ns
axes[0].plot(n_inst_cisc, time_cisc, 'r-', linewidth=2, label='CISC')
axes[0].plot(n_inst_risc, time_risc, 'b-', linewidth=2, label='RISC')
axes[0].set_xlabel('Number of Instructions')
axes[0].set_ylabel('Execution Time [ns]')
axes[0].set_title('CISC vs RISC Execution Time')
axes[0].legend()
axes[0].grid(True)
# CPI分布
cpis = {'ADD': (1, 1), 'SUB': (1, 1), 'MUL': (3, 3),
'DIV': (10, 20), 'LOAD': (1, 4), 'STORE': (1, 4),
'BRANCH': (1, 2)}
names = list(cpis.keys())
risc_cpis = [v[0] for v in cpis.values()]
cisc_cpis = [v[1] for v in cpis.values()]
x = np.arange(len(names))
width = 0.35
axes[1].bar(x - width/2, risc_cpis, width, label='RISC', color='steelblue')
axes[1].bar(x + width/2, cisc_cpis, width, label='CISC', color='salmon')
axes[1].set_xticks(x)
axes[1].set_xticklabels(names, rotation=45)
axes[1].set_ylabel('CPI')
axes[1].set_title('CPI per Instruction Type')
axes[1].legend()
axes[1].grid(True, axis='y')
plt.tight_layout()
plt.show()
まとめ
本記事では、CISCとRISCの違いについて解説しました。
- CISCは複雑で高機能な命令を持ち、命令数を削減できるが回路が複雑になる
- RISCは単純な命令に特化し、パイプライン処理に適した設計である
- CPU性能は $T_{\text{CPU}} = N_{\text{inst}} \times \text{CPI} \times T_{\text{clk}}$ で評価される
- 現代のx86プロセッサは外見上CISCだが、内部的にRISC的な処理を行っている
- ARM(RISC)はモバイルからサーバーまで幅広く普及している