【问题标题】:How do I calculate clock cycles on mips assembly programming?如何计算 mips 汇编编程的时钟周期?
【发布时间】:2019-03-19 04:44:49
【问题描述】:

我到处搜索,我收集了管道或其他东西。我检查了其他程序,似乎有一个单周期和多周期:Clock cycles of Single-cycle and multi-cycle MIPS

如何区分哪个周期。

例如,这是多少个时钟周期:

li $a0, 0 # $a0 = 0
 li $t0, 10 # Initialize loop counter to 10
Loop:
 add $a0, $a0, $t0
 addi $t0, $t0, -1 # Decrement loop counter
 bgt $t0, $zero, Loop # If ($t0 > 0) Branch to loop

我的教授给了我这个作业:“假设在内存中加载和存储值需要 100 个周期加上指令的成本 自己。”

根据我的阅读,加载是 5 个周期,那么为什么我的教授说它是 100 个周期。我可以做任何我想要的数字吗?我很困惑。

【问题讨论】:

  • 实际内存相对于处理器来说非常慢。 100 个周期似乎很快。但只是说就是这样。管道给人一种每个周期一个时钟的错觉,因此为每个添加一个时钟,然后决定您的分支惩罚是什么。获取当然算作从内存中加载?
  • 计算周期是这类处理器的幻想,有一些 mcus 可以整天计算时钟并且非常准确,可能是 8051,肯定是 PIC,6502 等等。基于MCU,对不起。任何带有管道的东西,不,不是真的,任何带有 DRAM 和缓存的东西,不,对不起,不要费心去尝试。所以现实是这是一个没有真正答案的虚假问题,但同时你的教授已经教了一些有固定答案的东西,所以你必须玩学校游戏并按照教授想要的方式回答,然后在之后转向现实你通过了课程。
  • 既然我们不在您的教授课上,也没有看过讲座,那么我们应该如何在这里提供帮助?有教科书管道的东西,例如 $t0 的 addi 结果必须在 bgt 可以测试之前完成,所以管道是否必须停止一两个周期?在第一次传入时,$t0 的 li 是否会导致 add $a0 必须停止一两个周期?这是基于教科书的,因为真正的 mips 管道很可能与教科书不相似,其他真正的处理器管道、arm、x86 等也不是...不在您的课堂上不知道您使用的是什么教科书。
  • @old_timer 我们的教科书是:MIPS 汇编语言编程,Prentice Hall, Inc. 的 Robert L. Britton 着,2004 年。但我不确定你提到的管道是什么意思。 .
  • 如果你不是流水线的,那么多少时钟取决于设计,每条指令可以根据实现需要任意多的时钟。理想情况下有一些最低限度的确定,但如果你愿意的话,即使有设计捷径。

标签: assembly mips mars-simulator


【解决方案1】:

这个问题没有意义。

在大多数教育 MIPS 实现中使用的标准多周期 RISC 流水线从根本上基于可以在单个周期内同时访问程序和数据存储器的要求。要“假设在内存中加载和存储值需要 100 个周期”,则需要完全不同的架构。

【讨论】:

    【解决方案2】:

    我们要区分两种情况:

    案例 1:MIPS 模拟器

    根据我的阅读,加载是 5 个周期,那为什么我的教授说它是 100 个周期。

    您使用的不是真实的 CPU,而是模拟的 CPU。因此,争论您的程序在模拟 CPU 上“真正”需要多少个周期是没有意义的。

    也许模拟器模拟每个内存访问5个周期。但是,另一个模拟器可能会模拟 10 个周期或仅模拟 1 个周期来进行内存访问。

    这意味着在谈论模拟周期数时,您必须说使用哪个模拟器。你的教授说应该假设一个模拟器模拟100个周期。

    案例 2:真正的 MIPS CPU

    我可以随心所欲吗?

    在这种情况下,您必须查看 CPU 手册以获取 CPU 所需的实际周期数。

    然而,真正的 MIPS 类型 CPU 的指令集并非 100% 与“MIPS”仿真器相同。在您的程序中,指令bgt 的工作方式会有所不同。

    这意味着我们也无法争论您的程序在真正的 MIPS CPU 上需要多少个周期,因为我们必须修改它才能在真正的 MIPS CPU 上运行它 - 这可能会改变所需的周期数。

    如果您想知道使用真实 CPU 时数字 100 是否合理:

    正如我们从“Spectre”和“Meltdown”安全漏洞中了解到的,在真实 CPU 上读取内存所需的时间大量取决于 CPU 缓存的状态。如果我们假设a0 指向一个内存映射外设(从不缓存),100 个周期可能是合理的。

    【讨论】:

    • 我正在使用 MARS MIPS 模拟器,如果这有助于确定多少个周期。
    • @Jordles 重点是:实际使用哪个模拟器并不重要。教授希望您根据每次内存访问需要 100 个周期的模拟器来计算周期。
    【解决方案3】:

    “假设在内存中加载和存储值需要 100 个周期加上指令本身的成本。”

    这没什么意义。除非您应该假设指令获取也很慢,否则这就像有一个指令缓存但没有数据缓存。

    (或者运行程序时将数据内存映射到不可缓存的内存区域。)

    正如@Martin 指出的那样,您可以组成任何您想要的数字并进行相应的模拟,即使没有合理的工程原因需要以这种方式构建 CPU。

    如果您尝试在主机 CPU 上模拟 MARS 等模拟器本身的性能,那么加载/存储的成本也不是特别合理。每条指令的解释器成本因分支预测在主机 CPU 上的工作情况而有很大差异,而模拟客户内存只是解释器模拟器工作的一小部分。


    现代 x86 上的负载对于 L1d 缓存命中(从准备好地址到准备好数据)通常具有 5 个周期延迟,但它们也具有每时钟 2 个吞吐量。因此,即使没有任何缓存未命中,也可以在英特尔 Sandybridge 系列 CPU 或 AMD K8 / Bulldozer / Zen 的两个流水线负载执行单元中同时运行多达 10 个负载。 (使用负载缓冲区来跟踪缓存未命中负载,可以在整个乱序后端同时运行更多负载。)

    除非您谈论的是周围代码的特定上下文,否则您不能说负载在这样的 CPU 上“花费”了 5 个周期,例如遍历链表,因为下一次加载的地址取决于当前加载的结果。


    在遍历数组时,您通常会使用 add 指令(或 MIPS addui)生成下一个指针,该指令有 1 个周期延迟。即使负载在简单的有序流水线上有 5 个周期的延迟,展开 + 软件流水线也可以让您在每个时钟周期维持 1 个负载。

    在流水线 CPU 上,性能不是一维的,您不能只将成本数字放在指令上并将它们相加。即使对于带有乘法指令的经典有序 MIPS 上的 ALU 指令,您也可以看到这一点:如果您不立即使用 mflo / mhi,则乘法延迟会被您填补该空白的任何指令隐藏。


    正如@duskwuff 指出的那样,像第一代 MIPS 一样的classic RISC 5-stage pipeline (fetch/decode/exec/mem/write-back) 假设缓存命中具有 1 个时钟的内存吞吐量和访问 L1d 本身的延迟。但是 MEM 阶段为加载(包括 EX 阶段中的地址生成)留出了 2 个周期延迟的空间。

    而且我猜他们也不需要存储缓冲区。更复杂的 CPU 使用存储缓冲区将执行与可能在 L1d 缓存中丢失的存储分离,即使缓存未命中也隐藏存储延迟。这很好,但不是必需的

    早期的 CPU 通常使用简单的直接映射虚拟寻址缓存,从而在不降低最大时钟速度的情况下实现如此低的缓存延迟。但是在缓存未命中时,管道会停止。 https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Cache_miss_handling.

    更复杂的有序 CPU 可以记分板加载,而不是在它们中的任何一个未在缓存中丢失时停止,并且只有在后面的指令尝试实际读取由尚未完成的加载最后写入的寄存器时才会停止。这允许 hit-under-miss 和多个未完成的缓存未命中来创建内存并行性,从而允许同时进行多个 100 周期的内存访问。


    但幸运的是,您的循环首先不包括任何内存访问。它是纯 ALU + 分支。

    在带有分支延迟槽的真实 MIPS 上,你可以这样写:

     li   $t0, 10                        # loop counter
     li   $a0, 10                        # total = counter    # peel the first iteration
    
    Loop:                                # do{
     addi $t0, $t0, -1
     bgtz $t0, Loop                      # } while($t0 > 0);
     add $a0, $a0, $t0                   # branch-delay: always executed for taken or not
    

    这仍然只是 10+9+8+...+0 = (10*11)/2 最好用乘法而不是循环来完成。但这不是重点,我们正在分析循环。我们执行相同数量的添加,但我们在末尾添加 += 0 而不是在开头添加 0 + 10

    注意我使用了the real MIPS bgtz instruction,而不是$zerobgt 伪指令。希望汇编程序会为$zero 的特殊情况选择它,但它可能只是遵循使用slt $at, $zero, $t0 / bne $at, $zero, target 的正常模式。

    经典 MIPS 不做分支预测 + 推测执行(它有一个分支延迟槽来隐藏控制依赖的气泡)。但要让它工作,it needs the branch input ready in the ID stage,所以读取前一个add 的结果(在 EX 结束时产生结果)将导致 1 个周期停顿。 (或者更糟,取决于是否支持转发到 ID 阶段。https://courses.engr.illinois.edu/cs232/sp2009/exams/final/2002-spring-final-sol.pdf 问题 2 部分(a)有一个这样的例子,但我认为如果你需要等待 add WB 之前完成,他们会低估停顿周期bne/bgtz ID 可以启动。)

    因此,无论如何,这应该在标量有序 MIPS I 上每 4 个周期运行 1 次迭代,可以从 EX 转发到 ID。 3 条指令 + 每个 bgtz 之前的 1 个停顿周期。


    我们可以通过将add $a0, $a0, $t0 放在循环计数器和分支之间来优化它,用有用的工作填充这个停顿循环。

     li    $a0, 10                        # total = counter    # peel the first iteration
     li    $t0, 10-1                      # loop counter, with first -- peeled
    
    Loop:                                 # do{
                                              # counter--   (in the branch delay slot and peeled before first iteration)
     add   $a0, $a0, $t0                      # total += counter
     bgtz  $t0, Loop                      # } while($t0 > 0);
     addi  $t0, $t0, -1
    

    这以 3 个周期/迭代运行,3 条指令没有停顿周期(再次假设从 EX 转发到 ID)。

    counter-- 放在分支延迟槽中使其尽可能早于下一个 执行循环分支。一个简单的bne 而不是bgtz 也可以;我们知道循环计数器从有符号正数开始,每次迭代减 1,因此我们不断检查非负数和非零数并不重要。


    我不知道您使用的是什么性能模型。如果它不是经典的 5 阶段 MIPS,那么以上内容无关紧要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-16
      • 1970-01-01
      • 2020-06-09
      • 1970-01-01
      相关资源
      最近更新 更多