超标量处理器设计——第八章_发射

参考《超标量处理器》姚永斌著

文章目录

  • 超标量处理器设计——第八章_发射
    • 8.1 简述
      • 8.1.1 集中式 VS. 分布式
      • 8.1.2 数据捕捉 VS. 非数据捕捉
      • 8.1.3 压缩 VS. 非压缩
    • 8.2 发射过程的流水线
      • 8.2.1 非数据捕捉结构的流水线
      • 8.2.2 数据捕捉结构的流水线
    • 8.3 分配
    • 8.4 仲裁
      • 8.4.1 1-of-M的仲裁电路
      • 8.4.2 N-of-M的仲裁电路
    • 8.5 唤醒
      • 8.5.1 单周期指令唤醒
      • 8.5.2 多周期指令的唤醒
      • 8.5.3 推测唤醒
        • 1. Issue Queue Based Replay
          • Non-Selective Replay
          • Load-Vector
          • Load Position Vector(LPV)
          • 基于IQ进行replay方法的缺陷
        • 2. Replay Queue Based Replay

8.1 简述

  • 发射:将符合条件的指令从发射队列(Issue Queue)中选出来,送到FU中执行的过程。
  • 重命名阶段的指令被写到ROB的同时,也会写到Issue Queue中
  • 发射队列也称为保留站(Reservation Sation, RS)

上图中的发射阶段涉及的硬件有:

  1. 发射队列(Issue queue)
  2. 分配电路(Allocation)
  3. 选择电路(Select)也叫仲裁(Arbiter)电路
  4. 唤醒电路(Wake-up)
    后面会对这些电路一一进行介绍。

另外,发射阶段有很多实现方式,主要分为:

  1. 集中式Centralized)和分布式Distributed
  2. 压缩式Compressing)和非压缩式Non-compressing
  3. 数据捕捉式Data-capture)和非数据捕捉式Non-data-capture

8.1.1 集中式 VS. 分布式

  • 集中式:所有FU公用一个发射队列(Centralized Issue Queue, CIQ)

    • 优势:容量大,利用效率高
    • 劣势:选择和唤醒电路会变得复杂
  • 分布式:每个FU都有一个单独的发射队列(Distributed Issue Queue, DIQ)
    • 优势:容量小,选择电路简单(每个FU对应一个选择电路即可)
    • 劣势:
      • 指令分布较为分散,唤醒电路需要较多布线资源;
      • 效率低,会有“木桶效应”,只要一个队列满,就不能写入新指令,需要将发射之前的流水线都暂停。

   实际现代处理器会结合上述两个方法,有些FU公用一个发射队列。其他用各自的发射队列。

8.1.2 数据捕捉 VS. 非数据捕捉

  • 数据捕捉式:在流水线发射阶段之前读取寄存器。

    • 寄存器重命名后的指令先读PRF,将读出值与指令一起写入IQ。

    • 如果有寄存器的值还没计算出来,那就写入寄存器编号,供唤醒使用。且会标记为Non-available。

    • 发射队列中存储指令操作数的地方是payload RAM:

    • payload RAM存储了指令的源操作数,当指令从IQ中被仲裁电路选中(grant),就会去payload RAM里取源操作数送到FU执行。

    • 当指令被select时,会将自己的目的寄存器编号广播给IQ中的其他指令,有相等的话会在payload RAM中相应位置做标记,表示这些指令的源操作数会用到该指令的目的寄存器值。

    • 当目的寄存器的值经过FU计算出来,就会经过bypass网络来更新payload RAM中做了标记的位置。

    • 这种方式像是payload RAM在捕捉FU计算的结果,所以称为数据捕捉(data-capture)。注意从payload RAM中读取数据时指令还没离开发射队列。

    • 工作流程:

  • 非数据捕捉式:在发射阶段之后读取物理寄存器。

    • 重命名后的指令不读物理寄存器堆,而是直接将源寄存器编号一起送入IQ。

    • 工作流程:

    • 因为发射时才会读物理寄存器堆,所以寄存器堆的读端口个数是issue width*2, 一般会比较大, 相较于在发射阶段之前读取寄存器来说,这种方式读端口会更多

对比:

  1. 数据捕捉式需要的寄存器读端口较少,但是要在IQ中存储操作数,所以IQ面积会大些.
  2. 数据捕捉式的源操作数要经历两读一写, 需要从寄存器内读出来,写到IQ的payload RAM, 之后再从IQ读到FU, 因此功耗会大.
  3. 非数据捕捉式需要的寄存器读端口多,但IQ不用存操作数,所以IQ面积小,速度也快些, 且源操作数只要读一次,功耗也会低一些.

应用:

  • 两种方式与寄存器重命名的实现方式直接相关.
  • 如果用ROB进行寄存器重命名, 一般都会用数据捕捉式, 因为此时指令退休时会将结果从ROB搬运到ARF, 用数据捕捉方法不用关心目的寄存器位置的变化,因为指令结果会存到payload RAM中
  • 用非数据捕捉式需要关注指令结果的位置变化.

8.1.3 压缩 VS. 非压缩

  • 压缩式(Compressing Issue Queue)

    • 给个图自行体会一下:

    • 简单来说就是发射出去的指令空出来的位置会被后面的质量补上

    • 一种电路实现结构如下, 这种结构只能实现靠在一起的指令发生的压缩:

    • 压缩的IQ选择电路比较简单

    • 选择电路一般会从IQ中准备好的指令选择一条最旧的指令发射,称为oldest first方法.

    • 压缩式的IQ天然地按照最新->最旧的顺序排列,因此只需要用优先级编码器就可以实现选择电路, 结构如下:

    • 优点:

      • 分配(Allocation)电路简单, 因为空闲空间总是处于上面,所以只需要用发射队列的写指针指向地一个空闲地址即可
      • 选择电路简单, 上文已经提到了
    • 缺点:

      • 费面积, 当一次发射多条指令, 且发射的指令不靠在一起, 需要复杂的布线和MUX逻辑.
      • 功耗大, 因为每个周期都要移动IQ的内容来进行压缩, 移动的项很多.
  • 非压缩式(Non-Compressiong Issue Queue)

    • 发射出指令后IQ中其他指令不移动,也就是不进行压缩

    • 与压缩式的区别:

    • IQ中的指令不再有最新到最旧的顺序, 因为发射出指令后的空档可能被新指令填满, 此时从下到上就没有顺序关联了.

    • 虽然也能让选择电路仍然按照从下到上取指令,但是因为没有oldest first, 所以对RAW相关性的指令就不能最大限度地释放.

    • 优点:

        1. 功耗小,面积小
    • 缺点:

        1. 要实现oldest-first需要更复杂的电路,延时更大.
        1. 分配电路比较复杂,需要扫描所有空闲空间

8.2 发射过程的流水线

8.2.1 非数据捕捉结构的流水线

   进入到IQ中的指令要被FU执行,必须满足:

  1. 这条指令所有的源操作数都准备好了
  2. 能够从发射队列被选中(需要经过仲裁电路允许)
  3. 需要能够从寄存器、payload RAM或旁路网络中获得源操作数
    下面是一条指令从旁路网络获得源操作数的示意图:
  • 上图的方法实际上在上世纪六七十年代就提出了,名字叫tomasulo算法。

  • 但是这种方法效率不高,实际上具有RAW相关性的指令可以这样做:

  • 更接近显示的是下面的示意图:

  • 仲裁(Select)负责从发射队列中找出一条合适的指令。

  • 唤醒(Wake-up)负责将发射队列中相关寄存器置为准备好的状态。

  • 为了使RAW相关的指令背靠背执行,需要保持仲裁和唤醒在一个周期内做完

  • 仲裁和唤醒电路相对比较复杂,一个周期做完会对频率有影响。如果将两步分两拍做,虽然能提升性能,但是会影响IPC:

  • 上图中假设原始频率为1GHz, 每周期可执行两条指令,则IPC=2

  • 当仲裁和唤醒拆分后,指令之间就不能背靠背执行,假设使IPC下降了15%,变成了2*0.85=1.7, 但是频率变为2GHz, 每秒指令数提升到.3.4G, 提升了85%

  • 但是实际上拆分后延时不能均分,所以达不到2GHz, 可能就1.5GHz,这样速度提升就变为了27.5%

  • 此外还会引入一些负面影响:

    1. 分支预测失败的惩罚加大
    2. cache访问周期数增加(因为周期变小了)
    3. 增加一级流水线,功耗增大
  • 当FU需要多周期才能算完时,需要将唤醒延迟:

8.2.2 数据捕捉结构的流水线

  • 与非数据捕捉型最大的区别是发射队列中采用了payload RAM存储所有指令的源操作数。而不需要读寄存器堆。
  • 指令在被仲裁电路选中之后,同周期也会堆发射队列的其他指令进行唤醒;同时还会读取payload RAM; 两个操作是并行的
  • 不一定所有指令都要从payload RAM读取操作数,也可以从旁路网络读取。
  • 该流水线payload RAM在同一个周期既要被读又要被写,延时是比较大的,可以让他单独占一级流水线

8.3 分配

  • 对于非压缩的发射队列,空闲的空间是没有规律的

  • 分配电路需要从IQ中找出4条(4-way处理器)空闲的表项写入重命名后的指令

  • 如下图所示,分配电路需要根据IQ的free bit位选择4个空位:

  • 一次性处理所有的free bit对发射电路来说是较大的负担,可以采用分治的方法:

  • 上面的分治方法弊端也很明显,如果某一组没有空闲位,可能会出现一条指令阻碍其他指令写入IQ

8.4 仲裁

8.4.1 1-of-M的仲裁电路

  • 仲裁电路的一个主要工作就是落实oldest-first的仲裁效果。先执行旧的指令可以唤醒更多指令

  • 对于非压缩的IQ, 由于指令在IQ中的年龄不一定跟位置直接相关,所以不能根据位置来选择。

  • 在ROB中保留了指令的顺序,因此可以使用每条指令在ROB中的位置作为该指令的年龄信息。但是由于ROB是一个FIFO,其读写指针地址会翻转,所以不能简单地根据地址来判断:

    • 需要在ROB地址再加上一位,称为“位置位”

    • 可以发现:

      1. 位置位相同时,ROB地址越小,指令越旧
      2. 位置位不同时,ROB地址越大,指令越旧
  • ROB中的年龄值需要写入IQ中,供仲裁电路使用,找出年龄最旧的指令。例如下面就是一个1-8的仲裁电路,采用二分的方式实现:

  • 实际IQ中会存在没有准备好的指令,仲裁时需要加以屏蔽,可以为IQ的表项增加一个ready位,当两者ready位均为1时,才会比较选择年龄值小(对应最旧的);两个ready均为0时,会将输出级的ready置0;有一个ready为0时,选另外一个输出。

    • 找到年龄最小的指令时,最好将该指令在IQ中的地址也跟随着送出:

8.4.2 N-of-M的仲裁电路

  • 近似的N-M仲裁电路:

  • 每个FU都有一个1-M仲裁器,IQ中的表项根据所属的指令类型选择送到那个FU的1-M仲裁器中。

  • 这种方式的限制是,如果要实现5-M仲裁,但是只有4个FU时,最多只能实现4-M仲裁

    • 例如下面的这种情况: IQ每次可以发射2条ALU指令, 如果只有一个ALU和1-4仲裁器,没法实现2发射,所以增加一个ALU. 并增加一个ALU分配位, 每次进IQ的指令都会带上这个分配位, 且分配位每周期都会翻转,从而实现轮换使用两个ALU的目的. 但是轮换的方式也有问题:

    • 左图: ADD和SUB同一周期来, 分配状态=0,被送入第一个ALU;AND XOR下一周期来,状态=1, 送入第二个ALU. 经过select后, 可能ADD和AND最终被发射,但是实际上并没有满足严格的oldest-first, 因为SUB比AND跟旧

    • 右图: 如果某次解码出3条ALU指令,同周期进入IQ, 那么他们都属于ALU0, 同时准备好, 此时ALU1就被闲置了.

8.5 唤醒

8.5.1 单周期指令唤醒

  • 唤醒是指被仲裁电路选中的指令将其目的寄存器编号和IQ中所有源寄存器编号进行比较,并将结果想等的源寄存器进行标记的过程(标记为ready)

  • 唤醒电路如下:

  • VAlL/R: 指令中是否有第一/二个源寄存器

  • RdyL/R: 指令中第一/二个源寄存器是否已经被唤醒

  • Issued: 指令被仲裁电路选中后,可能不会马上离开IQ, 用该位进行标记, 这样后续他将不会再向仲裁电路发出请求信号.

  • 一条指令的源操作数都ready后,才会向仲裁器发出req, 仲裁器仲裁后给出结果grant. 上图中4个grant信号实际只会有一个有效.

  • Grant只要有效, 该指令就被标记为issued

  • 被issued的指令会将自己的dest寄存器地址广播到tag bus上,用来唤醒其他指令的src

8.5.2 多周期指令的唤醒

  • 单周期的唤醒中, 一条被仲裁器选中的指令能在本周期对IQ中其他源寄存器进行唤醒,前提是这条指令在一个周期内能被FU执行完.
  • 当指令无法在单周期执行完, 需要将唤醒过程进行延迟
  • 唤醒实际分两个步骤:
    1. 将被选中指令的目的寄存器编号送到总线
    2. 将总线上的值和发射队列所有源寄存器编号进行比较
  • 两个步骤都可以延迟,衍生出两种方法:
    1. 延迟广播: 如果仲裁电路选中的指令执行周期大于1, 则在当前周期, 并不将该指令的目的寄存器编号送到总线, 而是根据执行周期数N, 延迟N-1个周期后送到总线.

      • 问题: 如果两条指令共用一个FU, 可能一条经过延迟唤醒后与下一条竞争总线:

      • 可以记录FU中执行指令所需的周期数, 后续仲裁电路选中时, 如果发现表格中之前记录的指令会跟当前指令冲突, 则本次仲裁不选中该指令. 等下个周期再次仲裁

    2. 延迟唤醒: 延迟N-1拍将ready置1
      • 可以在ready状态位之前加入控制电路控制延迟的周期数,下面是一种实现方式:

      • 其中:

        1. freed: 表示表项是否空闲, 当一条指令写入时,表项就不空闲了, 而当指令被仲裁电路选中时, 并确定没有问题, 可以离开发射队列,此时就可以变为空闲
        2. SrcL._M: 寄存器编号比较结果相等时, 该位会置1. 当仲裁电路给出响应信号, 清零. 他是移位寄存器SrcL_SHIFT的时能
        3. SrcL_SHIFT: 移位寄存器, 当寄存器编号想等时, 会将对应的Delay写到移位寄存器中, 然后在SrcL._M的控制下每周期算数右移一位, 其最低位等效为ready位
        4. ROBID: 该指令在ROB中的位置,也就是年龄信息

8.5.3 推测唤醒

  • DELAY值并不都是固定的:

    1. Load指令: 周期数取决于D-Cache是否命中:

    2. 某些特殊情况, 允许FU结果提前得到(early out). 例如被乘数位数比较小时, 乘法结果可以提前得到

  • 一种办法是等到指令执行完毕的到数据时, 才将目的寄存器编号送到总线上唤醒其他指令:

  • 上图可以进行改进, 因为Cache是否命中是可以提前知道的, 所以可以将wake提前:

  • 更为激进的方式是: 假设DCache总是命中, 也就是推测唤醒(speculative wake-up):

    • 如果L1-Cache缺失:

    • 还需进行状态恢复, 将被load误唤醒的寄存器置为非ready, 如果一些指令离开了IQ,还需要将他们从流水线中抹除,重新放回IQ

  • Independent WIndow(IW) 和Speculative Window(SW):

    • IW是load指令被仲裁器选中到执行间隔的时间. 在这个时间内可以放入一些跟本条load无关的指令.

    • SW是指load指令开始执行到它发现自身是否会DCache命中/缺失的周期数. 在该窗口可以放入一些跟load无关或相关的指令

  • 当发现D-Cache缺失时, 和load指令有相关性的指令需要重新被放回发射队列等待唤醒, 就需要这些指令重新在发射队列中变为not ready的状态, 并重新向仲裁电路发出申请, 这个过程称为replay.

1. Issue Queue Based Replay

  • 这种方式中, 指令被选中之后不会马上离开IQ, 而是将issued置为1.

  • issued =1的指令不会再向仲裁电路发出请求

  • 当该指令需要replay时, 会将issued清除.

  • D-Cache缺失, 交叠结构D-Cache的bank冲突, store/load指令的违例, load/load的违例都可能引发replay

  • 在完全乱序机中, 只有指令退休时指令才能离开IQ, 在顺序或部分乱序机中, SW中的指令才可能被replay

  • 由于大部分指令都不会replay, 但是他们却仍然占着毛坑, 是及浪费了IQ的容量, 会降低性能

Non-Selective Replay
  • 将load指令之后被仲裁电路选中的指令都重新放回发射队列
  • 简单,但无差别地选择浪费了性能:
    • 上图中,Data阶段LD指令发现D-Cache miss, 此时需要把图中所有指令都抹掉

    • 但是实际上, 只有OR和SUB指令真的跟LD有相关性, 应该replay, 其他指令不应该被replay

    • 改良: IW窗口中的指令, 肯定跟本LD无关, 不需要被replay, 也就是途中的MUL和NOR

Load-Vector
  • replay时需要找出跟LD存在相关性的指令, 怎么找? 一种方法是Load-Vector:

      • 每个物理寄存器都有一个load-vector, 对于非load指令, 如果它有目的寄存器, 那么目的寄存器的向量值=两个源寄存器的向量相或
      • 向量的宽度跟流水线深度相同
      • 对于load指令的向量值, 除了来自于源寄存器, 还会占用向量的一个新位
      • 实现方式是一个表格, 表项个数为物理寄存器个数, 指令在寄存器重命名后会访问该表, 或的源寄存器的向量, 并更新目的寄存器向量
      • 当发现一条load产生了D-Cache miss, 就可以根据该load在向量值中对应的位来找到跟他相关的寄存器, 将他们置为not ready
      • 流水线不深时, 这种方法是可以的, 但是流水线很深时, 向量值的宽度也会增加, 会导致更大的硬件资源消耗, 也会增大IQ的延迟
Load Position Vector(LPV)
  • load-vector消耗的资源较多,LPV则试图借助延迟唤醒的操作,找到与load指令相关的指令。
  • load指令使用一个5位的值,表示该load指令处于流水线的哪个位置(select,RF, Cal Addr, TLB/Tag, Data), 该值称为Load Position Vector (LPV)
    1. 每当一条load被仲裁电路选中, 会将他的LPV置为10000, 表示这条指令处于Select阶段。之后会将load放到总线上唤醒与IQ中所有源寄存器比较,如果结果相等,该源寄存器会获得该LPV
    2. 每个周期,IQ中所有源寄存器的移位寄存器会因为延迟唤醒而右移,同样,该寄存器捕获的LPV也会右移一位, 来追踪load在流水线中的状态。
    3. 为了检查间接相关性,每条指令在唤醒其他指令时,会将LPV传递给其他指令的源寄存器
    4. load指令的LPV也会不断右移,这样当load运行到data阶段发现D-Cache缺失, 就去IQ中寻找LPV最低位是1的所有源寄存器,这些寄存器就是与load存在直接或间接相关性的,需要置为not ready
  • 下面几张图展示了LPV传递这一过程:
  • LPV的位数不会随流水线中指令个数增多而显著增大,而是只跟load执行的流水线级数有关。会比load-vector资源少些,速度快些
  • 如果每周期可以处理两条load指令,那么需要设置两个LPV值,分别记录两条load的位置。但是D-Cache的端口不会很多,所以每周期可执行的load不会很多

小结:

  • Non-selective Replay实际是在load发现D-cache缺失时,将SW窗口中的指令从流水线中抹除,
  • Selective Replay则结合LPV等方法选择性地将有关联的指令抹除
  • 之后这些指令重新放回IQ,等待重新唤醒。
基于IQ进行replay方法的缺陷
  • 基于IQ进行replay的方法中,很多指令在被仲裁电路选中后不能马上离开IQ, 会降低发射队列实际可用的容量,使得处理器无法最大限度寻找并行性

2. Replay Queue Based Replay

  • 为了提高IQ的利用率, 这种方法中,指令被仲裁电路选中后,会马上离开发射队列,但不会消失,而是进入另一个部件Replay Queue(RQ)

  • 当一条指令被验证执行正确,例如退休时,才能离开RQ

  • 如果load发现D-Cache缺失,除了将相关指令从流水线抹除, 还要将这些指令在RQ中再次唤醒,并向仲裁电路发出请求,他们相比IQ中的指令有更高的优先级。

  • 一般从流水线Select到Execute需要的周期越多,replay的指令也越多。对于数据捕捉结构的IQ,S-E时间短,所以可以用基于IQ的replay方法;而对于非数据捕捉结构,S-E时间会长一些,基于IQ的replay会造成IQ实际容量变小,所以可以配合使用基于RQ的replay方法。

超标量处理器设计——第八章_发射相关推荐

  1. 超标量处理器设计——第十章_提交

    参考<超标量处理器>姚永斌著 文章目录 超标量处理器设计--第十章_提交 10.1 概述 10.2 重排序缓存 10.2.1 一般结构 10.2.2 端口需求 10.3 管理处理器的状态 ...

  2. 超标量处理器设计——第四章_分支预测

    超标量处理器设计--第四章_分支预测 参考<超标量处理器>姚永斌著 4.1 简述 分支预测主要与预测两个内容, 一个是分支方向, 还有一个是跳转的目标地址 首先需要识别出取出的指令是否是分 ...

  3. 超标量处理器设计——第七章_寄存器重命名

    超标量处理器设计--第七章_寄存器重命名 参考<超标量处理器>姚永斌著 文章目录 超标量处理器设计--第七章_寄存器重命名 7.1 简述 7.2 寄存器重命名方式 7.2.1 用ROB进行 ...

  4. 超标量处理器设计 姚永斌 第8章 指令发射 摘录

    8.1 概述 何为发射?它就是将符合一定条件的指令从发射队列issue queue中选出来,并送到FU中执行的过程.发射队列页可以叫做保留站reservation station. 发射队列会按照一定 ...

  5. 超标量处理器设计——第二章_Cache

    超标量处理器设计--第二章_Cache 参考<超标量处理器>姚永斌著 文章目录 超标量处理器设计--第二章_Cache Cache的一般设计 2.1.1 cache组成方式 2.1.2 C ...

  6. 超标量处理器设计 姚永斌 第9章 指令执行 摘录

    9.1 概述 执行阶段负责指令的执行,在流水线的之前阶段做了那么多的事情,就是为了将指令送到这个阶段进行执行.在执行阶段,接受指令的源操作数,对其进行规定的操作,例如加减法.访问存储器.判断条件等,然 ...

  7. 多核处理器_基于NXP i.MX8MM多核应用处理器设计的智能加油机

    随着近几年信息技术的不断发展,智能化的概念逐渐渗透到各行各业以及我们的生活里,智能化.自助设备随处可见.在人来人往.车来车往的城市建设中,节省时间.免去排队百脸茫然.一脸懵圈的痛苦过程成为智能设备发展 ...

  8. 超标量处理器基础笔记

    超标量处理器基础-笔记 现代处理器设计经典书籍,2004年出版 第一章 处理器设计 1.2 指令集处理器设计 1.微处理器的设计规范就是指令集体系结构(ISA),它定义了微处理器必须执行的一整套指令集 ...

  9. 第十八章_后端架构选型、离线及实时计算

    文章目录 第十八章_后端架构选型.离线及实时计算 18.1 为什么需要分布式计算? 18.2 目前有哪些深度学习分布式计算框架? 18.2.1 PaddlePaddle 18.2.2 Deeplear ...

最新文章

  1. 在ireport报错 报 jdk5找不到的解决办法
  2. 计算机基础教育德育教学,【家庭教育论文】计算机基础教学的德育教育(共2650字)...
  3. tp5中在where中使用in
  4. MapReduce的计算资源划分
  5. Python自动化整理文件,还增加了可视化GUI界面
  6. FFmpeg之x264/x265转码去掉B帧(二十六)
  7. 自然伽马测井基础知识
  8. HTML里css画蝴蝶,纯css3制作煽动翅膀的蝴蝶的示例
  9. appstore ip地址
  10. coding AD:最短路径Floyd算法过程矩阵的计算:十字交叉法
  11. html-HR标签分割线
  12. RHCE证书考试报名流程
  13. 在麒麟桌面操作系统编译安装postgresql的经历
  14. U盘安装纯净版的win7系统
  15. AI为什么救不了“想上天”的猪?
  16. 微信朋友圈卖葡萄经验分享
  17. 奥维 最新 图源2023
  18. iphone相册源码下载(仿three20)
  19. ARM平台处理器简介-ARMv7
  20. 《日落苍狼》读后感及摘录

热门文章

  1. 中文 NLP 工具总结
  2. 传智播客JavaEE 第168期就业班视频教程 Java自学课程
  3. php 什么是负载均衡,什么是负载均衡器
  4. Java#中间件Tomcat-Typora笔记
  5. RuntimeError: Failed to allocate graph: MYRIAD device is not opened
  6. dbi mysql_使用Perl DBI操作MySQL的一些建议
  7. [模板] BSGS/扩展BSGS
  8. 简单医疗系统设计(一)登录界面的制作
  9. GStreamer编程笔记
  10. mac系统 flutter从安装到第一个应用