1

这是我正在处理的内核的一些 SASS 代码片段(对于 sm52 目标,在调试模式下编译):

/*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 代码进行调试时,您不会得到这些指令。

4

2 回答 2

2

你得到的简单答案是得到奇怪的代码,因为你打开了关闭优化的调试。这对于现代优化编译器来说是正常的,因为它们是如何工作的。它们将操作分解为原始的静态单一赋值 (SSA) 形式,这使得优化变得更容易,但在不优化时会生成更糟糕的代码,而不是更简单的非优化编译器。

还有一种可能,尽管我认为这里不是这种情况,指令是故意插入 NOP 以延迟执行。GPU 的指令集与您可能熟悉的通用 CPU 大不相同。例如,大多数 CPU 的工作方式就像是一次执行一条指令,并且严格按照给定的顺序执行。尽管现代 CPU 会尝试并行甚至乱序执行指令以提高性能,但这是事实。GPU 通常不会以这种方式工作。如果您尝试使用前一条指令在该指令完成之前存储在某个寄存器中的结果,您将获得该寄存器的旧值。与 CPU 不同,GPU 不会

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

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

于 2016-09-01T18:25:08.483 回答
1

基于一般的编译器知识,我对CUDA一无所知。

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

例如variable = variable + 2;可以独立于源代码中的上一条和下一条命令(简单而快速)编译为“将两个变量加到变量”,这变成:“将变量从内存加载到寄存器,将两个加到寄存器,将值从寄存器存储回可变内存”。

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

但是然后你需要一些“桥”代码来连接它们之间的命令,或者使用内存中的严格变量(然后根本没有桥代码),或者在命令之间重用/共享一些值,只需将它们移动到适当的寄存器中(你的“非感觉”mov rN,rN指令,从内存中保存一些获取指令)。

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

同样在调试构建中,希望在每个源命令之后将变量值存储到它的内存中,以使结果在调试器中可见,尽管在优化发布构建中编译器可能会识别某些结果的“中间”性质,并将它们暂时保存在寄存器中.

于 2016-09-01T15:46:20.473 回答