# 习题（Gregory D. Peterson 编写）
习题由 Gregory D. Peterson 编写。

<!-- source: p0714 -->

### 习题 A.1 {#exercise-a-1}

**[10]〈A.9〉** 利用图 A.29，计算某个嵌入式 RISC-V CPU 实现的有效 CPI。假设已经测得各类指令的平均 CPI 如下：

| 指令 | 时钟周期数 |
|---|---:|
| 所有 ALU 操作 | 1.0 |
| 加载 | 5.0 |
| 存储 | 3.0 |
| 分支：跳转 | 5.0 |
| 分支：不跳转 | 3.0 |
| 跳转指令 | 3.0 |

将 astar 与 gcc 的指令频率取平均，得到所用的指令混合。

<!-- source: p0715 -->

### 习题 A.2 {#exercise-a-2}

**[10]〈A.9〉** 利用图 A.29 和上表，计算 RISC-V 的有效 CPI。将 bzip 与 hmmer 的指令频率取平均，得到指令混合。可以假设所有其他指令，即图 A.29 所列类别未涵盖的指令，每条需要 3.0 个时钟周期。

### 习题 A.3 {#exercise-a-3}

**[10]〈A.9〉** 利用图 A.29，计算某个 RISC-V CPU 实现的有效 CPI。假设测得各类指令的平均 CPI 如下：

| 指令 | 时钟周期数 |
|---|---:|
| 所有 ALU 操作 | 1.0 |
| 加载 | 3.5 |
| 存储 | 2.8 |
| 分支：跳转 | 4.0 |
| 分支：不跳转 | 2.0 |
| 跳转指令 | 2.4 |

将 gobmk 与 mcf 的指令频率取平均，得到指令混合。可以假设所有其他指令，即图 A.29 所列类别未涵盖的指令，每条需要 3.0 个时钟周期。

### 习题 A.4 {#exercise-a-4}

**[10]〈A.9〉** 利用图 A.29 和上表，计算 RISC-V 的有效 CPI。将 perlbench 与 sjeng 的指令频率取平均，得到指令混合。

### 习题 A.5 {#exercise-a-5}

**[10]〈A.8〉** 考虑下面由三条语句组成的高级语言代码：

~~~c
A = B + C;
B = A + C;
D = A - B;
~~~

使用复制传播技术（见图 A.20）变换代码，直到所有操作数都不是计算得到的值。指出哪些变换减少了一条语句的计算工作，哪些增加了计算工作。这说明，要满足人们对优化编译器的期望，会面临什么技术挑战？

### 习题 A.6 {#exercise-a-6}

**[30]〈A.8〉** 编译器优化可以改善代码大小和／或性能。从 SPEC CPU2017 或 EEMBC 基准套件中选择一个或多个程序，使用 RISC-V 或你能使用的其他处理器，以及 GNU C 编译器，分别在不优化、`-O1`、`-O2`、`-O3` 条件下编译。比较生成程序的性能与大小，并与图 A.21 的结果比较。

<!-- source: p0716 -->

### 习题 A.7 {#exercise-a-7}

**[20/20/20/25/10]〈A.2，A.9〉** 考虑以下 C 代码：

~~~c
for (i = 0; i < 100; i++) {
    A[i] = B[i] + C;
}
~~~

假定 A、B 为 64 位整数数组，C、i 为 64 位整数。所有数据值及地址，除运算时外，均保存在存储器中；A、B、C、i 的地址分别为 1000、3000、5000、7000。寄存器中的值在循环的两次迭代之间丢失。所有地址和字均为 64 位。

a. **[20]〈A.2，A.9〉** 写出 RISC-V 代码。动态执行多少条指令？发生多少次存储器数据访问？代码占多少字节？

b. **[20]〈A.2〉** 写出 x86 代码。动态执行多少条指令？发生多少次存储器数据访问？代码占多少字节？

c. **[20]〈A.2〉** 写出栈式计算机的代码。所有运算都在栈顶进行；只有 push、pop 指令访问存储器，其余指令从栈中移除操作数，再将结果放回栈。实现只对栈顶两个表项采用硬连线栈，使处理器电路很小、成本很低；更深的栈位置保存在存储器中，对它们的访问需要访存。动态执行多少条指令？发生多少次存储器数据访问？

d. **[25]〈A.2，A.9〉** 不再处理上述代码，而是编写一个稠密单精度矩阵乘法例程，即 SGEMM。输入矩阵为 \(100\times100\) 时，动态执行多少条指令？发生多少次存储器数据访问？

e. **[10]〈A.2，A.9〉** 矩阵规模增大时，动态指令数和存储器数据访问次数如何变化？

### 习题 A.8 {#exercise-a-8}

**[25/25]〈A.2，A.8，A.9〉** 考虑以下 C 代码：

~~~c
for (p = 0; p < 8; p++) {
    Y[p] = (9798*R[p] + 19235*G[p] + 3736*B[p])/32768;
    U[p] = (-4784*R[p] - 9437*G[p] + 4221*B[p])/32768 + 128;
    V[p] = (20218*R[p] - 16941*G[p] - 3277*B[p])/32768 + 128;
}
~~~

> 译注：本题 U、V 两行含有三个减号，A.8 节的对应例题缺少这些运算符，两处代码有所不同。

假定 R、G、B、Y、U、V 为 64 位整数数组。所有数据值及地址，除运算时外，均保存在存储器中；<!-- source: p0717 -->各数组的地址依次为 1000、2000、3000、4000、5000、6000。寄存器中的值在循环两次迭代之间丢失，所有地址和字均为 64 位。

a. **[25]〈A.2，A.9〉** 写出 RISC-V 代码。动态执行多少条指令？发生多少次存储器数据访问？代码占多少字节？

b. **[25]〈A.2〉** 写出 x86 代码。动态执行多少条指令？发生多少次存储器数据访问？代码占多少字节？与 A.8 节讨论的 MMX 多媒体指令实现和向量实现比较。

### 习题 A.9 {#exercise-a-9}

**[10/10/10/10]〈A.2，A.7〉** 以下各问讨论 ISA 的指令编码。

a. **[10]〈A.2，A.7〉** 某处理器指令长 14 位，有 64 个通用寄存器，因此地址字段长 6 位。能否为以下指令提供编码：3 条二地址指令、63 条一地址指令、45 条零地址指令？

b. **[10]〈A.2，A.7〉** 指令长度和地址字段大小同上。能否编码 3 条二地址指令、65 条一地址指令、35 条零地址指令？说明理由。

c. **[10]〈A.2，A.7〉** 指令长度和地址字段大小同上。若已有 3 条二地址指令和 24 条零地址指令，最多还能编码多少条一地址指令？

d. **[10]〈A.2，A.7〉** 指令长度和地址字段大小同上。若已有 3 条二地址指令和 65 条零地址指令，最多还能编码多少条一地址指令？

### 习题 A.10 {#exercise-a-10}

**[10/15]〈A.2〉** 假定整数值 A、B、C、D、E、F 保存在存储器中；指令操作码占 8 位，存储器地址占 64 位，寄存器地址占 6 位。

a. **[10]〈A.2〉** 对图 A.2 中每种指令集体系结构，计算 \(C=A+B\) 的代码中，每条指令包含多少个地址或名称？总代码大小是多少？

<!-- source: p0718 -->

b. **[15]〈A.2〉** 图 A.2 中某些体系结构在计算时会破坏操作数。处理器内部存储中的数据值丢失，会影响性能。对每种体系结构，写出计算以下语句的代码序列：

   ~~~text
   C = A + B
   D = A - E
   F = C + D
   ~~~

   标出执行期间被破坏的每个操作数，并标出仅用于弥补处理器内部数据丢失而加入的每条“额外开销”指令。对每段代码，给出总代码大小、进出存储器的指令与数据字节数、额外指令数和额外数据字节数。

### 习题 A.11 {#exercise-a-11}

**[15]〈A.2，A.7，A.9〉** RISC-V 设计提供 32 个通用寄存器与 32 个浮点寄存器。如果寄存器有好处，更多寄存器是否更好？当 ISA 设计者考虑是否增加、以及增加多少 RISC-V 寄存器时，应权衡哪些因素？尽量多地列出并讨论。

### 习题 A.12 {#exercise-a-12}

**[5]〈A.3〉** 考虑具有以下成员的 C 结构体：

~~~c
struct foo {
    char a;
    bool b;
    int c;
    double d;
    short e;
    float f;
    double g;
    char *cptr;
    float *fptr;
    int x;
};
~~~

C 编译器必须按结构体定义中的顺序保留成员。32 位机器上 `foo` 的大小是多少？若允许任意调整成员顺序，最小需要多少空间？64 位机器上又如何？

### 习题 A.13 {#exercise-a-13}

**[30]〈A.7〉** 许多计算机厂商现在提供工具或模拟器，允许测量用户程序的指令集使用情况。方法包括机器模拟、硬件支持的陷阱，以及在目标代码模块中插入软件计数器<!-- source: p0719 -->或使用内置硬件计数器的插桩技术。选择一种处理器及工具，对用户程序插桩。（开源 RISC-V 体系结构有一组相关工具；Performance API，即 PAPI 等工具可用于 x86。）测量一个 SPEC CPU2017 基准程序的指令混合，并与本附录给出的结果比较。

### 习题 A.14 {#exercise-a-14}

**[30]〈A.8〉** Intel i7 Kaby Lake 等较新的处理器支持 AVX2 向量／多媒体指令。编写单精度稠密矩阵乘法函数，使用不同编译器与优化选项编译。使用基本线性代数子程序库（BLAS）例程，例如 SGEMM 的线性代数代码，包含经过优化的稠密矩阵乘法版本。比较你的代码与 BLAS SGEMM 的代码大小及性能；再探索改用双精度和 DGEMM 后的变化。

### 习题 A.15 {#exercise-a-15}

**[30]〈A.8〉** 在上一题为 i7 开发的 SGEMM 代码中使用 AVX2 intrinsics，以改善性能。尤其应尝试向量化代码，更充分利用 AVX 硬件。将代码大小和性能与原代码比较，再与 Intel Math Kernel Library（MKL）的 SGEMM 实现比较。

### 习题 A.16 {#exercise-a-16}

**[30]〈A.7，A.9〉** RISC-V 处理器是开源的，拥有丰富的实现、模拟器、编译器及其他工具。可在 [riscv.org](https://riscv.org/) 查看工具概览，包括 RISC-V 处理器模拟器 spike。使用 spike 或另一种模拟器，测量若干 SPEC CPU2017 基准程序的指令混合。

### 习题 A.17 {#exercise-a-17}

**[35/35/35/35]〈A.2–A.8〉** gcc 支持大多数现代指令集体系结构，见 [GNU GCC](https://www.gnu.org/software/gcc/)。为你能使用的若干体系结构，例如 x86、RISC-V、PowerPC、ARM，分别构建 gcc。

a. **[35]〈A.2–A.8〉** 编译一部分 SPEC CPU2017 整数基准程序，建立代码大小表。每个程序在哪种体系结构上最好？

b. **[35]〈A.2–A.8〉** 编译一部分 SPEC CPU2017 浮点基准程序，建立代码大小表。每个程序在哪种体系结构上最好？

c. **[35]〈A.2–A.8〉** 编译一部分 EEMBC AutoBench 基准程序，见 [EEMBC](https://www.eembc.org/home.php)，建立代码大小表。每个程序在哪种体系结构上最好？

d. **[35]〈A.2–A.8〉** 编译一部分 EEMBC FPBench 浮点基准程序，建立代码大小表。每个程序在哪种体系结构上最好？

### 习题 A.18 {#exercise-a-18}

**[40]〈A.2–A.8〉** 能效对现代处理器，尤其嵌入式系统，已经非常重要。为你能使用的两种体系结构分别构建 gcc，例如 x86、RISC-V、PowerPC、Atom、ARM。（也可以探索比较 RISC-V 的不同版本。）编译一部分 EEMBC 基准程序，并使用 EnergyBench 测量<!-- source: p0720 -->执行期间的能耗。比较各处理器的代码大小、性能和能耗。每个程序在哪种处理器上最好？

### 习题 A.19 {#exercise-a-19}

**[20/15/15/20]** 比较以下四种指令集体系结构的存储器使用效率：

- **累加器型**：所有运算都在一个寄存器和一个存储器位置之间进行。
- **存储器—存储器型**：指令中的所有地址都只引用存储器位置。
- **栈式**：所有运算都在栈顶进行。只有 push、pop 访问存储器；其余指令从栈中取出操作数，并用结果替代。实现仅对栈顶两个表项采用硬连线栈，使处理器电路很小、成本很低；其余栈位置保存在存储器中，访问它们需要访存。
- **加载—存储型**：所有运算都在寄存器中进行，寄存器间运算每条指令包含三个寄存器名。

为衡量存储器使用效率，对四种指令集均作如下假设：

- 指令长度均为整数字节。
- 操作码始终占 1 字节（8 位）。
- 访存采用直接寻址，即绝对寻址。
- 变量 A、B、C、D 最初都在存储器中。

a. **[20]〈A.2，A.3〉** 自行设计汇编助记符，可以参照并推广图 A.2。对每种体系结构，为下列高级语言代码写出最优的等价汇编代码：

   ~~~c
   A = B + C;
   B = A + C;
   D = A - B;
   ~~~

b. **[15]〈A.3〉** 在 a 问代码中，标出每一次已经加载过的值又从存储器重新加载的情形。再标出每一次某条指令的结果作为另一条指令操作数传递的情形，并区分这些传递经由处理器内部存储还是存储器。

c. **[15]〈A.7〉** 假设代码来自小型嵌入式应用，使用 16 位存储器地址和数据操作数；加载—存储体系结构有 16 个通用寄存器。对每种体系结构回答：取入多少指令字节？进出存储器的数据有多少字节？按总访存流量<!-- source: p0721 -->（代码加数据）衡量，哪种体系结构效率最高？

d. **[20]〈A.7〉** 现改为采用 64 位存储器地址和数据操作数的处理器。对每种体系结构回答 c 问。按这些指标衡量，各体系结构的相对优劣发生了什么变化？

### 习题 A.20 {#exercise-a-20}

**[30]〈A.2，A.3〉** 采用上述四种指令集体系结构，但假定支持的访存方式除直接寻址外还包括寄存器间接寻址。自行设计汇编助记符（可参照并推广图 A.2），分别为以下 C 代码写出最优的等价汇编代码：

~~~c
for (i = 0; i <= 100; i++) {
    A[i] = B[i] * C + D;
}
~~~

假定 A、B 是 64 位整数数组，C、D、i 是 64 位整数。

### 习题 A.21 {#exercise-a-21}

**[20/20]〈A.3，A.6，A.9〉** 可以从已编译应用中提取位移寻址或 PC 相对寻址所需的位移大小。对编译到 RISC-V 的一个或多个 SPEC CPU2017、EEMBC 基准程序使用反汇编器。

a. **[20]〈A.3，A.9〉** 记录每条使用位移寻址的指令的位移值，绘制直方图，并与图 A.8 比较。

b. **[20]〈A.6，A.9〉** 记录每条使用 PC 相对寻址的分支指令的偏移量，绘制直方图，并与图 A.15 比较。

### 习题 A.22 {#exercise-a-22}

**[15/15/10/10/10/10]〈A.3〉** 将十六进制数 `5249 5343 5643 5055` 表示的值，存入一个对齐的 64 位双字中。

a. **[15]〈A.3〉** 按图 A.5 第一行的物理排列，用 Big Endian 字节序写出要保存的值。然后将每字节解释为 ASCII 字符，在字节下面写出对应字符，组成按 Big Endian 顺序存储的字符串。

b. **[15]〈A.3〉** 使用与 a 问相同的物理排列，以 Little Endian 字节序写出要保存的值，并在每字节下方写出对应 ASCII 字符。

c. **[10]〈A.3〉** 采用 Big Endian 字节序存储时，从给定 64 位双字中能读出的所有未对齐 2 字节字，其十六进制值分别是什么？

<!-- source: p0722 -->

d. **[10]〈A.3〉** 采用 Big Endian 字节序存储时，从给定 64 位双字中能读出的所有未对齐 2 字节字，其十六进制值分别是什么？[^duplicate-exercise]

e. **[10]〈A.3〉** 采用 Little Endian 字节序存储时，从给定 64 位双字中能读出的所有未对齐 2 字节字，其十六进制值分别是什么？

f. **[10]〈A.3〉** 采用 Little Endian 字节序存储时，从给定 64 位双字中能读出的所有未对齐 4 字节字，其十六进制值分别是什么？

### 习题 A.23 {#exercise-a-23}

**[25,25]〈A.3，A.9〉** 不同寻址方式的相对频率，会影响 ISA 选择支持哪些寻址方式。图 A.7 展示三个 VAX 应用的寻址方式相对频率。

a. **[25]〈A.3〉** 将一个或多个 SPEC CPU2017、EEMBC 基准程序编译到 x86。使用反汇编器考察指令及各种寻址方式的相对频率，并绘制直方图。结果与图 A.7 相比如何？

b. **[25]〈A.3，A.9〉** 将一个或多个 SPEC CPU2017、EEMBC 基准程序编译到 RISC-V。使用反汇编器考察指令及各种寻址方式的相对频率，并绘制直方图。结果与图 A.7 相比如何？

### 习题 A.24 {#exercise-a-24}

**[讨论]〈A.2–A.12〉** 考虑桌面、服务器、云和嵌入式计算的典型应用。面向这些市场的计算机，其指令集体系结构设计分别会受到什么影响？

[^duplicate-exercise]: 译注：原书 A.22 的 c、d 两问完全重复，均写作 Big Endian 和 2 字节。
