重构目录层次
0-课程笔记 1-平时作业 2-实验报告 3-期末大作业
This commit is contained in:
68
计算机系统结构/平时作业/mypreamble.tex
Normal file
68
计算机系统结构/平时作业/mypreamble.tex
Normal file
@@ -0,0 +1,68 @@
|
||||
\usepackage{amssymb, amsfonts, amstext, amsmath, amsopn, amsthm}
|
||||
\usepackage{booktabs}
|
||||
\usepackage[framemethod=TikZ]{mdframed}
|
||||
% \usepackage[tikz]{bclogo}
|
||||
\usepackage{fontawesome5}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{array}
|
||||
\usepackage{ragged2e}
|
||||
\usepackage{bm}
|
||||
\usepackage{hyperref}
|
||||
|
||||
\usepackage{fancyhdr}
|
||||
\usepackage{enumitem}
|
||||
\usepackage{totpages}
|
||||
\usepackage{mylatex}
|
||||
\usepackage{subfiles}
|
||||
|
||||
\title{《计算机系统结构》作业}
|
||||
\author{岳锦鹏}
|
||||
\newcommand{\mysignature}{10213903403 岳锦鹏}
|
||||
\date{2024年3月17日——2024年6月18日}
|
||||
\setlist[1]{listparindent=\parindent}
|
||||
\setlist[2]{label=\alph{enumii}.,listparindent=\parindent}
|
||||
\definecolor{shadecolor}{RGB}{204,232,207}
|
||||
\definecolor{verificationColor}{RGB}{85,206,255} % 这颜色误差确实有点大啊,目前手动调的
|
||||
|
||||
\newcommand{\complicateditem}[3]{
|
||||
\item[\textbf{#1}] [#2]<#3>
|
||||
}
|
||||
|
||||
\renewcommand{\questionandanswer}[3][]{%
|
||||
\begin{shaded}%
|
||||
\ifstrequal{#1}{-}{}{\item[\textbf{#1}]} #2%
|
||||
\end{shaded}%
|
||||
\begin{zhongwen}%
|
||||
#3%
|
||||
\end{zhongwen}%
|
||||
}
|
||||
|
||||
\newmdenv[%
|
||||
frametitle={%
|
||||
\tikz[]
|
||||
\node[anchor=east,rectangle,fill=verificationColor]
|
||||
{ \faIcon{search}\ 验证一下};}, %
|
||||
% frametitlerule=true, %
|
||||
% frametitlebelowskip=10pt, %
|
||||
roundcorner=5pt, %
|
||||
innertopmargin=10pt,linecolor=verificationColor,%
|
||||
linewidth=2pt,topline=true, %
|
||||
frametitleaboveskip=\dimexpr-\ht\strutbox\relax,
|
||||
]{verification}
|
||||
|
||||
% \newenvironment{verification}{
|
||||
% \begin{bclogo}[couleur=verificationColor, arrondi =0.1 , logo=\bcloupe, noborder=true]{验证一下}
|
||||
% }{
|
||||
% \end{bclogo}
|
||||
% }
|
||||
|
||||
\usemintedstyle{staroffice} % 虽然这个没有颜色区分操作符与操作数,但是对于数字的强调很重要,因为汇编中的这些立即数都是比较重要,同时注释的字体也比较正常没有花体
|
||||
|
||||
% 该命令用于控制 p{} 的情况
|
||||
\newcolumntype{P}[1]{>{\RaggedRight\hspace{0pt}}p{#1}} % 使用过程中,将p{4cm}换成P{4cm},小写改成大写即可!
|
||||
% 该命令用于控制 X 的情况
|
||||
\newcolumntype{Z}{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}X} % 使用过程中,将Z 换成 X,即可!
|
||||
|
||||
\let\kaishu\relax % 清除旧定义
|
||||
\newCJKfontfamily\kaishu{KaiTi}[AutoFakeBold] % 重定义 \kaishu
|
||||
\newcommand{\boldkai}[1]{{\bfseries\kaishu #1}}
|
||||
5
计算机系统结构/平时作业/mysubpreamble.tex
Normal file
5
计算机系统结构/平时作业/mysubpreamble.tex
Normal file
@@ -0,0 +1,5 @@
|
||||
\pagestyle{fancyplain}
|
||||
\fancyhead{}
|
||||
\fancyhead[C]{\mysignature}
|
||||
\fancyfoot[C]{第 \thepage 页\quad 共 \ref{TotPages} 页}
|
||||
% \definecolor{shadecolor}{named}{white}
|
||||
12
计算机系统结构/平时作业/全部作业.tex
Normal file
12
计算机系统结构/平时作业/全部作业.tex
Normal file
@@ -0,0 +1,12 @@
|
||||
\documentclass[a4paper]{ctexbook}
|
||||
\input{mypreamble}
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
\subfile{第一章作业}
|
||||
\subfile{第二章作业}
|
||||
\subfile{第三章作业}
|
||||
\subfile{第四章作业}
|
||||
\subfile{第五章作业1}
|
||||
\subfile{第五章作业2}
|
||||
\end{document}
|
||||
178
计算机系统结构/平时作业/第一章作业.tex
Normal file
178
计算机系统结构/平时作业/第一章作业.tex
Normal file
@@ -0,0 +1,178 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
\begin{document}
|
||||
\chapter{计算机抽象及相关技术}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[1.3]{
|
||||
[ 2 ]<1.3>请描述高级语言(例如C)编写的程序转化为能够直接在计算机处理器上执行的表示的具体步骤。
|
||||
}{
|
||||
高级语言通过编译器(编译型的高级语言,如C、C++)或解释器(解释型的高级语言,如Python、JavaScript)转化成指令,再转化成二进制的机器码,或者直接转化成二进制的机器码(不同的硬件对应的机器码可能不同),机器码就是能够直接在计算机处理器上执行的表示。
|
||||
}
|
||||
\questionandanswer[1.5]{
|
||||
[ 4 ]<1.6>有3种不同的处理器P1、P2和P3执行同样的指令系统。P1的时钟频率为3GHz,CPI为1.5;P2的时钟频率为2.5GHz,CPI为1.0;P3的时钟频率为4GHz,CPI为2.2。
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[-]{
|
||||
\item 以每秒执行的指令数为标准,哪个处理器性能最高?
|
||||
}{
|
||||
\noindent
|
||||
P1:$\frac{3 \text{GHz}}{1.5\text{时钟周期数/指令}}=2\times 10^{9}$\\
|
||||
P2:$\frac{2.5\text{GHz}}{1.0\text{时钟周期数/指令}}=2.5\times 10^{9}$\\
|
||||
P3:$\frac{4\text{GHz}}{2.2\text{时钟周期数/指令}}\approx 1.818\times 10^{9}$\\
|
||||
所以处理器P2性能最高。
|
||||
}
|
||||
\questionandanswer[-]{
|
||||
\item 如果每个处理器执行一个程序都花费10秒时间,求它们的时钟周期数和指令数。
|
||||
}{
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
& 时钟周期数 & 指令数 \\
|
||||
\midrule
|
||||
P1 & $3\text{GHz}\times 10s=30\times 10^{9}$ & $\frac{30\times 10^{9}}{1.5}=20\times 10^{9}$ \\
|
||||
P2 & $2.5\text{GHz}\times 10s=25\times 10^{9}$ & $\frac{25\times 10^{9}}{1.0}=25\times 10^{9}$ \\
|
||||
P3 & $4\text{GHz}\times 10s=40\times 10^{9}$ & $\frac{40\times 10^{9}}{2.2}\approx 18.1818\times 10^{9}$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
}
|
||||
\questionandanswer[-]{
|
||||
\item 我们试图把执行时间减少30\%,但这会引起CPI增大20\%。请问为达到时间减少30\%的目标,时钟频率应达到多少?
|
||||
}{
|
||||
$$
|
||||
\text{执行时间}\times 70\% = \text{指令数}\times (\text{CPI}\times 120\%) / \text{时钟频率}
|
||||
$$
|
||||
$$
|
||||
\text{时钟频率}=\frac{\text{指令数}\times \text{CPI}}{\text{执行时间}}\times \frac{1.2}{0.7} \approx 1.714 \times \text{原始时钟频率}
|
||||
$$
|
||||
所以时钟频率要达到原始时钟频率的大约$1.714$倍。
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[1.6]{
|
||||
[ 20 ]<1.6>同一个指令系统体系结构有两种不同的实现方式。根据CPI的不同将指令分成四类(A、B、C和D)。P1的时钟频率为2.5GHz,CPI分别为1、2、3和3;P2时钟频率为3GHz,CPI分别为2、2、2和2。\\
|
||||
给定一个程序,有$1.0\times 10^{6}$条动态指令,按如下比例分为4类:A,10\%;B,20\%;C,50\%;D,20\%。
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[-]{
|
||||
\item 每种实现方式下的整体CPI是多少?
|
||||
}{
|
||||
P1:$1\times 10\%+2\times 20\%+3\times 50\%+3\times 20\% = 2.6$\\
|
||||
P2:$2$
|
||||
}
|
||||
\questionandanswer[-]{
|
||||
\item 计算两种情况下的时钟周期总数。
|
||||
}{
|
||||
P1:$1.0\times 10^{6}\times 2.6 = 2.6\times 10^{6}$\\
|
||||
P2:$1.0\times 10^{6}\times 2=2\times 10^{6}$
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[1.7]{
|
||||
[ 15 ]<1.6>编译器对应用程序性能有极深的影响。假定对于一
|
||||
个程序,如果采用编译器A,则
|
||||
动态指令数为$1.0\times 10^{9}$,执行时间为1.1s;如果采用编译器B,则动态指令数为$1.2\times 10^{9}$,执行时间为1.5s。
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[-]{
|
||||
\item 在给定处理器时钟周期长度为1ns时,求每个程序的平均CPI。
|
||||
}{
|
||||
编译器A:$\frac{1.1s}{1\times 10^{-9} s}/1.0\times 10^{9}=1.1$\\
|
||||
编译器B:$\frac{1.5s}{1\times 10^{-9} s}/1.2\times 10^{9}=1.25$
|
||||
}
|
||||
\questionandanswer[-]{
|
||||
\item 假定被编译的程序分别在两个不同的处理器上运行。如果这两个处理器的执行时间相同,求运行编译器A生成之代码的处理器时钟比运行编译器B生成之代码的处理器时钟快多少。
|
||||
}{
|
||||
% 相同处理器时执行时间分别为1.1s和1.5s,那么相同执行时间时处理器时钟的比值为:
|
||||
$$
|
||||
\begin{aligned}
|
||||
% &\frac{处理器1时钟}{处理器2时钟}=\frac{处理器1时钟\times 执行时间}{处理器2时钟\times 执行时间}=\frac{编译器A生成代码的时钟周期数}{编译器B生成代码的时钟周期数} \\
|
||||
执行时间_{A}&=执行时间_{B} \\
|
||||
\frac{指令数_{A}\times CPI_{A}}{时钟频率_{A}}&=\frac{指令数_{B}\times CPI_{B}}{时钟频率_{B}} \\
|
||||
\frac{时钟频率_{A}}{时钟频率_{B}}&=\frac{指令数_{A}\times CPI_{A}}{指令数_{B}\times CPI_{B}}=\frac{1.0\times 10^{9}\times 1.1}{1.2\times 10^{9}\times 1.25} = \frac{11}{15} = 0.733 \\
|
||||
\end{aligned}
|
||||
$$
|
||||
% $$
|
||||
% \frac{1.5s}{1.2s} = 1.25
|
||||
% $$
|
||||
所以题意应该是错了,运行编译器A生成之代码的处理器时钟比运行编译器B生成之代码的处理器时钟慢,约$0.733$倍。
|
||||
}
|
||||
\questionandanswer[-]{
|
||||
\item 假设开发了一种新的编译器,只需$6.0\times 10^{8}$条指令,程序平均CPI为1.1。求这种新的编译器在原处理器环境下相对于原编译器A和B的加速比。
|
||||
}{
|
||||
记这个新的编译器为C。
|
||||
$$
|
||||
\frac{性能C}{性能A}=\frac{执行时间A}{执行时间C}=\frac{\frac{指令数_{A}\times CPI_{A}}{时钟频率}}{\frac{指令数_{C}\times CPI_{C}}{时钟频率}}=\frac{指令数_{A}\times CPI_{A}}{指令数_{C}\times CPI_{C}}=\frac{1.0\times 10^{9}\times 1.1}{6.0\times 10^{8}\times 1.1} = \frac{5}{3} \approx 1.667
|
||||
$$
|
||||
同理,
|
||||
$$
|
||||
\frac{性能C}{性能B}=\frac{指令数_{B}\times CPI_{B}}{指令数_{C}\times CPI_{C}}=\frac{1.2\times 10^{9}\times 1.25}{6.0\times 10^{8}\times 1.1} = \frac{25}{11} \approx 2.273
|
||||
$$
|
||||
所以这种新的编译器在原处理器环境下相对于原编译器A和B的加速比分别约为$1.667$和$2.273$。
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[1.9]{
|
||||
在某处理器中,假定算术指令、load/store指令和分支指令的CPI分别是1、12和5。同时假定某程序在单个处理器核上运行时需要执行$2.56\times 10^{9}$条算术指令、$1.28\times 10^{9}$条load/store指令和$2.56\times 10^{8}$条分支指令,并假定处理器的时钟频率为2GHz。
|
||||
现假定程序并行运行在多核上,分配到每个处理器核上运行的算术指令和 load/store指令数目为单核情况下相应指令数目除以$0.7\times p$ ( $p$为处理器核数),而每个处理器的分支指令的数量保持不变。
|
||||
}{}
|
||||
\questionandanswer[1.9.1]{
|
||||
[ 5 ]<1.7>求出当该程序分别运行在1、2、4和8个处理器核上的执行时间,并求出其他情况下相对于单核处理器的加速比。
|
||||
}{
|
||||
$$
|
||||
多核执行时间=\frac{指令数\times CPI}{时钟周期}=\frac{\frac{2.56\times 10^{9}}{0.7\times p}\times 1+\frac{1.28\times 10^{9}}{0.7\times p}\times 12+2.56\times 10^{8}\times 5}{2\times 10^{9}} = \frac{16 (p + 20)}{25 p} s
|
||||
$$
|
||||
$$
|
||||
单核执行时间=\frac{指令数\times CPI}{时钟周期}=\frac{2.56\times 10^{9}\times 1+1.28\times 10^{9}\times 12+2.56\times 10^{8}\times 5}{2\times 10^{9}} = \frac{48}{5} = 9.6 s
|
||||
$$
|
||||
$$
|
||||
多核与单核的加速比=\frac{单核执行时间}{多核执行时间}=\frac{\frac{48}{5}}{\frac{16(p+20)}{25p}} = \frac{15 p}{p + 20}
|
||||
$$
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
处理器核数 & 执行时间 & 加速比 \\
|
||||
\midrule
|
||||
1 & 9.600 & 1.000 \\
|
||||
2 & 7.040 & 1.364 \\
|
||||
4 & 3.840 & 2.500 \\
|
||||
8 & 2.240 & 4.286 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}
|
||||
\questionandanswer[1.9.2]{
|
||||
[ 10 ]<1.6,1.8>如果算术指令的CPI加倍,对分别运行在1、2、4和8个处理器核上的执行时间有何影响?
|
||||
}{
|
||||
$$
|
||||
多核执行时间=\frac{\frac{2.56\times 10^{9}}{0.7\times p}\times 2+\frac{1.28\times 10^{9}}{0.7\times p}\times 12+2.56\times 10^{8}\times 5}{2\times 10^{9}} = \frac{16 (7 p + 160)}{175 p} s
|
||||
$$
|
||||
$$
|
||||
单核执行时间=\frac{2.56\times 10^{9}\times 2+1.28\times 10^{9}\times 12+2.56\times 10^{8}\times 5}{2\times 10^{9}} = \frac{272}{25} = 10.88 s
|
||||
$$
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
处理器核数 & 之前的执行时间 & 现在的执行时间 \\
|
||||
\midrule
|
||||
1 & 9.600 & 10.880 \\
|
||||
2 & 7.040 & 7.954 \\
|
||||
4 & 3.840 & 4.297 \\
|
||||
8 & 2.240 & 2.469 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}
|
||||
\questionandanswer[1.9.3]{
|
||||
[ 10 ]<1.6,1.8>如果要使单核处理器的性能与四核处理器相当,单处理器中 load/store指令的CPI应该降低多少?此处假定四核处理器的CPI保持原数值不变。
|
||||
}{
|
||||
设单处理器中 load/store指令的CPI应该降低到原CPI的$x$倍。
|
||||
$$
|
||||
\begin{aligned}
|
||||
单核处理器(CPI降低)的执行时间&=四核处理器(原CPI)的执行时间 \\
|
||||
\frac{2.56\times 10^{9}\times 1+1.28\times 10^{9}\times 12x+2.56\times 10^{8}\times 5}{2\times 10^{9}} &= \frac{16 \times (4 + 20)}{25 \times 4} \\
|
||||
\frac{192 x}{25} + \frac{48}{25} &= \frac{96}{25} \\
|
||||
\end{aligned}
|
||||
$$
|
||||
$$
|
||||
x = \frac{96-48}{192} = \frac{1}{4} = 0.25
|
||||
$$
|
||||
所以单处理器中 load/store指令的CPI应该降低到原CPI的0.25倍,即$12\times 0.25=3$。
|
||||
}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
46
计算机系统结构/平时作业/第三章作业.tex
Normal file
46
计算机系统结构/平时作业/第三章作业.tex
Normal file
@@ -0,0 +1,46 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
|
||||
\setcounter{chapter}{2}
|
||||
\begin{document}
|
||||
\chapter{计算机的算术运算}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[3.7]{
|
||||
[ 5 ]<3.2>假设带符号的8位十进制整数185和122以符号-数值形式存储。计算185+122。是否上溢或下溢,或都没有?
|
||||
}{
|
||||
185 转成8位二进制为 $1011\ 1001$,其最高位为1,表示负数,即$185-256=-71$;
|
||||
122 转成8位二进制为 $0111\ 1010$,其最高位为0,表示正数。
|
||||
|
||||
$1011\ 1001 + 0111\ 1010 = 1\ 0011\ 0011$,只有8位,所以结果为$0011\ 0011$,即51。符合结果$-71+122 = 51$,所以没有上溢或下溢。
|
||||
}
|
||||
\questionandanswer[3.22]{
|
||||
[ 10 ] <3.5>如果是浮点数,位模式0x0C000000表示的十进制数是什么?使用IEEE 754标准。
|
||||
}{
|
||||
这是32位的浮点数,即单精度浮点数。根据 IEEE 754 标准,即1位符号位、8位指数位、23位尾数位。
|
||||
$$
|
||||
\underbrace{0}_{\text{符号位}} | \underbrace{000\ 1100\ 0}_{\text{指数位}}|\underbrace{000\ 0000\ 0000\ 0000\ 0000\ 0000}_{尾数位}
|
||||
$$
|
||||
符号位是0,表示正数;指数位是$0001\ 1000$,转化为十进制为24;尾数位是0。
|
||||
|
||||
所以位模式0x0C000000表示的十进制数为
|
||||
$$
|
||||
(-1)^{\text{符号}}\times (1+\text{尾数})\times 2^{\text{指数}-127} = (-1)^{0}\times (1+0.0)\times 2^{24-127} = 2^{-103}
|
||||
$$
|
||||
}
|
||||
\questionandanswer[3.23]{
|
||||
[ 10 ]<3.5>假定采用IEEE 754单精度格式,写出十进制数63.25的二进制表示。
|
||||
}{
|
||||
$$
|
||||
63.25 = \frac{253}{4} = 1111\ 1101_{2} \times 2^{-2} =1.111\ 1101_{2} \times 2^{5} =(-1)^{0}\times (1+0.111\ 1101)\times 2^{132-127}
|
||||
$$
|
||||
根据 IEEE 754 标准,单精度格式为1位符号位、8位指数位、23位尾数位,132转化为8位二进制为$1000\ 0100$,所以十进制数63.25的二进制表示为
|
||||
$$
|
||||
\underbrace{0}_{\text{符号位}}|\underbrace{100\ 0010\ 0}_{\text{指数位}}|\underbrace{111\ 1101\ 0000\ 0000\ 0000\ 0000}_{\text{尾数位}}
|
||||
$$
|
||||
即
|
||||
$$
|
||||
0100\ 0010\ 0111\ 1101\ 0000\ 0000\ 0000\ 0000
|
||||
$$
|
||||
}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
249
计算机系统结构/平时作业/第二章作业.tex
Normal file
249
计算机系统结构/平时作业/第二章作业.tex
Normal file
@@ -0,0 +1,249 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
|
||||
\setcounter{chapter}{1}
|
||||
\begin{document}
|
||||
\chapter{指令:计算机的语言}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[2.3]{
|
||||
[ 5 ]<2.2,2.3>对于以下C语句,请编写相应的RISC-V汇编代码。假设变量f、g、h、i和j分别分配给寄存器x5、x6、x7、x28和x29。假设数组A和B的基地址分别在寄存器x10和x11中。
|
||||
\mint{C}|B[8] = A[i-j];|
|
||||
}{}
|
||||
{\kaishu
|
||||
% 这个minted环境放到自定义的命令里好像一直会报错
|
||||
这里假设数组A和B中的元素都是64位的,即“双字”,即8个字节。
|
||||
|
||||
\begin{minted}{asm}
|
||||
sub x30, x28, x29 // i-j,从A开始的双字偏移
|
||||
slli x30, x30, 3 // (i-j)*8, 从A开始的字节偏移
|
||||
add x30, x30, x10 // A[i-j]的地址
|
||||
ld x31, 0(x30) // 加载A[i-j]
|
||||
sd x31, 64(x11) // 放到B[8],8是双字偏移,转化成字节偏移就是64
|
||||
\end{minted}
|
||||
|
||||
% 由于minted用了verbatim,而verbatim的文档中有这样一句话:
|
||||
% You cannot use the verbatim environment inside user defined commands;
|
||||
% 可以看到在自己定义的环境里无法使用minted了。
|
||||
\begin{verification}
|
||||
|
||||
使用命令 \mintinline{shell}|riscv64-unknown-linux-gnu-gcc -S main.c -o main.s| 编译,左边是C代码,右边是汇编代码。
|
||||
|
||||
\begin{minipage}{0.5\linewidth}
|
||||
\begin{minted}{C}
|
||||
...
|
||||
long long *A, *B, i,j;
|
||||
B[8] = A[i - j];
|
||||
...
|
||||
\end{minted}
|
||||
\end{minipage}
|
||||
\begin{minipage}{0.5\linewidth}
|
||||
\begin{minted}{asm}
|
||||
...
|
||||
ld a4,-24(s0) // 加载i
|
||||
ld a5,-32(s0) // 加载j
|
||||
sub a5,a4,a5
|
||||
slli a5,a5,3
|
||||
ld a4,-40(s0) // 加载A
|
||||
add a4,a4,a5
|
||||
ld a5,-48(s0) // 加载B
|
||||
addi a5,a5,64
|
||||
ld a4,0(a4)
|
||||
sd a4,0(a5)
|
||||
...
|
||||
\end{minted}
|
||||
\end{minipage}
|
||||
|
||||
比较后可以发现基本差不多,但是最后一步赋值时这里先用了addi偏移64位,再存储,为什么不是直接64(a5)一步存储到从a5偏移64个字节的位置呢?
|
||||
\end{verification}
|
||||
}
|
||||
\questionandanswer[2.4]{
|
||||
[ 10 ]<2.2,2.3>对于以下 RISC-V汇编指令,相应的C语句是什么?假设变量f、g、h、i和j分别分配给寄存器x5、x6、x7、x28和x29。假设数组A和B的基地址分别在寄存器x10和x11中。}{}
|
||||
\begin{minted}[fontfamily=tt]{asm}
|
||||
slli x30, x5, 3 // x30 = f*8
|
||||
add x30, x10, x30 // x30 = &A[f]
|
||||
slli x31, x6, 3 // x31 = g*8
|
||||
add x31, x11, x31 // x31 = &B[g]
|
||||
ld x5, 0(x30) // f = A[f]
|
||||
|
||||
addi x12, x30, 8 // x12 = (&f + 1) = (原始的f)&A[f + 1]
|
||||
ld x30, 0(x12) // x30 = A[f + 1]
|
||||
add x30, x30, x5 // x30 = f + A[f + 1] = (原始的f)A[f] + A[f + 1]
|
||||
sd x30, 0(x31) // B[g] = A[f] + A[f + 1]
|
||||
\end{minted}
|
||||
|
||||
{\kaishu
|
||||
注释已写在上方,因此此汇编相应的C语句如下:
|
||||
\mint{C}|B[g] = A[f] + A[f + 1];|
|
||||
}
|
||||
\questionandanswer[2.10]{
|
||||
假设寄存器x5和x6分别保存值0x8000000000000000和0xD000000000000000。
|
||||
}{}
|
||||
|
||||
\questionandanswer[2.10.1]{
|
||||
[ 5 ]<2.4>以下汇编代码中x30的值是多少?
|
||||
\mint{asm}|add x30, x5, x6|
|
||||
}{
|
||||
由于后面全是0,只需要考虑最高4位二进制。x5为$8_{16}$即$1000_{2}$,x6为$\mathrm{D}_{16}$即$1101_{2}$,要注意最高位为1代表负数,由于负数已按照补码方式表示,所以可以直接相加。这里两个数都是负数,相加后为$1\ 0101_{2}$,进位忽略,即$0101_{2}=5_{16}$,此时x30的值为
|
||||
$$
|
||||
0 \mathrm{x} 5000000000000000
|
||||
$$
|
||||
}
|
||||
\questionandanswer[2.10.2]{
|
||||
[ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出?
|
||||
}{
|
||||
两个负数相加,结果的最高位为0,表示正数,不是预期结果,即产生了溢出。
|
||||
}
|
||||
\questionandanswer[2.10.3]{
|
||||
[ 5 ]<2.4>对于上面指定的寄存器x5和x6的内容,以下汇编代码中x30的值是多少?
|
||||
\mint{asm}|sub x30, x5, x6|
|
||||
}{
|
||||
还是只考虑最高4位二进制,最高位为1代表负数,直接相减不方便,可以先按照补码方式转换为相反数再相加,按照补码方式$1101_{2}$的相反数为$0011$,因此$1000_{2}-1101_{2}=1000_{2}+0011_{2}=1011_{2}$,即$\mathrm{B}_{16}$。因此x30的值为
|
||||
$$
|
||||
0 \mathrm{x} \mathrm{B}000000000000000
|
||||
$$
|
||||
}
|
||||
\questionandanswer[2.10.4]{
|
||||
[ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出?
|
||||
}{
|
||||
两个负数相减,结果仍为负数,最高位是1,为预期结果,无溢出。
|
||||
}
|
||||
\questionandanswer[2.10.5]{
|
||||
[ 5 ]<2.4>对于上面指定的寄存器x5和x6的内容,以下汇编代码中x30的值是多少?
|
||||
\mint{asm}|add x30, x5, x6|
|
||||
\vspace{-1.5em}
|
||||
\mint{asm}|add x30, x30, x5|
|
||||
}{
|
||||
第一行就是2.10.1的指令,之后执行第二行,按照最高位来看就是$0101_{2}+1000_{2}=1101_{2}$,即$\mathrm{D}_{16}$,所以x30的值为
|
||||
$$
|
||||
0 \mathrm{xD} 000000000000000
|
||||
$$
|
||||
}
|
||||
\questionandanswer[2.10.6]{
|
||||
[ 5 ]<2.4> x30中的结果是否为预期结果,或者是否溢出?
|
||||
}{
|
||||
结果的最高位为1,虽然仍是负数但显然不是$\mathrm{x}5+\mathrm{x}6+\mathrm{x}5$的结果,所以不是预期结果,产生了溢出。
|
||||
}
|
||||
\questionandanswer[2.27]{
|
||||
[ 5 ]<2.7>将以下循环转换为C代码。假设C语言级的整数i保存在寄存器x5中, x6保存名为result的C语言级的整数,x10保存整数MemArray的基址。
|
||||
}{}
|
||||
\begin{minted}{asm}
|
||||
addi x6, x0, 0 // result = 0
|
||||
addi x29, x0, 100 // x29 = 100
|
||||
LOOP: ld x7, 0(x10) // x7 = *MemArray
|
||||
add x5, x5, x7 // i = i + *MemArray
|
||||
addi x10, x10, 8 // MemArray++
|
||||
addi x6, x6, 1 // result++
|
||||
blt x6, x29, LOOP // (result < 100)
|
||||
\end{minted}
|
||||
|
||||
{\kaishu
|
||||
注释已写在上方。根据题意和命名来看, \mintinline{C}{MemArray} 应该是整数数组而不是整数吧,不然总不能 \mintinline{C}{(&MemArray)++} 吧。这里假设整数都是64位的,所以 \mintinline{C}{MemArray++} 对应的汇编代码是增加8个字节,即 \mintinline{asm}{addi x10, x10, 8 }。
|
||||
|
||||
根据注释分析出的结果,可以得到C代码:
|
||||
\begin{minted}{C}
|
||||
for (int result = 0; result < 100; result++) {
|
||||
i += *(MemArray++);
|
||||
}
|
||||
\end{minted}
|
||||
|
||||
为什么这里是 \mintinline{C}{result} 作为循环变量而 \mintinline{C}{i} 作为结果啊???但按照题意分析出来就是这样。
|
||||
}
|
||||
|
||||
\questionandanswer[2.31]{
|
||||
[ 20 ]<2.8>将函数f转换为RISC-V汇编语言。假设g的函数声明是 \mintinline{C}{int g(int a,int b)}。函数f的代码如下:
|
||||
}{}
|
||||
\begin{minted}{C}
|
||||
int f(int a, int b, int c, int d) {
|
||||
return g(g(a,b), c+d);
|
||||
}
|
||||
\end{minted}
|
||||
{\kaishu
|
||||
注意:
|
||||
% \setlist[2]{label=\alph{enumii}.,listparindent=\parindent}
|
||||
\begin{enumerate}[label=(\arabic{enumii})]
|
||||
\item 调用函数时参数应该是从右往左计算的,但是需要保存的局部变量是从左到右保存的;
|
||||
\item 栈指针应该是16字节(四字)对齐的;
|
||||
\item 返回地址应该是调用者保存的。
|
||||
\end{enumerate}
|
||||
\begin{minted}{asm}
|
||||
f: // 保存自己要用到的保存寄存器,好像没有
|
||||
// 此时a,b,c,d 分别保存在 x10, x11, x12, x13 中
|
||||
addw x5, x12, x13 // 计算c+d,4字节整数的要加w
|
||||
addi sp, sp, -16 // 移动栈指针,栈指针应该是16字节对齐的?
|
||||
sd x1, 8(sp) // 保存x1,返回地址
|
||||
sd x5, 0(sp) // 保存x5,这里int类型4个字节,由于对齐产生了空位
|
||||
// a和b在调用g(a,b)后不会用到,所以不用保存
|
||||
// ***开始计算g(a,b)
|
||||
// a和b已经在x10和x11中所以不需要移动
|
||||
jal x1, g // 跳转到g
|
||||
// g的返回值在x10中,正好是下一次调用的第一个参数
|
||||
ld x11, 0(sp) // 恢复x5,直接恢复到x11上避免再移动
|
||||
// x1不需要着急恢复,马上就是下一次调用了
|
||||
// 只恢复了x5,栈指针要16字节对齐不能移动
|
||||
// ***开始计算g(g(a,b), c+d)
|
||||
// 第二个参数已从x5恢复
|
||||
// 第一个参数已经在x10中
|
||||
jal x1, g // 跳转到g
|
||||
// g的返回值在x10中,不需要移动
|
||||
ld x1, 0(sp) // 恢复x1
|
||||
addi sp, sp, 16 // 恢复栈指针
|
||||
// 恢复保存的保存寄存器,好像没有
|
||||
jalr x0, x1 // 返回
|
||||
\end{minted}
|
||||
}
|
||||
\vspace{2em}
|
||||
|
||||
\begin{verification}
|
||||
使用命令 \mintinline{shell}|riscv64-unknown-linux-gnu-gcc -S main.c -o main.s| 编译。
|
||||
\baselineskip=1.4em
|
||||
\begin{minted}{asm}
|
||||
f:
|
||||
.LFB0:
|
||||
.cfi_startproc
|
||||
addi sp,sp,-32
|
||||
.cfi_def_cfa_offset 32
|
||||
sd ra,24(sp)
|
||||
sd s0,16(sp)
|
||||
.cfi_offset 1, -8
|
||||
.cfi_offset 8, -16
|
||||
addi s0,sp,32
|
||||
.cfi_def_cfa 8, 0
|
||||
mv a5,a0
|
||||
mv a4,a3
|
||||
sw a5,-20(s0)
|
||||
mv a5,a1
|
||||
sw a5,-24(s0)
|
||||
mv a5,a2
|
||||
sw a5,-28(s0)
|
||||
mv a5,a4
|
||||
sw a5,-32(s0)
|
||||
lw a4,-24(s0)
|
||||
lw a5,-20(s0)
|
||||
mv a1,a4
|
||||
mv a0,a5
|
||||
call g
|
||||
mv a5,a0
|
||||
mv a3,a5
|
||||
lw a5,-28(s0)
|
||||
mv a4,a5
|
||||
lw a5,-32(s0)
|
||||
addw a5,a4,a5
|
||||
sext.w a5,a5
|
||||
mv a1,a5
|
||||
mv a0,a3
|
||||
call g
|
||||
mv a5,a0
|
||||
mv a0,a5
|
||||
ld ra,24(sp)
|
||||
.cfi_restore 1
|
||||
ld s0,16(sp)
|
||||
.cfi_restore 8
|
||||
.cfi_def_cfa 2, 32
|
||||
addi sp,sp,32
|
||||
.cfi_def_cfa_offset 0
|
||||
jr ra
|
||||
.cfi_endproc
|
||||
\end{minted}
|
||||
\end{verification}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
363
计算机系统结构/平时作业/第五章作业1.tex
Normal file
363
计算机系统结构/平时作业/第五章作业1.tex
Normal file
@@ -0,0 +1,363 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
|
||||
\setcounter{chapter}{4}
|
||||
\begin{document}
|
||||
\chapter{大而快:层次化存储}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.2]{
|
||||
cache对于为处理器提供高性能存储器层次结构非常重要。下面是64位存储器地址访问顺序列表,以字地址的形式给出。
|
||||
\mint{C}|0x03, 0xb4, 0x2b, 0x02, 0xbf, 0x58, 0xbe, 0x0e, 0xb5, 0x2c, 0xba, 0xbd|
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.2.1]{
|
||||
[ 10 ]<5.3>对于一个有16个cache块、每个块大小为1个字的cache,标识这些引用的二进制字地址、标签和索引。此外,假设cache最初为空,列出每个地址的访问会命中还是失效。
|
||||
}{
|
||||
缓存块有16个,也就是$2^{4}$,所以索引的大小为4个位,给出的地址为两个十六进制位,即8位二进制,所以索引实际上就是低4位地址,标签实际上是就是高4位地址,是否命中只需要看在某一行之前是否出现过相同的标签和索引。
|
||||
\includexopp[1.5]{5.2.1.1}
|
||||
}
|
||||
|
||||
\questionandanswer[5.2.2]{
|
||||
[ 10 ]<5.3>对于一个有8个cache块、每个块大小为2个字的cache,标识这些引用的二进制字地址、标签、索引和偏移。此外,假设cache最初为空,列出每个地址的访问会命中还是失效。
|
||||
}{
|
||||
每个缓存块的大小变大了,缓存块的数量变小了,所以偏移实际上就是把索引的最低位移过来了。同样是否命中只需要看在某一行之前是否出现过相同的标签和索引,并且在这之后没有索引相同但标签不同的访问(不然就被置换了),不需要考虑偏移。这里的命中一列,不命中使用$\times $表示,而命中使用〇表示(用〇比√从视觉上更好区分)。
|
||||
\includexopp[1.5]{5.2.2.1}
|
||||
}
|
||||
\questionandanswer[5.2.3]{
|
||||
[ 20 ]<5.3,5.4>请针对给定的访问顺序优化cache设计。这里有三种直接映射cache的设计方案,每种方案都可以容纳8个字的数据:
|
||||
\begin{itemize}
|
||||
\item C1的块大小为1个字
|
||||
\item C2的块大小为2个字
|
||||
\item C3的块大小为4个字
|
||||
\end{itemize}
|
||||
}{
|
||||
\includexopp[1.5]{5.2.3.1}
|
||||
可见C2的缓存命中次数最多,所以这里应该选择C2方案。
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.3]{
|
||||
按照惯例, cache 以它包含的数据量来进行命名(例如,4KiB cache可以容纳4KiB的数据),但是cache还需要SRAM来存储元数据,如标签和有效位等。本题研究cache的配置如何影响实现它所需的SRAM总量以及cache 的性能。对所有的部分,都假设cache是字节可寻址的,并且地址和字都是64位的。
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.3.1]{
|
||||
[ 10 ]<5.3>计算实现每个块大小为2个字的32KiB cache 所需的总位数。
|
||||
}{
|
||||
32KiB即$2^{5}\times 2^{10}=2^{15} \text{B}$,每个块大小为2个字即$2\times 64 / 8=2^{4}\text{B}$,由于字节寻址所以偏移为4位,共有$2^{15-4}=2^{11}$个块,因此索引为11位,那么地址为64位,所以标签就是$64-4-11=49$位,有效位为1位。每个块都有一个标签和一个有效位,所以SRAM存储的元数据需要$2^{11}\times (49+1) = 102400$位。再加上存储数据的$2^{15+3}$位,即\boldkai{$\bm{2^{11}\times (49+1) + 2^{15+3} = 364544}$位}。
|
||||
}
|
||||
\questionandanswer[5.3.2]{
|
||||
[ 10 ]<5.3>计算实现每个块大小为16个字的64KiB cache所需的总位数。这个cache 比 5.3.1中描述的32KiB cache大多少?(请注意,通过更改块大小,我们将数据量增加了一倍,但并不是将cache的总大小加倍。)
|
||||
}{
|
||||
64KiB即$2^{6}\times 2^{10}=2^{16}\text{B}$,每个块的大小为16个字即$16\times 64/8=2^{7}$B,所以偏移为7位。
|
||||
|
||||
共有$2^{16-7}=2^{9}$个块,索引为9位。
|
||||
标签为$64-7-9 = 48$位,有效位为1位。
|
||||
|
||||
所以需要的总位数为$2^{9}\times (48+1)+2^{16+3} = \bm{549376}$位,比5.3.1大了$\frac{549376-364544}{364544} = 0.507022471910112$,即约大了$\bm{50.70\%}$。
|
||||
}
|
||||
\questionandanswer[5.3.3]{
|
||||
[ 5 ]<5.3>解释为什么5.3.2中的64KiB cache尽管数据量比较大,但是可能会提供比5.3.1中的cache更慢的性能。
|
||||
}{
|
||||
5.3.2中的64KiB cache每个块的大小比5.3.1大,所以每次缓存失效时都需要把更多的数据读入缓存或写回存储,所以失效代价会更大。而且总块数更少,所以命中率会更低。
|
||||
}
|
||||
\questionandanswer[5.3.4]{
|
||||
[ 10 ]<5.3,5.4>生成一系列读请求,这些请求需要在32KiB的两路组相联cache上的失效率低于在5.3.1中描述的cache的失效率。
|
||||
}{
|
||||
在5.3.1中标签49位,索引11位,偏移4位。若改成两路组相联,那么每个块就只有1个字了,且索引为10位,标签为50位,每个块都各自有一个标签和一个有效位。所以只需要构造索引相同的地址,但是标签不同,即可让5.3.1的直接映射失效,而两路组相联不失效。所以构造的读请求地址如下:\boldkai{0x00000, 0x10000, 0x00000, 0x10000, 0x00000, 0x10000, ……},前两次请求都未命中缓存,但从第三次请求开始,直接映射会一直失效,而两路组相联会一直命中,所以符合要求。
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[5.5]{
|
||||
对一个64位地址的直接映射cache的设计,地址的以下位用于访问cache。
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
标签 & 索引 & 偏移 \\
|
||||
\midrule
|
||||
$63 \sim 10$ & $9\sim 5$ & $4\sim 0$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{}
|
||||
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.5.1]{
|
||||
[ 5 ]<5.3>cache 块大小为多少(以字为单位)?
|
||||
}{
|
||||
偏移是0到4,也就是5位,所以cache块的大小是$2^{5}=32$B,一个字是4B,所以cache块的大小是\boldkai{8}个字。
|
||||
}
|
||||
\questionandanswer[5.5.2]{
|
||||
[ 5 ]<5.3 >cache块有多少个?
|
||||
}{
|
||||
索引从5到9,也就是5位,所以cache块有$2^{5}=\bm{32}$个。
|
||||
}
|
||||
\questionandanswer[5.5.3]{
|
||||
[ 5 ]<5.3>这种cache实现所需的总位数与数据存储位之间的比率是多少?
|
||||
}{
|
||||
标签是10到63,也就是54位,还需要一位有效位,所以实现所需的总位数是$2^{5}\times (54+1)+2^{5}\times 2^{5} \times 8 = 9952$,数据存储位是$2^{5}\times 2^{5}\times 8 = 8192$。所以这种cache实现所需的总位数与数据存储位之间的比率是$\frac{9952}{8192}\times 100\% = \bm{121.484375\%}$。
|
||||
}
|
||||
\questionandanswer[5.5.4]{
|
||||
下表记录了从上电开始cache访问的字节地址。
|
||||
\begin{center}
|
||||
\begin{tabular}{cccccccccccccc}
|
||||
\toprule
|
||||
十六进制 & 00 & 04 & 10 & 84 & E8 & A0 & 400 & 1E & 8C & C1C & B4 & 884 \\
|
||||
\midrule
|
||||
十进制 & 0 & 4 & 16 & 132 & 232 & 160 & 1024 & 30 & 140 & 3100 & 180 & 2180 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
[ 20 ]<5.3>对每一次访问,列出:它的标签、索引和偏移;指出命中还是失效;替换了哪个字节(如果有的话)。
|
||||
}{
|
||||
\includexopp[1.5]{5.5.4.1}
|
||||
|
||||
图中的Address表示十六进制的地址,Tag为标签,,Index为索引,Offset为偏移,Hit列中勾表示命中,叉表示失效;Sub表示替换的字节地址范围。
|
||||
}
|
||||
\questionandanswer[5.5.5]{
|
||||
[ 5 ]<5.3>命中率是多少?
|
||||
}{
|
||||
12次访问中有4次命中,所以命中率为$\frac{1}{3} \approx \bm{33.33}\%$。
|
||||
}
|
||||
\questionandanswer[5.5.6]{
|
||||
[ 5 ]<5.3>列出cache的最终状态,每个有效表项表示为<索引,标签,数据>的记录。例如:
|
||||
\mint{C}|<0, 3, Mem[0xC00]-Mem[0xC1F]|
|
||||
}{}
|
||||
{\kaishu
|
||||
对于某个索引,从后往前看Index,首次找到这个索引的地方所在的标签即为最终状态的标签,之后把偏移全填为0,再把地址转成十六进制(已按4位一组用蓝色线分隔),即为起始地址;把偏移量全填为1,再把地址转成十六机制,即为结束地址。
|
||||
|
||||
例如,对于0号索引,最后一次出现是在地址C1C的地方,它的标签为11,也就是十六进制的3。之后看这一行的地址为 11\!|\!00 000\!|\!1 1100,将偏移量全填成0,也就是 11\!|\!00 000\!|\!0 0000,这就是0xC00,将偏移量全填成1,也就是 11\!|\!00 000\!|\!1 1111,这就是 0xC1F,所以结果为 \mintinline{C}{<0, 3, Mem[0xC00]-Mem[0xC1F]} 。
|
||||
|
||||
所以cache的最终状态如下:
|
||||
\begin{minted}{C}
|
||||
<0, 3, Mem[0xC00]-Mem[0xC1F]>
|
||||
<4, 2, Mem[0x880]-Mem[0x89F]>
|
||||
<5, 0, Mem[0x0A0]-Mem[0x0BF]>
|
||||
<7, 0, Mem[0x0E0]-Mem[0x0FF]>
|
||||
\end{minted}
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.7]{
|
||||
考虑以下的程序和cache行为:
|
||||
\begin{center}
|
||||
\begin{tabularx}{\linewidth}{ZZZZZ}
|
||||
\toprule
|
||||
每1000条指令的数据读次数 & 每1000条指令的数据写次数 & 指令cache失效率 & 数据cache失效率 & 块大小(字节) \\
|
||||
\midrule
|
||||
250 & 100 & 0.30\% & 2\% & 64 \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{center}
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.7.1]{
|
||||
[ 10 ]<5.3,5.8>假设一个带有写直达、写分配cache的CPU实现了2的CPI,那么RAM和cache之间的读写带宽(用每个周期的字节数进行测量)是多少?(假设每个失效都会生成一个块的请求。)
|
||||
}{
|
||||
CPI为2,每条指令2个周期,那么每个周期就是0.5条指令,那么指令的读带宽就是$0.5\times 0.30\% \times 64 = 0.096$字节/周期,而数据的读带宽就是$0.5\times \frac{250}{1000}\times 2\%\times 64 = 0.16$字节/周期。
|
||||
% $$
|
||||
% 0.5\times 0.30\% \times 64 + 0.5\times \frac{250}{1000}\times 2\%\times 64 = 0.256 \text{字节/周期}
|
||||
% $$
|
||||
|
||||
由于写直达,所以不管是否失效,都需要将某个寄存器的数据写入到RAM中,RISC-V中的寄存器为64位,也就是8个字节,那么写入RAM的带宽为$0.5\times \frac{100}{1000}\times 8 = 0.4$字节/周期。
|
||||
|
||||
由于写分配,所以当失效时,需要先(后*)将RAM中的数据放回到寄存器中,这时会产生读带宽$0.5\times \frac{100}{1000}\times 2\%\times 64 = 0.064$字节/周期。
|
||||
|
||||
(*如果先取回数据,那么需要同时写入cache和RAM;如果后取回数据,那么就只写入RAM,取回时就已经是最新的数据了。)
|
||||
|
||||
所以总的读带宽为
|
||||
$
|
||||
0.096+0.16+0.064 = \bm{0.32} \text{字节/周期}
|
||||
$,
|
||||
总的写带宽为
|
||||
$
|
||||
\bm{0.4} \text{字节/周期}
|
||||
$。
|
||||
}
|
||||
\questionandanswer[5.7.2]{
|
||||
[ 10 ]<5.3,5.8>对于一个写回、写分配 cache来说,假设替换出的数据cache块中有30\%是脏块,那么为了实现CPI为2,读写带宽需要达到多少?
|
||||
}{
|
||||
指令的读带宽仍为0.096字节/周期,数据的读带宽仍为0.16字节/周期。但是这里的“写分配”似乎没用?写的时候如果不失效,那cache中就是最新的;如果失效,那先替换旧的块,之后再写入cache,也不会出现分配的情况。
|
||||
|
||||
对于写回策略,当写不失效时,不需要在cache与RAM中传输数据。当写失效时,如果被替换的cache块是“脏”块,也就是被修改过,那么需要将这个块写入到RAM中。当读失效时,仍然会产生cache块的替换,所以如果是“脏”块也需要写入RAM。并且这里没有提及缓冲的事,所以认为没有写缓冲,那么写带宽为$0.5\times \left( \frac{250}{1000}+\frac{100}{1000} \right) \times 2\%\times 30\%\times 64 = 0.0672$。
|
||||
|
||||
所以总的读带宽为$\bm{0.096}$字节/周期,总的写带宽为$\bm{0.0672}$字节/周期。
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.9]{
|
||||
cache块大小(B)可以影响失效率和失效延迟。假设一台机器的基本CPI为1,每条指令的平均访问次数(包括指令和数据)为1.35,给定以下各种不同cache块大小的失效率,找到能够最小化总失效延迟的cache块大小。
|
||||
|
||||
\begin{center}
|
||||
\begin{tabularx}{0.8\linewidth}{ZZZZZ}
|
||||
\toprule
|
||||
8:4\% & 16:3\% & 32:2\% & 64:1.5\% & 128:1\% \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{center}
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.9.1]{
|
||||
[ 10 ]<5.3>失效延迟为20$\times $B周期时,最优块大小是多少?
|
||||
}{
|
||||
总失效延迟为$1.35\times \text{失效率}\times 20\times B$周期,所以所求问题为
|
||||
$$
|
||||
\mathop{\arg\min}_{i}
|
||||
\quad 1.35\times \text{失效率}_{i}\times 20\times B_i
|
||||
$$
|
||||
$$
|
||||
\left\{ \left( B_i, \text{失效率}_{i} \right) \right\} = \{ (8,0.04), (16,0.03), (32,0.02), (64,0.015), (128,0.01) \}
|
||||
$$
|
||||
枚举可得
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
$B_i$ & $\text{失效率}_{i}$ & 总失效延迟 \\
|
||||
\midrule
|
||||
8 & 0.04 & 8.64 \\
|
||||
16 & 0.03 & 12.96\\
|
||||
32 & 0.02 & 17.28\\
|
||||
64 & 0.015 & 25.92\\
|
||||
128 & 0.01 & 34.56\\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\boldkai{所以最优块大小是8。}
|
||||
}
|
||||
\questionandanswer[5.9.2]{
|
||||
[10]<5.3>失效延迟为24+B周期时,最优块大小是多少?
|
||||
}{
|
||||
总失效延迟为$1.35\times \text{失效率}\times (24+B)$周期,所以所求问题为
|
||||
$$
|
||||
\mathop{\arg\min}_{i}
|
||||
\quad 1.35\times \text{失效率}\times (24+B)
|
||||
$$
|
||||
$$
|
||||
\left\{ \left( B_i, \text{失效率}_{i} \right) \right\} = \{ (8,0.04), (16,0.03), (32,0.02), (64,0.015), (128,0.01) \}
|
||||
$$
|
||||
枚举可得
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
$B_i$ & $\text{失效率}_{i}$ & 总失效延迟 \\
|
||||
\midrule
|
||||
8 & 0.04 & 1.728\\
|
||||
16 & 0.03 & 1.62 \\
|
||||
32 & 0.02 & 1.512\\
|
||||
64 & 0.015 & 1.782\\
|
||||
128 & 0.01 & 2.052\\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
|
||||
\boldkai{所以最优块大小是32。}
|
||||
}
|
||||
\questionandanswer[5.9.3]{
|
||||
5.9.3[ 10 ]<5.3>失效延迟为定值时,最优块大小是多少?
|
||||
}{
|
||||
总失效延迟为$1.35\times \text{失效率}\times \text{定值}$,所以失效率越小,总失效延迟就越小,\boldkai{所以最优块大小是128。}
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.10]{
|
||||
本题研究不同cache容量对整体性能的影响。通常, cache访问时间与cache容量成正比。假设主存访问需要70ns,并且在所有指令中有36\%的指令访问数据内存。下表显示了两个处理器P1和P2中每个处理器各自的L1 cache 的数据。
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
& L1大小 & L1失效率 & L1命中时间 \\
|
||||
\midrule
|
||||
P1 & 2 KiB & 8.0\% & 0.66 ns \\
|
||||
P2 & 4 KiB & 6.0\% & 0.90 ns \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{}
|
||||
\questionandanswer[5.10.1]{
|
||||
[ 5 ]<5.4>假设L1命中时间决定Pl和P2的时钟周期时间,它们各自的时钟频率是多少?
|
||||
}{
|
||||
% 设P1的时钟周期频率为C1,CPI为CPI,则
|
||||
% % + C_1 \times CPI \times (1+0.36)\times 0.08 \times 70
|
||||
% $$
|
||||
% C_1\times CPI \times 0.36\% \times 2k = 0.66
|
||||
% $$
|
||||
P1:$\frac{1}{0.66 \times 10^{-9}} \approx 1.515 \times 10^{9} \text{Hz}=1.515 \text{GHz}$。
|
||||
|
||||
P2:$\frac{1}{0.90 \times 10^{-9}} \approx 1.111 \times 10^{9} \text{Hz}=1.111 \text{GHz}$。
|
||||
}
|
||||
\questionandanswer[5.10.2]{
|
||||
[ 10 ]<5.4>P1和P2各自的AMAT (平均内存访问时间)是多少(以周期为单位)?
|
||||
}{
|
||||
这里的平均内存访问时间应该是每条指令的,而且是已知产生内存访问的情况下计算的,
|
||||
% 所以应该为$36\% \times (0.66 + 8\% \times 70) \times 10^{-9}\times \frac{1}{0.66\times 10^{-9}} = 3.41454545454545$
|
||||
所以为
|
||||
$$
|
||||
\text{P1:}\quad 1+8\% \times \left\lceil \frac{70}{0.66} \right\rceil = 9.56 \text{周期}
|
||||
$$
|
||||
$$
|
||||
\text{P2:} \quad 1+6\%\times \left\lceil \frac{70}{0.90} \right\rceil = 5.68 \text{周期}
|
||||
$$
|
||||
}
|
||||
\questionandanswer[5.10.3]{
|
||||
[ 5 ]<5.4>假设基本CPI为1.0而且没有任何内存停顿,那么P1和P2的总CPI是多少?哪个处理器更快?(当我们说“基本CPI为1.0”时,意思是指令在一个周期内完成,除非指令访问或者数据访问导致cache失效。)
|
||||
}{
|
||||
这里计算的总CPI仍然是每条指令的周期数,但是并没有已知产生内存访问,所以
|
||||
$$
|
||||
\text{P1:}\quad 1+ 8\% \times \left\lceil \frac{70}{0.66} \right\rceil +36\% \times 8\% \times \left\lceil \frac{70}{0.66} \right\rceil = 12.6416 \text{周期/指令}
|
||||
$$
|
||||
$$
|
||||
\text{P2:} \quad 1+ 6\% \times \left\lceil \frac{70}{0.90} \right\rceil + 36\% \times 6\% \times \left\lceil \frac{70}{0.90} \right\rceil = 7.3648 \text{周期/指令}
|
||||
$$
|
||||
\boldkai{所以P2更快。}
|
||||
}
|
||||
\questionandanswer[]{
|
||||
对于接下来的三个问题,我们将考虑向P1添加L2 cache(可能弥补其有限的Ll cache容量)。解决这些问题时,请使用上一个表中的Ll cache容量和命中时间。L2失效率表示的是其局部失效率。
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
L2大小 & L2失效率 & L2命中时间 \\
|
||||
\midrule
|
||||
1 MiB & 95\% & 5.62 ns \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{}
|
||||
\questionandanswer[5.10.4]{
|
||||
[ 10 ]<5.4>添加L2 cache的P1的AMAT是多少?在使用L2 cache后,AMAT变得更好还是更差?
|
||||
}{
|
||||
$$
|
||||
1+8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times 95\% \times \left\lceil \frac{70}{0.66} \right\rceil = 9.852 \text{周期}
|
||||
$$
|
||||
显然使用L2 cache后,AMAT变得更差。
|
||||
}
|
||||
\questionandanswer[5.10.5]{
|
||||
[ 5 ]<5.4>假设基本CPI为1.0而且没有任何内存停顿,那么添加L2 cache的P1的总CPI是多少?
|
||||
}{
|
||||
$$
|
||||
1+ 8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times 95\% \times \left\lceil \frac{70}{0.66} \right\rceil + 36\% \times \left( 8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times 95\% \times \left\lceil \frac{70}{0.66} \right\rceil \right) = 13.03872
|
||||
$$
|
||||
}
|
||||
\questionandanswer[5.10.6]{
|
||||
[ 10 ]<5.4>为了使具有L2 cache的P1比没有L2 cache的P1更快,需要L2的失效率为多少?
|
||||
}{
|
||||
设L2的失效率最大为$x$,则
|
||||
$$
|
||||
1+8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times x \times \left\lceil \frac{70}{0.66} \right\rceil = 9.56
|
||||
$$
|
||||
解得
|
||||
$ x = \frac{98}{107} \approx 0.9159 = 91.59\%$,所以L2的失效率需要小于$91.59\%$。
|
||||
}
|
||||
\questionandanswer[5.10.7]{
|
||||
[ 15 ]<5.4>为了使具有L2 cache 的P1比没有L2 cache的P2更快,需要L2的失效率为多少?
|
||||
}{
|
||||
设L2的失效率最大为$x$,由于涉及到P1和P2,所以这里需要比较的是实际的执行时间,即CPI $\times $ 每条指令执行的时间,由于5.10.1的假设,所以每条指令执行的时间就是L1命中时间。
|
||||
$$
|
||||
\begin{aligned}
|
||||
&\left[1+ 8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times x\times \left\lceil \frac{70}{0.66} \right\rceil + 36\% \times \left( 8\% \times \left\lceil \frac{5.62}{0.66} \right\rceil + 8\% \times x\times \left\lceil \frac{70}{0.66} \right\rceil \right) \right] \times 0.66 \\
|
||||
&= 7.3648 \times 0.90 \\
|
||||
\end{aligned}
|
||||
$$
|
||||
解得
|
||||
$ x = \frac{27719}{40018} \approx 0.6927 = 69.27 \%
|
||||
$,所以L2的失效率需要小于$69.27\%$。
|
||||
}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
245
计算机系统结构/平时作业/第五章作业2.tex
Normal file
245
计算机系统结构/平时作业/第五章作业2.tex
Normal file
@@ -0,0 +1,245 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
|
||||
\setcounter{chapter}{4}
|
||||
\begin{document}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.11]{
|
||||
本题研究不同cache 设计的效果,特别是将组相联cache 与5.4节中的直接映射cache进行比较。有关这些练习,请参阅下面显示的字地址序列:
|
||||
\mint{C}|0x03, 0xb4, 0x2b, 0xbe, 0x58, 0xbf, 0x0e, 0x1f, 0xb5, 0xba, 0x2e, 0xce|
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.11.1]{
|
||||
[ 10 ]<5.4>绘制块大小为2字、总容量为48字的三路组相联cache 的组织结构图。图中应有类似于图5-18的样式,还应该清楚地显示标签和数据字段的宽度。
|
||||
}{
|
||||
\includexopp[1.1]{5.11.1.1}
|
||||
|
||||
块大小为$2=2^{1}$字,所以地址中的偏移量为1位。$48 \div 3 \div 2 = 8$,所以索引有$8=2^{3}$行,所以地址中的索引有3位。所以标签为$64-3-1 = \bm{60}$位。一个字应该是32位,并且块大小为2字,那么数据字段就是$2\times 32 = \bm{64}$位。
|
||||
}
|
||||
\questionandanswer[5.11.2]{
|
||||
[ 10 ]<5.4>从5.11.1中记录cache 的行为。假设cache使用LRU替换策略。对于每一次 cache访问,确定:
|
||||
\begin{itemize}
|
||||
\item 二进制字地址。
|
||||
\item 标签。
|
||||
\item 索引。
|
||||
\item 偏移。
|
||||
\item 访问会命中还是失效。
|
||||
\item 在处理访问后,cache每一路中有哪些标签。
|
||||
\end{itemize}
|
||||
}{
|
||||
\includexopp[1.1]{5.11.2.1}
|
||||
|
||||
图中的Hit(命中)列用蓝色圈表示命中,命中时在上一行会有蓝色框表示命中了哪个缓存。红色下划线表示在这一时刻产生了缓存替换,标记了新的缓存放在哪个位置。
|
||||
}
|
||||
\questionandanswer[5.11.3]{
|
||||
[ 5 ]<5.4>绘制块大小为1字、总容量为8字的全相联cache 的组织结构图。图中应有类似于图5-18的样式,还应该清楚地显示标签和数据字段的宽度。
|
||||
}{
|
||||
\includexopp[1.1]{5.11.3.1}
|
||||
|
||||
地址是按字索引的,块大小是1字,所以没有偏移量。由于是全相联,所以没有索引位,所以标签为64位。总容量为8字,所以8个块排成一行。数据字段就是块大小即1字 $=$ 32位。
|
||||
}
|
||||
\questionandanswer[5.11.4]{
|
||||
[ 10 ]<5.4>从5.11.3中记录cache的行为。假设cache使用LRU替换策略。对于每一次 cache访问,确定:
|
||||
\begin{itemize}
|
||||
\item 二进制字地址。
|
||||
\item 标签。
|
||||
\item 索引。
|
||||
\item 偏移。
|
||||
\item 访问会命中还是失效。
|
||||
\item 在处理访问后,cache每一路中有哪些标签。
|
||||
\end{itemize}
|
||||
}{
|
||||
\includexopp[1.1]{5.11.4.1}
|
||||
|
||||
图中的Hit(命中)列用蓝色圈表示命中,命中时在上一行会有蓝色框表示命中了哪个缓存。红色下划线表示在这一时刻产生了缓存替换,标记了新的缓存放在哪个位置。
|
||||
}
|
||||
\questionandanswer[5.11.5]{
|
||||
[ 5 ]<5.4>绘制块大小为2字、总容量为8字的全相联cache 的组织结构图。图中应有类似于图5-18的样式,还应该清楚地显示标签和数据字段的宽度。
|
||||
}{
|
||||
\includexopp[1.1]{5.11.5.1}
|
||||
|
||||
块大小为2字,所以偏移量为1位,一个字是32位,所以数据大小为64位。总容量为8字,所以有4个块,全相联所以4个块排成一行,没有索引位,所以标签为$64-1=63$位。
|
||||
}
|
||||
\questionandanswer[5.11.6]{
|
||||
[ 10 ]<5.4>从5.11.5中记录cache 的行为。假设cache使用LRU替换策略。对于每一次 cache访问,确定:
|
||||
\begin{itemize}
|
||||
\item 二进制字地址。
|
||||
\item 标签。
|
||||
\item 索引。
|
||||
\item 偏移。
|
||||
\item 访问会命中还是失效。
|
||||
\item 在处理访问后,cache每一路中有哪些标签。
|
||||
\end{itemize}
|
||||
}{
|
||||
\includexopp[1.1]{5.11.6.1}
|
||||
|
||||
图中Tag列中的蓝色线表示4位分隔,便于二进制转十六进制。Hit(命中)列用蓝色圈表示命中,在命中时右侧的蓝色下划线表示命中了哪一路的标签,同时也代表产生了一次访问(相当于LRU把它放到链表最后)。在未命中时右侧的红色下划线表示替换了哪一路的标签,同时也代表产生了一次访问。
|
||||
|
||||
所以,对于每一行,填写方法是:先把Tag转成十六进制,再查看Way(这里全相联就是四路组相联,Way表示路)中是否有这个十六进制地址,如果有,表示命中,那就把上一行的Way复制下来,并在命中的地址上划一条蓝色下划线;如果没有,表示未命中,就从这行开始向上查看最近的哪条下划线距离最远(表示最久没访问),那么就替换这个地址,其他地址不变,替换后在这个地址上划一条红色下划线。
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.12]{
|
||||
多级cache是一种重要的技术,可以在克服一级cache提供的有限空间的同时仍然保持速度。考虑具有以下参数的处理器:
|
||||
\begin{center}
|
||||
\small
|
||||
\begin{tabularx}{\linewidth}{ZZZZZZZZ}
|
||||
\toprule
|
||||
无内存停顿的基本CPI & 处理器速度 & 主存访问时间 & 每条指令的 L1 cache 的失效率 * & L2 直接映射 cache 速度 & L2 直接映射 cache 全局失效率 & L2八路组相联速度 & L2八路组相联cache全局失效率 \\
|
||||
\midrule
|
||||
1.5 & 2GHz & 100ns & 7\% & 12cycles & 3.5\% & 28cycles & 1.5\% \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
*Ll cache失效率是针对每条指令而言的。假设Ll cache的总失效数量(包括指令和数据)为总指令数的7\%。
|
||||
\end{center}
|
||||
}{
|
||||
\textcolor{red}{\Large 这题的参考答案有误,答案当成局部失效率计算了。(全局失效率与局部失效率的定义在课本第290页)}
|
||||
}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.12.1]{
|
||||
[ 10 ]<5.4>使用以下方法计算表中处理器的CPI:仅有Ll cache;使用L2直接映射cache;使用L2八路组相联cache。如果主存访问时间加倍,这些数据会如何变化?(将每个更改作为绝对CPI和百分比更改。)请注意L2 cache可以隐藏慢速内存影响的程度。
|
||||
}{
|
||||
先计算主存访问的时钟周期,$2 \times 10^{9} \text{Hz} \times 100 \times 10^{-9} \text{s} = 200 \text{cycles}$。
|
||||
|
||||
全局失效率是指访问L2并且L2失效的指令数量与全部指令数量的比值;局部失效率是指访问L2并且L2失效的指令数量与访问L2的指令的数量的比值。
|
||||
|
||||
|
||||
\begin{itemize}
|
||||
\item 仅有L1 cache时,CPI为$1.5+7\% \times 200 = \bm{15.5}$周期;
|
||||
\item 使用L2直接映射cache时,CPI为 $1.5+7\% \times 12 + 3.5\% \times 200 = \bm{9.34}$周期;
|
||||
\item 使用L2八路组相联cache时,CPI为 $1.5+7\% \times 28 + 1.5\% \times 200 = \bm{6.46}$周期。
|
||||
\end{itemize}
|
||||
|
||||
如果主存访问时间加倍,
|
||||
\begin{itemize}
|
||||
\item 仅有L1 cache时,CPI为$1.5+7\% \times 400 = \bm{29.5}$周期,增加了$29.5-15.5=\bm{14}$个周期,增加了$14 / 15.5 = \bm{90.3225806451613\%}$;
|
||||
\item 使用L2直接映射cache时,CPI为 $1.5+7\% \times 12 + 3.5\% \times 400 = \bm{16.34}$周期,增加了$16.34-9.34 = \bm{7}$个周期,增加了$7 / 15.5 = \bm{45.1612903225806\%}$;
|
||||
\item 使用L2八路组相联cache时,CPI为 $1.5+7\% \times 28 + 1.5\% \times 400 = \bm{9.46}$周期,增加了$9.46-6.46 = \bm{3}$个周期,增加了$3 / 6.46 = \bm{46.4396284829721}\%$。
|
||||
\end{itemize}
|
||||
}
|
||||
|
||||
\questionandanswer[5.12.2]{
|
||||
[ 10 ]<5.4>可能有比两级更多的cache层次结构吗?已知上述处理器具有L2直接映射cache,设计人员希望添加一个L3 cache,访问时间为50个时钟周期,并且该cache将具有13\%的失效率。这会提供更好的性能吗?一般来说,添加L3 cache有哪些优缺点?
|
||||
}{
|
||||
可能有比两级更多的cache层次结构。这里的13\%失效率没有说局部还全局。那么,如果它是全局失效率,那么肯定要比L2的全局失效率低,但是这里它比L2的全局失效率高,所以只能是局部失效率。
|
||||
|
||||
那么加入L3 cache后的CPI为:$1.5+7\%\times 12 +3.5\% \times (50 + 13\% \times 200) = 5$周期 $<$ 9.34 周期,所以\boldkai{会}提供更好的性能。
|
||||
|
||||
添加L3 cache的优点是能用更小的全局失效率兜底,隐藏慢速内存影响的程度,减小总体的CPI;缺点是一旦全部缓存都失效,必须访问主存时,会产生很大的延迟。
|
||||
}
|
||||
\questionandanswer[5.12.3]{
|
||||
[ 20 ]<5.4>在较老的处理器中,例如Intel Pentium或Alpha 21264,L2 cache在主处理器和Ll cache的外部(位于不同芯片上)。虽然这种做法使得大型L2 cache成为可能,但是访问cache 的延迟也变得很高,并且因为L2 cache以较低的频率运行,所以带宽通常也很低。假设512KiB的片外L2 cache 的失效率为4\%,·如果每增加一个额外的512KiB cache能够降低0.7\%的失效率,并且cache 的总访问时间为50个时钟周期,那么cache容量必须多大才能与上面列出的L2直接映射cache的性能相匹配?
|
||||
}{
|
||||
这里$4\%$的失效率应该为局部失效率。设有$x+1$个512 KiB 的片外L2 cache。那么
|
||||
$$
|
||||
1.5+7\%\times 12+3.5\%\times 200 = 1.5 + 7\% \times (50+(4\% - 0.7\% x)\times 200)
|
||||
$$
|
||||
解得
|
||||
$x = - \frac{270}{7} = -38.5714285714286$,但$x$应$\geqslant 0$,所以\boldkai{不存在合适的cache容量与上面列出的L2直接映射cache的性能相匹配。}
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[5.16]{
|
||||
如5.7节所述,虚拟内存使用页表来跟踪虚拟地址到物理地址的映射。本题显示了在访问地址时必须如何更新页表。以下数据构成了在系统上看到的虚拟字节地址流。假设有4KiB页,一个4表项全相联的TLB,使用严格的LRU替换策略。如果必须从磁盘中取回页,请增加下一次能取的最大页码:
|
||||
\begin{center}
|
||||
\begin{tabular}{cccccccc}
|
||||
\toprule
|
||||
十进制 & 4669 & 2227 & 13916 & 34587 & 48870 & 12608 & 49225 \\
|
||||
\midrule
|
||||
十六进制 & 0x123d & 0x08b3 & 0x365c & 0x871b & 0xbee6 & 0x3140 & 0xc049 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
TLB
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
有效位 & 标签 & 物理页号 & 上次访问时间间隔 \\
|
||||
\midrule
|
||||
1 & 0xb & 12 & 4 \\
|
||||
1 & 0x7 & 4 & 1 \\
|
||||
1 & 0x3 & 6 & 3 \\
|
||||
0 & 0x4 & 9 & 7 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
页表
|
||||
\begin{center}
|
||||
\begin{tabular}{ccc}
|
||||
\toprule
|
||||
索引 & 有效位 & 物理页号/在磁盘中 \\
|
||||
\midrule
|
||||
0 & 1 & 5 \\
|
||||
1 & 0 & 在磁盘中 \\
|
||||
2 & 0 & 在磁盘中 \\
|
||||
3 & 1 & 6 \\
|
||||
4 & 1 & 9 \\
|
||||
5 & 1 & 11 \\
|
||||
6 & 0 & 在磁盘中 \\
|
||||
7 & 1 & 4 \\
|
||||
8 & 0 & 在磁盘中 \\
|
||||
9 & 0 & 在磁盘中 \\
|
||||
a & 1 & 3 \\
|
||||
b & 1 & 12 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[5.16.1]{
|
||||
[ 10 ]<5.7>对于上述每一次访问,列出:
|
||||
\begin{itemize}
|
||||
\item 本次访问在 TLB会命中还是失效。
|
||||
\item 本次访问在页表中会命中还是失效。
|
||||
\item 本次访问是否会造成缺页错误。
|
||||
\item TLB的更新状态。
|
||||
\end{itemize}
|
||||
}{
|
||||
4KiB即$2^{12}$Bytes,所以对于一个十六进制地址,右侧三位十六进制表示页内偏移,左侧一位表示标签。
|
||||
\includexopp[1.5]{5.16.1.1}
|
||||
|
||||
\begin{itemize}
|
||||
\item TLB是否失效,只需要查看是否在上一行的Tag中出现过;
|
||||
\item 页表是否失效,只需要看标签是否在页表中出现;
|
||||
\item 如果某个标签在磁盘中,或者不在页表中,都会造成缺页错误;
|
||||
\item 每次访问后,TLB中的上次访问时间间隔都需要加一(用蓝色表示),如果TLB未命中,则需要替换上次访问时间间隔最大的那一行(用红色表示),不管是否命中都需要把当前访问到的Tag所在的那行的上次访问时间间隔改为0(用红色表示);
|
||||
\item 题目中的“如果必须从磁盘中取回页,请增加下一次能取的最大页码”的意思是发生缺页错误时,分配的物理页号是当前最大的物理页号加一。
|
||||
\item 答案中的last access是相对顺序,每次替换序号最小的一行,而这里是指访问时间间隔,所以每次替换最大的一行。
|
||||
\end{itemize}
|
||||
}
|
||||
|
||||
\questionandanswer[5.16.2]{
|
||||
[ 15 ]<5.7>重复5.16.1,但这次使用16KiB页而不是4KiB页。拥有更大页大小的优势是什么?有什么缺点?
|
||||
}{
|
||||
16KiB = $2^{14}$Bytes,所以右侧14位二进制位表示页内偏移,左侧2位二进制位表示页号。
|
||||
\includexopp[1.5]{5.16.2.1}
|
||||
|
||||
由于标签只有2位,所以一共4行的TLB不会出现很多次置换,这里的上次访问间隔就省略了(直接按照原始的TLB,先置换第4行,再置换第1行,再置换第3行,再置换第2行),每次置换的行仍然用红色表示。
|
||||
|
||||
拥有更大页大小的优势是有更高的快表命中率,缺点是会降低内存使用率(产生了更多内零头)。
|
||||
}
|
||||
\questionandanswer[5.16.3]{
|
||||
[ 15 ]<5.7>重复5.16.1,但这次使用4KiB页和一个两路组相联TLB。
|
||||
}{
|
||||
一个页是4KiB,两个页组成一行。右侧12位表示页内偏移,中间1位表示索引,左侧3位表示标签。页号仍然是左侧4位。
|
||||
\includexopp[1.1]{5.16.3.1}
|
||||
}
|
||||
|
||||
\questionandanswer[5.16.4]{
|
||||
[ 15 ]<5.7>重复5.16.1,但这次使用4KiB页和一个直接映射 TLB。
|
||||
}{
|
||||
一个页是4KiB,右侧12位表示页内偏移,中间2位表示索引,左侧2位表示标签。页号仍然是左侧4位。
|
||||
\includexopp[1.5]{5.16.4.1}
|
||||
}
|
||||
|
||||
\questionandanswer[5.16.5]{
|
||||
[ 10 ]<5.4,5.7>讨论为什么CPU必须使用TLB才能实现高性能。如果没有TLB,如何处理虚拟内存访问?
|
||||
}{
|
||||
为了便于编写程序,写代码时不需要指定某段数据放在哪个物理地址中,出现了虚拟地址,为了将虚拟地址存放到实际的物理地址中,需要有个表存放这个映射关系,这就是页表,而页表存放在内存中,访问比较慢。但是每次访问虚拟地址时,都需要访问一次页表再访问实际的数据,也就是访问两次内存,所以就出现了TLB(快表)作为页表的缓存,在TLB命中时只需要访问一次内存,从而提升性能。
|
||||
|
||||
如果没有TLB,每次虚拟内存访问就需要访问两次内存,第一次访问页表,第二次再访问实际的数据。
|
||||
}
|
||||
\end{enumerate}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
288
计算机系统结构/平时作业/第四章作业.tex
Normal file
288
计算机系统结构/平时作业/第四章作业.tex
Normal file
@@ -0,0 +1,288 @@
|
||||
\documentclass[全部作业]{subfiles}
|
||||
\input{mysubpreamble}
|
||||
|
||||
\setcounter{chapter}{3}
|
||||
\begin{document}
|
||||
\chapter{处理器}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[4.7]{
|
||||
假设用来实现处理器数据通路的各功能模块延迟如下所示:
|
||||
|
||||
\noindent{\footnotesize\sffamily
|
||||
\begin{tabularx}{\linewidth}{ZZZZZZZZZZ}
|
||||
\toprule
|
||||
I-Mem / D-Mem & Register File & Mux & ALU & Adder & Single gate & Register Read & Register Setup & Sign extend & Control \\
|
||||
\midrule
|
||||
250 ps & 150 ps & 25 ps & 200 ps & 150 ps & 5 ps & 30 ps & 20 ps & 50 ps & 50 ps \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
}
|
||||
|
||||
其中,寄存器读延迟指的是,时钟上升沿到寄存器输出端稳定输出新值所需的时间。该延迟仅针对PC寄存器。寄存器建立时间指的是,寄存器的输入数据稳定到时钟上升沿所需的时间。该数值针对PC寄存器和寄存器堆。
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[4.7.1]{
|
||||
[ 5 ]<4.4>R型指令的延迟是多少?比如,如果想让这类指令工作正确,时钟周期最少为多少?
|
||||
}{
|
||||
R型指令的步骤如下图所示。其中,在译码阶段,Control的延迟为50ps,Register File的延迟为150ps,Mux的延迟为25ps,由于这三个步骤可以同时执行,所以延迟取最大值,即150ps。在访存(MEM)阶段,由于R型指令不需要访问内存,所以只需要通过一个多路选择器(MUX),所以延迟为25ps。
|
||||
\includexopp[1.2]{4.7.1.1}
|
||||
因此,延迟为$325+150+200+25+20 = 720$ ps,即如果想让这类指令工作正确,时钟周期最少为$720$ ps。
|
||||
}
|
||||
\questionandanswer[4.7.2]{
|
||||
[ 10 ]<4.4>ld指令的延迟是多少?仔细检查你的答案,许多学生会在关键路径上添加额外的寄存器。
|
||||
}{
|
||||
ld指令的步骤如下图所示,可以观察到在译码步骤中,虽然延迟的组成部分和上一题不一样,但由于Register File的延迟较长,因此总的延迟还是由Register File决定,即150ps。IF、ID、EX步骤的延迟与上一题没有改变,但后面两个步骤有所改变。
|
||||
\includexopp[1.2]{4.7.2.1}
|
||||
因此,延迟为$325+150+200+275+20 = 970$ ps。
|
||||
}
|
||||
\questionandanswer[4.7.3]{
|
||||
[ 10 ]<4.4>sd指令的延迟是多少?仔细检查你的答案,许多学生会在关键路径上添加额外的寄存器。
|
||||
}{
|
||||
前三个步骤仍然与之前一样,在MEM中,只需要访问D-Mem,即250 ps,并且没有WB步骤,所以延迟为$325+150+200+250 = 925$ ps。
|
||||
}
|
||||
\questionandanswer[4.7.4]{
|
||||
[ 5 ]<4.4> beq指令的延迟是多少?
|
||||
}{
|
||||
前三个步骤仍然与之前一样,只需要在MEM中加入一个Single gate的延迟,并且没有WB步骤。
|
||||
\includexopp[3]{4.7.4.1}
|
||||
因此,延迟为$325+150+200+5=680$ ps。
|
||||
}
|
||||
\questionandanswer[4.7.5]{
|
||||
[ 5 ]<4.4>I型指令的延迟是多少?
|
||||
}{
|
||||
与R型指令类似,I型指令只是在ID阶段需要在MUX前加入Sign extend的延迟,但仍然没有Register File的延迟大,所以ID步骤仍然需要150 ps的延迟,所以总延迟仍然为 720 ps。
|
||||
}
|
||||
\questionandanswer[4.7.6]{
|
||||
[ 5 ]<4.4>该CPU的最小时钟周期是多少?
|
||||
}{
|
||||
由于延迟最长的指令为ld指令,所以该CPU的最小时钟周期为ld指令的延迟,即970 ps。
|
||||
}
|
||||
\end{enumerate}
|
||||
|
||||
\questionandanswer[4.8]{
|
||||
[ 10 ]<4.4>假设你能设计一款处理器并让每条指令执行不同的周期数。给定指令比例如下表所示,相比图4-21中的处理器,这款新处理器的加速比是多少?
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
R-type/I-type (non-ld) & ld & sd & beq \\
|
||||
\midrule
|
||||
$52\%$ & $25\%$ & $11\%$ & $12\%$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{
|
||||
R型指令和I型指令没有MEM阶段,ld指令5个阶段都有,sd指令没有WB阶段,beq指令没有MEM和WB阶段。可以认为一个阶段的执行需要一个时钟周期,如果所有指令执行相同的周期数,那么都需要5个时钟周期,而如果每条指令可以执行不同的周期数,那么各类指令的周期数之比为R/I:4/5;ld:1;sd:4/5;beq:3/5。将各类指令的周期比按照指令比例加权平均即可得到周期比:
|
||||
$$
|
||||
\frac{4}{5}\times 0.52+1\times 0.25+\frac{4}{5}\times 0.11+\frac{3}{5}\times 0.12 = 0.826
|
||||
$$
|
||||
取倒数即可得到加速比:$\displaystyle \frac{1}{0.826} = \frac{500}{413} \approx 1.21065375302663$
|
||||
}
|
||||
\questionandanswer[4.16]{
|
||||
在本题中将讨论流水线如何影响处理器的时钟周期。假设数据通路的各个流水级的延迟如下:
|
||||
\begin{center}
|
||||
\begin{tabular}{ccccc}
|
||||
\toprule
|
||||
IF & ID & EX & MEM & WB \\
|
||||
\midrule
|
||||
250ps & 350ps & 150ps & 300ps & 200ps \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
同时,假设处理器执行的指令分布如下:
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
ALU/Logic & Jump/Branch & Load & Store \\
|
||||
\midrule
|
||||
$45\%$ & $20\%$ & $20\%$ & $15\%$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}{}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[4.16.1]{
|
||||
[ 5 ]<4.5>在流水化和非流水的处理器中,时钟周期分别是多少?
|
||||
}{
|
||||
流水化的处理器:按照最长的阶段,即350ps;\\
|
||||
非流水化的处理器:所有阶段延迟之和,即$250+350+150+300+200=1250$ ps。
|
||||
}
|
||||
\questionandanswer[4.16.2]{
|
||||
[ 10 ]<4.5 >在流水化和非流水的处理器中,对于ld指令的延迟分别是多少?
|
||||
}{
|
||||
ld指令5个阶段都有,在流水化的处理器中,为$350\times 5 = 1750$ ps;\\
|
||||
非流水化的处理器中,为所有阶段延迟之和,即$250+350+150+300+200=1250$ ps。
|
||||
}
|
||||
\questionandanswer[4.16.3]{
|
||||
[ 10 ]<4.5>如果我们将数据通路中的一个流水级拆成两个新流水级,每一个新流水级的延迟是原来的一半,那么我们将拆分哪一级?新处理器的时钟周期是多少?
|
||||
}{
|
||||
应该拆分最长的一级,即ID阶段,拆分之后最长的阶段延迟为300 ps,所以新处理器的时钟周期是300 ps。
|
||||
}
|
||||
\questionandanswer[4.16.4]{
|
||||
[ 10 ]<4.5>假设没有停顿或冒险,数据存储的利用率如何?
|
||||
}{
|
||||
既然题目中出现了“停顿”“冒险”这种只有在流水化处理器中才会出现的情况,那么说明这里只需要考虑流水化的情况。数据存储对应的是MEM阶段,MEM阶段只需要300ps,但是为了满足流水线,延迟到了350ps,所以MEM阶段的利用率为$\frac{300}{350}$;而在题目给出的指令分布中,只有Load和Store指令会用到数据存储,即$20\%+15\%$,所以数据存储的利用率为
|
||||
$$
|
||||
\frac{300}{350} \times (0.2+0.15) = 0.3
|
||||
$$
|
||||
}
|
||||
\questionandanswer[4.16.5]{
|
||||
[ 10 ]<4.5>假设没有停顿或冒险,寄存器堆的写口利用率如何?
|
||||
}{
|
||||
寄存器堆的写口对应的是WB阶段,WB阶段的利用率为$\frac{200}{350}$;在题目给出的指令分布中,使用到寄存器堆的写口的指令为ALU/Logic和Load,即$45\%+20\%$,所以寄存器堆写口的利用率为
|
||||
$$
|
||||
\frac{200}{350} \times (0.45+0.2) = \frac{13}{35} \approx 0.371428571428571
|
||||
$$
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[4.18]{
|
||||
[ 5 ]<4.5>假设初始化寄存器x11为11,x12为22,如果在4.5节中的流水线结构上执行下述代码,寄存器x13和x14中最终为何值?注:硬件不处理数据冒险,编程者需要在必要处插入NOP指令来解决数据冒险。
|
||||
}{}
|
||||
\begin{minted}{asm}
|
||||
addi x11, x12, 5
|
||||
add x13, x11, x12
|
||||
addi x14, x11, 15
|
||||
\end{minted}
|
||||
{\kaishu
|
||||
显然在硬件不处理数据冒险情况下,执行上述代码会出现数据冒险,以下是示意图,在ID指令旁边标注了实际取出的操作数。
|
||||
\includexopp[1.5]{4.18.1}
|
||||
所以寄存器x13最终为33,寄存器x14最终为26。
|
||||
}
|
||||
\questionandanswer[4.22]{
|
||||
[ 5 ]<4.5>对于如下的RISC-V的汇编片段:
|
||||
}{}
|
||||
\begin{minted}{asm}
|
||||
sd x29, 12(x16)
|
||||
ld x29, 8(x16)
|
||||
sub x17, x15, x14
|
||||
beqz x17, label
|
||||
add x15, x11, x14
|
||||
sub x15, x30, x14
|
||||
\end{minted}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[4.22.1]{
|
||||
[ 5 ]<4.5>请画出流水线图,说明以上代码会在何处停顿。
|
||||
}{
|
||||
在加入停顿之前的流水线图是这样的,显然由于前面的指令的MEM阶段和后面的指令的IF阶段都需要访问存储器,会发生结构冒险。
|
||||
\includexopp[1.1]{4.22.1.1}
|
||||
加入停顿后流水线图变成了这样:
|
||||
\includexopp{4.22.1.2}
|
||||
}
|
||||
\questionandanswer[4.22.2]{
|
||||
[ 5 ]<4.5>是否可通过重排代码来减少因结构冒险而导致停顿的次数?
|
||||
}{
|
||||
可以,由于只考虑结构冒险(即两条指令在同一个阶段访问寄存器堆或存储器的冒险),可以把第一行的sd指令放到第二三行的ld和sub指令后面,这样就可以减少一个停顿。
|
||||
}
|
||||
\questionandanswer[4.22.3]{
|
||||
[ 5 ]<4.5>该结构冒险必须用硬件来解决吗?我们可以通过在代码中插入NOP指令来消除数据冒险,对于结构冒险是否可以相同处理?如果可以,请解释原因。否则,也请解释原因。
|
||||
}{
|
||||
不一定要用硬件来解决,可以通过插入NOP指令来消除结构冒险,因为NOP指令相当于一个停顿,只需要把上述的停顿换成NOP指令即可。
|
||||
}
|
||||
\questionandanswer[4.22.4]{
|
||||
[ 5 ]<4.5>在典型程序中,大约需要为该结构冒险产生多少个周期的停顿?(使用习题4.8中的指令分布)。
|
||||
}{
|
||||
仔细观察可以发现,停顿的产生是由于sd和ld有MEM阶段,会和后续指令的IF阶段冲突,ld和R型指令有WB阶段,会和后续指令的ID阶段冲突,那么可以认为一个ld导致2个停顿,一个sd导致1个停顿,一个R型导致1个停顿。所以大概产生的停顿周期数为:
|
||||
$$
|
||||
1\times 0.52+2\times 0.25+1\times 0.11 = 1.13
|
||||
$$
|
||||
即产生$1.13\times \text{原始时钟周期数}$ 个周期的停顿。
|
||||
}
|
||||
\end{enumerate}
|
||||
\questionandanswer[4.27]{
|
||||
讨论下述指令序列,假设在一个五级流水的数据通路中执行:
|
||||
}{}
|
||||
\begin{minted}{asm}
|
||||
add x15, x12, x11
|
||||
ld x13, 4(x15)
|
||||
ld x12, 0(x2)
|
||||
or x13, x15, x13
|
||||
sd x13, 0(x15)
|
||||
\end{minted}
|
||||
\begin{enumerate}
|
||||
\questionandanswer[4.27.1]{
|
||||
[ 5 ]<4.7>如果没有前递逻辑或者冒险检测支持,请插入NOP指令保证程序正确执行。
|
||||
}{}
|
||||
{\kaishu
|
||||
\begin{minted}{asm}
|
||||
add x15, x12, x11 // 在第5个阶段写入x15
|
||||
nop
|
||||
nop
|
||||
ld x13, 4(x15) // 在第2个阶段读取x15,在第5个阶段写入x13
|
||||
ld x12, 0(x2) // 在第2个阶段读取x2,在第5个阶段写入x12
|
||||
nop
|
||||
or x13, x15, x13 // 在第2个阶段读取x13和x15,在第5个阶段写入x13
|
||||
nop
|
||||
nop
|
||||
sd x13, 0(x15) // 在第2个阶段读取x13和x15
|
||||
\end{minted}
|
||||
}
|
||||
\questionandanswer[4.27.2]{
|
||||
[ 10 ]<4.7>对代码进行重排,插入最少的NOP指令。假设寄存器x17可用来做临时寄存器。
|
||||
}{}
|
||||
{\kaishu
|
||||
\begin{minted}{asm}
|
||||
add x15, x12, x11
|
||||
nop
|
||||
nop
|
||||
ld x13, 4(x15)
|
||||
ld x12, 0(x2)
|
||||
nop
|
||||
or x17, x15, x13
|
||||
sd x17, 0(x15)
|
||||
\end{minted}
|
||||
}
|
||||
\questionandanswer[4.27.3]{
|
||||
[ 10 ]<4.7>如果处理器中支持前递,但未实现冒险检测单元,上述代码段的执行将会发生什么?
|
||||
}{
|
||||
不会发生数据冒险,因为在不存在加载后马上使用的情况,第二行加载到x13后在第4行才使用x13,此时已经可以使用前递确保正确执行。
|
||||
}
|
||||
\questionandanswer[4.27.4]{
|
||||
[ 20 ]<4.7>以图4-58中的冒险检测和前递单元为例,如果执行上述代码,在前7个时钟周期中,每周期哪些信号会被它们置为有效?
|
||||
}{
|
||||
\includexopp[1.2]{4.27.4.1}
|
||||
前递信号如图所示。所以在前7个时钟周期中,有效信号表示如下表:
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
时钟周期 & 冒险检测 & ForwardA & ForwardB \\
|
||||
\midrule
|
||||
1 & x & x & x \\
|
||||
2 & x & x & x \\
|
||||
3 & 有效 & 有效 & x \\
|
||||
4 & x & x & x \\
|
||||
5 & 有效 & x & 有效 \\
|
||||
6 & 有效 & 有效 & x \\
|
||||
7 & x & x & x \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}
|
||||
\questionandanswer[4.27.5]{
|
||||
[ 10 ]<4.7>如果没有前递单元,以图4-58中的冒险检测逻辑为例,需要为其增加哪些输入和输出信号?
|
||||
}{
|
||||
第3个周期的前递,需要IF/ID.Rs1 和 ID/EX.Rd的输入信号,ForwardA的输出信号;\\
|
||||
第5个周期的前递,需要IF/ID.Rs2 和 EX/MEM.Rd的输入信号,ForwardB的输出信号;\\
|
||||
第6个周期的前递,需要IF/ID.Rs1 和 ID/EX.Rd的输入信号,ForwardA的输出信号。\\
|
||||
综上所述,需要增加IF/ID.Rs1, IF/ID.Rs2, ID/EX.Rd, EX/MEM.Rd 的输入信号,ForwardA, ForwardB的输出信号。
|
||||
}
|
||||
\questionandanswer[4.27.6]{
|
||||
[ 20 ]<4.7>如果使用习题4.26.5中的冒险检测单元,执行上述代码,在前5个时钟周期中,每个周期哪些输出信号会有效?
|
||||
}{
|
||||
\includexopp[1.2]{4.27.4.1}
|
||||
前递信号如图所示。所以在前5个时钟周期中,有效信号表示如下表:
|
||||
\begin{center}
|
||||
\begin{tabular}{cccc}
|
||||
\toprule
|
||||
时钟周期 & 冒险检测 & ForwardA & ForwardB \\
|
||||
\midrule
|
||||
1 & x & x & x \\
|
||||
2 & x & x & x \\
|
||||
3 & 有效 & 有效 & x \\
|
||||
4 & x & x & x \\
|
||||
5 & 有效 & x & 有效 \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
}
|
||||
\end{enumerate}
|
||||
\end{enumerate}
|
||||
\end{document}
|
||||
Reference in New Issue
Block a user