0

我试图了解 OpenCL 编译器生成的机器代码以优化它。因此,我使用工具 m2s-opencl-kc(来自 multi2sim)离线编译我的 *.cl 文件并将中间文件(开关:-a)保留为 *.isa 文件。这个 *.isa 包含一个“反汇编”部分,这似乎是我正在寻找的......

注意:我的组装知识有点“老”。我为 Pentium 386/486 CPU 等老款 CPU 制作了组件。所以我实际上在阅读矢量指令时遇到了问题,而我对它们有一些理论知识。

[... OTHER STUFF ... ]
; --------  Disassembly --------------------
00 ALU_PUSH_BEFORE: ADDR(32) CNT(6) KCACHE0(CB2:0-15) KCACHE1(CB0:0-15)
      0  x: MOV         R2.x,  0.0f
         z: SETGT_INT   R0.z,  1,  KC0[0].y
         t: MULLO_INT   ____,  R1.x,  KC1[1].x
      1  w: ADD_INT     ____,  R0.x,  PS0
      2  y: ADD_INT     R2.y,  PV1.w,  KC1[6].x
      3  x: PREDE_INT   ____,  R0.z,  0.0f      UPDATE_EXEC_MASK UPDATE_PRED
01 JUMP  POP_CNT(1) ADDR(9)
02 ALU: ADDR(38) CNT(5) KCACHE0(CB1:0-15)
      4  x: MOV         R2.x,  0.0f
         y: MOV         R3.y,  0.0f
         w: LSHL        ____,  R2.y,  2
      5  z: ADD_INT     R2.z,  KC0[0].x,  PV4.w
03 LOOP_DX10 i0 FAIL_JUMP_ADDR(8)
    04 ALU: ADDR(43) CNT(11) KCACHE0(CB2:0-15)
          6  y: ADD_INT     R3.y,  R3.y,  1
             w: LSHL        ____,  R3.y,  2
          7  x: SETGT_INT   R3.x,  KC0[0].y,  PV6.y
             z: ADD_INT     ____,  R2.z,  PV6.w
             w: ADD_INT     ____,  PV6.w,  8
          8  x: ASHR        R0.x,  PV7.w,  4
             y: LSHR        R0.y,  PV7.z,  2
             z: BFE_UINT    R0.z,  PV7.w,  0x00000002,  0x00000002
[... some more ... ]

我想知道的是命令前面的数字和字符的含义。据我了解,编译器产生了一些“复杂”的指令:

00 ALU_PUSH_BEFORE: ADDR(32) CNT(6) KCACHE0(CB2:0-15) KCACHE1(CB0:0-15)

(问题:那是所谓的“非常长的指令词”吗?)

而这个“复杂”指令由多个“简单”指令组成:

      0  x: MOV         R2.x,  0.0f
         z: SETGT_INT   R0.z,  1,  KC0[0].y
         t: MULLO_INT   ____,  R1.x,  KC1[1].x
      1  w: ADD_INT     ____,  R0.x,  PS0
      2  y: ADD_INT     R2.y,  PV1.w,  KC1[6].x
      3  x: PREDE_INT   ____,  R0.z,  0.0f      UPDATE_EXEC_MASK UPDATE_PRED

这些“简单”的指令似乎是每个向量单元的指令。四个向量单元由 x、y、z 和 w 引用。但什么是“不”?那是另一个向量单位吗?我为“赛普拉斯”GPU编译了它......

现在关于数字......这些就像“行号”吗?前导零:复杂指令序列号...?无前导零:简单指令序列号...?

如果我们假设内存访问没有等待状态,我假设所有具有相同序列的“简单”指令都可以在一个周期内“逻辑地”执行。例如,以下指令(上述复杂指令的)在周期 0 中“执行”:

      0  x: MOV         R2.x,  0.0f
         z: SETGT_INT   R0.z,  1,  KC0[0].y
         t: MULLO_INT   ____,  R1.x,  KC1[1].x

“已执行”是指我们有某种(例如 4 周期)流水线。这意味着上述指令应该在周期 0 开始执行,并且应该在周期 3 之后完成。

关于流水线的问题

如果下一条指令(例如“1”)读取寄存器 R2.x 会发生什么?那会读取 R2.x 的旧值(在指令“0”之前)还是会延迟指令“1”,直到指令“0”完成?或者这可能是编译器必须注意的“不关心”情况(产生未定义的结果),这种情况永远不会发生?

关于内存访问的问题

我假设可以在数据获取周期期间执行对寄存器的访问,而无需等待。内存访问将需要一些额外的周期,具体取决于访问的内存类型:

  • “__private”内存应该主要映射到寄存器。
  • __local memory(最多 64KB 在同一组的工作项之间共享):在当前的 GPU 中我需要多少额外的周期?
  • __global memory:这应该是例如 256MB 到 x GB 的外部 DRAM。我在这里需要多少额外的周期?据我所知,此内存没有为 GPU 设备缓存。
  • __constant 内存应该类似于 __global 内存,但使用 __local 内存进行缓存

“ISA”有什么好的教程吗?

问候,斯特凡

4

1 回答 1

2

每个“编​​号”部分都是一个 VLIW,例如:

  4  x: MOV         R2.x,  0.0f
     y: MOV         R3.y,  0.0f
     w: LSHL        ____,  R2.y,  2

这是一条使用三个可用 ALU 的指令,即“x”、“y”和“w”。它还可以使用构成最大并行指令的“z”和“t”,例如:

  4  x: MOV         R2.x,  0.0f
     y: MOV         R3.y,  0.0f
     z: MOV         R3.z,  0.0f
     w: LSHL        ____,  R2.y,  2
     t: LSHL        ____,  R2.z,  4

尽管如此,这是一个单一的VLIW 指令,它“馈送”着色器核心的所有五个 ALU“通道”,然后五个操作在一个步骤中并行执行。

但什么是“不”?那是另一个向量单位吗?

是的,“t”是第五个标量单位,称为“先验”,可用于执行先验计算,例如sin(x)cos(x)。除此之外,它还可以执行正常的标量运算,但其局限性在于,并非“x”到“w”中可能的所有标量运算也可以在“t”中执行。因此,理想情况下,每个内核可以在一个步骤中执行五个标量操作。值得注意的是,与 CPU 上的 SSE 指令不同,这五个单元独立工作:每个单元都可以在每个步骤中执行自己的操作,而在 SSE 单元中,只能将一个操作并行应用于多个数据。和 VLIW 架构。

那些

01 JUMP  POP_CNT(1) ADDR(9)

指令显然是特殊指令,实际上并不在 ALU 上执行操作,例如从(片外)存储器或控制流指令中获取数据。

要估计内存延迟,请查看AMD 的 OpenCL 编程指南中的附录 D - 设备参数

__constant内存与内存不完全相同__local:它在芯片上有自己的内存空间,对于所有工作项都是相同的,并且根据文档,它的访问速度大约是内存的两倍__local- 因为没有一致性逻辑工作项之间需要。

互联网上的一些消息来源指出,(AMD)GPU 没有缓存,应该使用 LDS 内存来显式模拟缓存。在一些文档中,虽然有L1 和 L2 缓存的引用。

无论哪种方式,请注意,当一个线程停止等待数据时,GPU 通过极快地切换执行“上下文”来“隐藏”内存延迟非常好。如果有足够多的并行任务可供选择,GPU 很可能总能找到一个准备好执行的任务,该任务可以换成需要等待的任务。

于 2013-07-13T17:11:58.987 回答