计算机体系结构 - 实验报告
写在前面
啊我草大三上提前把所有学分修完了直接爆了,考完毛概还剩半天搞这个 b 实验报告,除了第一个代码都是 gpt 生成的,报告稍微用经典 \(\LaTeX\) 模板搞了搞看着不算太烂呃呃。
以下仅搬运文字版报告,需要 \(\LaTeX\) 代码请移步仓库:https://github.com/Luckyblock233/Computer-Architecture-Lab-Report。
实验背景
计算机体系结构是计算机专业学生的一门专业课程,本课程是计算机专业一门重要的专业课,着重讲述计算机系统的软、硬件界面。对于学生从事计算机系统的研制、使用和维护有重要意义。本课程概念多、内容涉及面广、系统性强。通过本课程的学习,学生应能从软件、硬件功能分配的角度去了解、分析和研究计算机系统,建立起对计算机系统的全面认识,树立全面地、发展地看问题的观点,从而加深对各种类型体系结构的了解,牢固地树立起整机系统的概念。
本课程的学习应注重理论与实践相结合,实验教学是教学环节中必不可少的重要内容。通过实验教学的学习,学生熟练掌握有关计算机体系结构的基本概念、基本原理和基本思想,掌握对计算机体系结构和组成进行分析和计算的方法。
实验部分包括如下三个实验。
- 对指令操作码进行霍夫曼编码;
- 使用LRU方法更新Cache;
- 单功能流水线调度机构模拟。
对指令操作码进行霍夫曼编码
实验目的
了解和掌握指令编码的基本要求和基本原理。
要求使用编程工具实现霍夫曼编码,对一组指令的操作码进行编码,并输出最终编码结果。
同时,计算编码长度,并与等长编码和扩展操作码进行比较,分析结果的正确性和有效性。
实验原理
指令编码是计算机体系结构设计中的重要组成部分。为了提高指令的执行效率和减少存储需求,不同的指令可以根据使用频率采用不同的编码方式。
霍夫曼编码是一种基于统计的无损数据压缩算法,通过赋予高频指令短码、低频指令长码来实现编码长度的优化。其基本原理如下:
- 构建频率表:统计每条指令的出现频率。
- 构建霍夫曼树:
- 将所有指令作为叶节点,根据频率排序。
- 每次取频率最低的两个节点合并为一个新节点,频率为两者之和,重复此过程直至形成一棵树。
- 生成编码:对树进行遍历,左子树赋值"0",右子树赋值"1",从根节点到叶节点的路径即为对应指令的编码。
通过霍夫曼编码,可以实现更短的平均编码长度。与扩展操作码和等长编码相比,其优点是编码长度可根据指令使用的实际概率分布进行动态调整。
代码实现
import networkx as nx
import matplotlib.pyplot as plt
from functools import reduce
import numpy as np
import math
import heapq
node_num = 0
class node(object):
def __init__(self, id: int, command: str, value: float):
self.id = id
self.command = command
self.value = value
self.fa = None
self.sons = []
def __lt__(self, other):
if self.value != other.value:
return self.value < other.value
return self.id < other.id
def add_father(self, fa):
self.fa = fa
self.fa.sons.append(self)
class haffmanCode(object):
def __init__(self, command: str,
id: int, value:float,
haffmanCode: str):
self.command = command
self.id = id
self.value = value
self.haffmanCode = haffmanCode
self.len = len(haffmanCode)
def newNode(command: str, value: float):
global node_num
node_num += 1
return node(node_num, command, value)
def merge(x: node, y: node):
fa = newNode('', x.value + y.value)
x.add_father(fa)
y.add_father(fa)
return fa
def buildHuffman(nd: list):
heapq.heapify(nd)
while len(nd) >= 2:
x = heapq.heappop(nd)
y = heapq.heappop(nd)
z = merge(x, y)
heapq.heappush(nd, z)
return heapq.heappop(nd)
def hierarchy_pos(G, root, width=10.,
vert_gap = 1.0, vert_loc = 0,
xcenter = 0.5,
pos = None, parent = None):
if pos is None:
pos = {root:(xcenter,vert_loc)}
else:
pos[root] = (xcenter, vert_loc)
children = list(G.neighbors(root))
if not isinstance(G, nx.DiGraph) and parent is not None:
children.remove(parent)
if len(children)!=0:
dx = width/len(children)
nextx = xcenter - width/2 - dx/2
for child in children:
nextx += dx
pos = hierarchy_pos(G,child, width = dx,
vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap,
xcenter=nextx,
pos=pos, parent = root)
return pos
def dfsGraph(G: nx.Graph, root: node, codes: list, command: str):
# print(root.id)
if len(root.sons):
G.add_edge(root.id, root.sons[0].id, color='red')
G.add_edge(root.id, root.sons[1].id, color='blue')
dfsGraph(G, root.sons[0], codes, command + '0')
dfsGraph(G, root.sons[1], codes, command + '1')
else:
codes.append(haffmanCode(root.command,
root.id,
root.value,
command))
def calcExtLen(nd:list):
ext_len = 0
len = [1, 2, 3, 5]
nd = sorted(nd, key=lambda x: x.value)
for i in range(0, n):
ext_len += nd[i].value * len[min(i, 3)]
return ext_len
def show(root: node, ext_len:float):
G = nx.Graph()
G.add_nodes_from(range(1, node_num + 1))
codes = []
dfsGraph(G, root, codes, '')
codes.sort(key = lambda x: x.len)
pos=hierarchy_pos(G, root.id)
edge_colors = [G[u][v]['color'] for u, v in G.edges()]
plt.subplot(131)
nx.draw(G, pos=pos, with_labels=True, edge_color=edge_colors)
plt.subplot(132)
col=['nodeId', 'haffmanCode', 'length']
row=[]
vals=[]
for x in codes:
row.append(x.command)
vals.append([str(x.id), x.haffmanCode, str(x.len)])
tab = plt.table(cellText=vals,
colLabels=col,
rowLabels=row,
loc='center',
cellLoc='center',
rowLoc='center')
tab.scale(1, 2)
plt.axis('off')
plt.subplot(133)
col=['length']
row=['haffman', 'equal length', '1-2-3-5']
vals=[[sum(map(lambda x: x.len*x.value, codes))],
[math.log2(n)],
[ext_len]]
tab = plt.table(cellText=vals,
colLabels=col,
rowLabels=row,
loc='center',
cellLoc='center',
rowLoc='center')
tab.scale(1, 3)
plt.axis('off')
plt.tight_layout()
plt.show()
n = int(input())
nd = []
for i in range(0, n):
command = input()
value = float(input())
nd.append(newNode(command, value))
ext_len = calcExtLen(nd)
root = buildHuffman(nd)
print(root.id)
show(root, ext_len)
运行结果
输入的频率表:
| 指令名称 | 指令长度 |
|---|---|
| P1 | 0.45 |
| P2 | 0.30 |
| P3 | 0.15 |
| P4 | 0.05 |
| P5 | 0.03 |
| P6 | 0.01 |
| P7 | 0.01 |
霍夫曼编码运行结果截图
运行结果分析
由上述运行结果可知,霍夫曼编码根据概率高低分配编码长度,成功实现了最优编码,平均长度为1.97。实验结果证明:哈夫曼编码可以显著减少平均编码长度,提高编码效率。
使用LRU方法更新Cache
实验目的
了解和掌握寄存器分配和内存分配的有关技术。
结合数据结构的相关知识,使用LRU的策略,对一组访问序列进行内部的Cache更新。
实验原理
LRU置换算法是选择最近最久未使用的页面予以置换。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来经历的时间T,当须淘汰一个页面时,选择现有页面中T值最大的,即最近最久没有访问的页面。这是一个比较合理的置换算法。
其基本原理如下:
- 记录页面使用时间:每次页面被访问时更新其时间戳,表示最近被使用。
- 选择替换页面:当缓存满时,选择最久未使用的页面进行替换。
- 更新缓存状态:根据访问序列动态调整缓存内容,保证访问效率。
代码实现
import matplotlib.pyplot as plt
def lru_cache_visualized(page_sequence, cache_size):
cache = [] # 缓存初始化
misses = 0 # 缺页次数
steps = [] # 用于记录每一步的缓存状态
print("任务到达 | 缓存状态 | 操作")
print("-" * 30)
for page in page_sequence:
if page in cache:
cache.remove(page)
operation = "Hit"
else: # 缺页
misses += 1
operation = "Miss"
if len(cache) >= cache_size:
cache.pop(0)
cache.append(page) # 更新缓存
steps.append((page, list(cache), operation))
print(f"{page:^10} | {str(cache):^10} | {operation}")
hit_rate = (1 - misses / len(page_sequence)) * 100
print("-" * 30)
print(f"总缺页次数: {misses}, 命中率: {hit_rate:.2f}%")
# 调用可视化函数
visualize_steps(steps, cache_size)
def visualize_steps(steps, cache_size):
fig, ax = plt.subplots(figsize=(10, len(steps) * 0.6))
y_labels = []
x_pages = []
for i, (page, cache, operation) in enumerate(steps):
y_labels.append(f"Step {i + 1}\n({operation})")
x_pages.append(page)
for j, cached_page in enumerate(cache):
ax.text(j, i, str(cached_page),
va='center', ha='center',
fontsize=10,
bbox=dict(facecolor='lightblue',
edgecolor='black',
boxstyle='round'))
ax.set_xlim(-0.5, cache_size - 0.5)
ax.set_ylim(-0.5, len(steps) - 0.5)
ax.set_xticks(range(cache_size))
ax.set_xticklabels([f"Cache {i + 1}" for i in range(cache_size)])
ax.set_yticks(range(len(steps)))
ax.set_yticklabels(y_labels)
ax.set_title("LRU Cache Replacement Visualization", fontsize=8)
ax.set_xlabel("Cache Slots")
ax.set_ylabel("Steps")
ax.grid(False)
plt.tight_layout()
plt.show()
page_sequence = [7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2]
cache_size = 3
lru_cache_visualized(page_sequence, cache_size)
运行结果
设置缓存大小为 3,输入的页面访问序列为:7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2。
LRU运行结果截图
总缺页次数: 9, 命中率: 30.77%
单功能流水线调度机构模拟
实验目的
结合数据结构的相关知识,编写流水线调度模拟程序。
通过模拟单功能流水线调度过程,掌握流水线技术,学会计算流水线的吞吐率、加速比、效率。
实验原理
流水线通常可以通过以下三种方式表示:
- 连接图:以图的形式表示各个流水段之间的连接关系;
- 时空图:以二维表格形式表示流水线中任务的执行顺序和时间分布;
- 预约表:用于描述任务在流水线中的具体分配情况和调度顺序。
在单功能流水线模拟中,主要使用连接图和时空图来分析调度过程和性能。
流水线的主要特点是,在流水线的每一个功能部件的后面都要有一个缓冲器,称为锁存器、闸门寄存器等,它的作用是保存本流水段的执行结果。各流水段的时间应尽量相等,否则回引起阻塞、断流等。只有连续提供同类任务才能充分发挥流水线的效率。在流水线的每一个流水线段中都要设置一个流水锁存器。流水线需要有“装入时间”和“排空时间”。只有流水线完全充满时,整个流水线的效率才能得到充分发挥。
以下为流水线的调度和运行的基本流程:
- 任务分解:将任务分解为多个阶段,每个阶段由流水线中的一个功能部件负责完成;
- 任务调度:按照一定的调度策略,将任务分配到流水线中;
- 并行执行:在流水线中同时处理多个任务,每个任务在不同阶段运行;
- 结果收集:当任务经过流水线所有阶段后,输出最终结果。
代码实现
import matplotlib.pyplot as plt
import numpy as np
def simulate_pipeline_stages(num_stages, num_instructions):
schedule = np.zeros((num_stages,
num_stages + num_instructions - 1),
dtype=int)
for i in range(num_instructions):
for j in range(num_stages):
schedule[j][i + j] = i + 1
total_cycles = num_stages + num_instructions - 1
throughput = num_instructions / total_cycles
ideal_cycles = num_stages * num_instructions
speedup = ideal_cycles / total_cycles
efficiency = (num_stages / total_cycles) * 100
return schedule, throughput, speedup, efficiency
def visualize_pipeline_schedule(schedule):
num_stages, num_cycles = schedule.shape
fig, ax = plt.subplots(figsize=(10, num_stages * 0.8))
for stage in range(num_stages):
for cycle in range(num_cycles):
if schedule[stage][cycle] != 0:
ax.text(cycle,
num_stages - stage - 1,
str(schedule[stage][cycle]),
ha='center', va='center', fontsize=10,
bbox=dict(boxstyle="round",
facecolor="lightblue",
edgecolor="black"))
ax.set_xticks(range(num_cycles))
ax.set_xticklabels([f"T{cycle + 1}"
for cycle in range(num_cycles)])
ax.set_yticks(range(num_stages))
ax.set_yticklabels([f"Stage {stage}"
for stage in range(num_stages, 0, -1)])
ax.set_title("Pipeline Schedule ", fontsize=14)
ax.set_xlabel("Time")
ax.set_ylabel("Stage")
ax.grid(True, linestyle="--", alpha=0.6)
plt.tight_layout()
plt.show()
def main():
schedule, throughput, speedup, efficiency =
simulate_pipeline_stages(num_stages, num_instructions)
print(f"总时间周期: {schedule.shape[1]}")
print(f"吞吐率: {throughput:.2f}")
print(f"加速比: {speedup:.2f}")
print(f"效率: {efficiency:.2f}%")
visualize_pipeline_schedule(schedule)
num_stages = 4 # 功能部件数目
num_instructions = 6 # 指令数目
if __name__ == "__main__":
main()
运行结果
设置功能部件数目为 4,流水处理的指令数目为:6。
流水线运行结果截图
- 总时间周期: 9;
- 吞吐率: 0.67;
- 加速比: 2.67;
- 效率: 44.44%。
总结
通过本次实验,我对计算机体系结构中的核心概念和基本原理有了更深入的理解。特别是通过对霍夫曼编码、LRU缓存替换算法以及流水线调度技术的实际编程和模拟,我深刻认识到计算机系统设计中的科学性与艺术性。
霍夫曼编码的实验让我理解了编码优化的数学原理,它在指令编码中的实际应用展现了如何通过概率分布来减少存储需求和提高执行效率;LRU缓存替换策略的实现让我切身体会到算法在有限资源条件下的高效利用;而单功能流水线的模拟则让我对计算机指令并行执行的高效性有了直观感受,同时也认识到流水线设计中时序控制的复杂性。
受时间和个人水平的局限,本次实验存在若干不足的地方,还有很大的改进空间。如实验中部分代码的注释和结构优化不足,尤其在霍夫曼编码和流水线调度中,部分函数的功能划分不够清晰,可能对后续代码复用和维护带来困难;在LRU缓存更新实验中,实现较为简单粗暴,可以使用更加精细的实现,结合更多数据结构进行维护以增加效率,如可以通过结合哈希表记录页面索引,来加速缓存查找与替换过程。
通过这些实验,我更加明确了理论与实践相结合的重要性,也意识到作为计算机专业学生,掌握底层系统知识对于解决实际问题和进一步研究具有重要意义。我不仅深化了对计算机体系结构知识的理解,更加感受到了计算机科学的魅力。在实验过程中,从理论到实践的探索让我体会到编程与逻辑推理的乐趣,也让我明白了持续学习和改进的重要性。未来的学习中,我将更加注重理论与实践的结合,不断提高解决实际问题的能力,为后续专业学习和研究打下更加坚实的基础。

浙公网安备 33010602011771号