为什么在调试模式nVIDIA SASS代码中看到MOV Rn,Rn指令?

时间:2016-09-01 08:21:48

标签: assembly cuda mov nvidia-sass

以下是我正在处理的内核的一些SASS代码片段(对于sm52目标,compiled in debugging mode):

/*0028*/                   ISETP.GE.U32.AND P0, PT, R1, R0, PT;    /* 0x5b6c038000070107 */
/*0030*/               @P0 BRA 0x40;                               /* 0xe24000000080000f */
/*0038*/                   BPT.TRAP 0x1;                           /* 0xe3a00000001000c0 */
                                                                   /* 0x007fbc0321e01fef */
/*0048*/                   IADD R2, R1, RZ;                        /* 0x5c1000000ff70102 */
/*0050*/                   I2I.U32.U32 R2, R2;                     /* 0x5ce0000000270a02 */
/*0058*/                   MOV R2, R2;                             /* 0x5c98078000270002 */
                                                                   /* 0x007fbc03fde01fef */
/*0068*/                   MOV R3, RZ;                             /* 0x5c9807800ff70003 */
/*0070*/                   MOV R2, R2;                             /* 0x5c98078000270002 */
/*0078*/                   MOV R3, R3;                             /* 0x5c98078000370003 */
                                                                   /* 0x007fbc03fde01fef */
/*0088*/                   MOV R4, R2;                             /* 0x5c98078000270004 */
/*0090*/                   MOV R5, R3;                             /* 0x5c98078000370005 */
/*0098*/                   MOV R2, c[0x0][0x4];                    /* 0x4c98078000170002 */
                                                                   /* 0x007fbc03fde01fef */
/*00a8*/                   MOV R3, RZ;                             /* 0x5c9807800ff70003 */
/*00b0*/                   LOP.OR R2, R4, R2;                      /* 0x5c47020000270402 */
/*00b8*/                   LOP.OR R3, R5, R3;                      /* 0x5c47020000370503 */

我注意到的形式是“将寄存器Rn的内容移动到寄存器Rn”形式的几个指令 - 这看起来没有意义。我知道在没有启用调试信息的情况下进行编译,并且通过优化,我没有得到这些说明。但是,即使在调试模式下 - 它们为什么存在?他们的目的是什么? AFAIK,在编译CPU代码进行调试时,你没有得到这些指令。

2 个答案:

答案 0 :(得分:2)

你得到的简单答案会得到奇怪的代码,因为你打开了调试,关闭了优化。这是正常的现代优化编译器,因为它们的工作方式。它们将操作分解为原始static single-assignment (SSA) form,这使得更容易优化,但是当不优化时会生成更糟糕的代码,而非优化编译器会更简单。

虽然我认为不是这种情况,但也有可能指令是故意插入NOP以便延迟执行。 GPU的指令集与您可能熟悉的通用CPU有很大不同。例如,大多数CPU的工作就好像一次一个地执行指令并且严格按照它们给出的顺序执行。尽管现代CPU会尝试并行执行指令甚至乱序执行,但这样做仍然是正确的,以提高性能。 GPU通常不以这种方式工作。如果您尝试在该指令完成之前使用先前指令存储在某个寄存器中的结果,您将获得该寄存器的旧值。与CPU不同,GPU在执行依赖于它的下一条指令之前不会自动等待指令完成。

如果您查看已拆解的代码,您会注意到指令被分为三个指令包。您可能还会看到捆绑包之间存在隐藏的指令。该指令的机器代码显示在右侧(例如/* 0x007fbc0321e01fef */),但它没有在左侧反汇编,尽管像任何其他指令一样占用了8字节的插槽,但它的地址没有显示。这实际上是scheduling block control code。它不是一个真正的指令,而是它指示GPU如何在它之前调度捆绑中的指令。它告诉GPU诸如哪些指令需要等待先前的指令完成以及它们应该等待多长时间的事情。

最后,还有一种可能性,尽管极不可能,冗余MOV实际上根本不是NOP。它们可能会以一些奇怪的方式对覆盖寄存器值进行操作,并与其他指令并行,这使得它们除了延迟之外还具有有用的效果。然而,这将是一种非常先进的优化技术,我只能在手动调整的汇编代码中使用,而不是在甚至不生成优化代码的编译器中。

答案 1 :(得分:1)

基于一般编译器知识,我不了解CUDA。

大多数编程语言主要具有上下文/无状态命令。每个这样的命令可以单独编译到目标机器代码/操作码输出中(使得这个编译步骤很容易实现,只处理单个实际解析的命令)。一些例外是各种前缀 / 后缀 / 修饰符,或类似continue / break来控制循环。

例如variable = variable + 2;可以编译成"将两个添加到变量"独立于源中的前一个和下一个命令(简单和快速),它变成:"从存储器加载变量到寄存器,添加两个到寄存器,将值从寄存器存储回变量存储器"。

将使用哪个寄存器很难决定。如果您想一段时间,随机寄存器分配与任何其他天真分配规则一样好。这通常是在编译的早期阶段如何分配寄存器的方式(使用任何具有最小惩罚的寄存器进行破坏)。

但是你需要一些"桥接"用于在它们之间连接命令的代码,要么在内存中使用严格的变量(根本没有桥接代码),要么在命令之间重用/共享某些值,只需将它们移动到正确的寄存器中(你的"无意义" { {1}}指令,从内存中保存一些提取指令。

编译阶段优化寄存器分配(尝试增加寄存器的共享/重用,为某些命令重新分配寄存器并再次编译它们,有时甚至重新排序命令块以使寄存器共享更加优化)是非平凡的任务和耗时的编译步骤,代码不需要工作。调试编译会跳过此步骤以更快地生成二进制文件。

同样在调试构建中,希望在每个源命令之后将变量值存储到其内存中,以使结果在调试器中可见,尽管在优化版本构建中,编译器可以识别"中介"某些结果的性质,并将它们暂时保留在寄存器中。