跳转到主要内容

习题(Gregory D. Peterson 编写)

习题由 Gregory D. Peterson 编写。

习题 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 的指令频率取平均,得到所用的指令混合。

习题 A.2

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

习题 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

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

习题 A.5

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

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

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

习题 A.6

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

习题 A.7

[20/20/20/25/10]〈A.2,A.9〉 考虑以下 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×100100\times100 时,动态执行多少条指令?发生多少次存储器数据访问?

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

习题 A.8

[25/25]〈A.2,A.8,A.9〉 考虑以下 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 位整数数组。所有数据值及地址,除运算时外,均保存在存储器中;各数组的地址依次为 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

[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

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

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

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

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

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

习题 A.11

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

习题 A.12

[5]〈A.3〉 考虑具有以下成员的 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

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

习题 A.14

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

习题 A.15

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

习题 A.16

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

习题 A.17

[35/35/35/35]〈A.2–A.8〉 gcc 支持大多数现代指令集体系结构,见 GNU 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,建立代码大小表。每个程序在哪种体系结构上最好?

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

习题 A.18

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

习题 A.19

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

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

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

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

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

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

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

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

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

习题 A.20

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

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

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

习题 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

[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 字节字,其十六进制值分别是什么?

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

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

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

习题 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

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


  1. 译注:原书 A.22 的 c、d 两问完全重复,均写作 Big Endian 和 2 字节。 ↩︎