CUDA-PTX进位传播

CUDA-PTX进位传播,cuda,ptx,carryflag,Cuda,Ptx,Carryflag,我想在CUDA PTX中添加两个32位无符号整数,还想处理进位传播。我正在使用下面的代码来执行此操作,但结果与预期不符。 根据,添加.cc.u32 d,a,b执行整数加法,并将进位值写入条件代码寄存器,即cc.CF 另一方面,addc.cc.u32 d,a,b使用进位执行整数加法,并将进位值写入条件代码寄存器。此指令的语义为 d=a+b+CC.CF。我还尝试了添加c.u32 d、a、b,没有任何区别 #include <stdio.h> #include <stdlib.h&g

我想在CUDA PTX中添加两个32位无符号整数,还想处理进位传播。我正在使用下面的代码来执行此操作,但结果与预期不符。
根据,添加.cc.u32 d,a,b执行整数加法,并将进位值写入条件代码寄存器,即
cc.CF

另一方面,
addc.cc.u32 d,a,b
使用进位执行整数加法,并将进位值写入条件代码寄存器。此指令的语义为
d=a+b+CC.CF
。我还尝试了添加c.u32 d、a、b,没有任何区别

#include <stdio.h>
#include <stdlib.h>
#include <cuda_runtime_api.h>
#include "device_launch_parameters.h"
#include <cuda.h>

typedef unsigned int u32;
#define TRY_CUDA_CALL(x) \
do \
  { \
    cudaError_t err; \
    err = x; \
    if(err != cudaSuccess) \
  { \
    printf("Error %08X: %s at %s in line %d\n", err, cudaGetErrorString(err), __FILE__, __LINE__); \
    exit(err); \
  } \
} while(0)


__device__ u32
__uaddo(u32 a, u32 b) {
    u32 res;
    asm("add.cc.u32 %0, %1, %2; /* inline */ \n\t" 
        : "=r" (res) : "r" (a) , "r" (b));
    return res;
}

__device__ u32
__uaddc(u32 a, u32 b) {
    u32 res;
    asm("addc.cc.u32 %0, %1, %2; /* inline */ \n\t" 
        : "=r" (res) : "r" (a) , "r" (b));
    return res;
}

__global__ void testing(u32* s)
{
    u32 a, b;

    a = 0xffffffff;
    b = 0x2;
    
    s[0] = __uaddo(a,b);
    s[0] = __uaddc(0,0);

}

int main()
{
    u32 *s_dev;
    u32 *s;
    s = (u32*)malloc(sizeof(u32));
    TRY_CUDA_CALL(cudaMalloc((void**)&s_dev, sizeof(u32)));
    testing<<<1,1>>>(s_dev);
    TRY_CUDA_CALL( cudaMemcpy(s, s_dev, sizeof(u32), cudaMemcpyDeviceToHost) );
    
    printf("s = %d;\n",s[0]);
    
    
    return 1;
}
#包括
#包括
#包括
#包括“设备启动参数.h”
#包括
typedef无符号整数u32;
#定义TRY\u CUDA\u调用(x)\
做\
{ \
错误\
误差=x\
if(err!=cudaSuccess)\
{ \
printf(“第%d行%s处的错误%08X:%s\n”,错误,cudaGetErrorString(错误),\uuuuuu文件,\uuuuuuuu行)\
退出(err)\
} \
}而(0)
__设备uuu32
__uaddo(u32 a、u32 b){
u32 res;
asm(“add.cc.u32%0,%1,%2;/*内联*/\n\t”
:“=r”(res):“r”(a),“r”(b));
返回res;
}
__设备uuu32
__uaddc(u32 a、u32 b){
u32 res;
asm(“addc.cc.u32%0,%1,%2;/*内联*/\n\t”
:“=r”(res):“r”(a),“r”(b));
返回res;
}
__全局无效测试(u32*s)
{
u32 a,b;
a=0xFFFFFF;
b=0x2;
s[0]=u uaddo(a,b);
s[0]=uu-uaddc(0,0);
}
int main()
{
u32*s_dev;
u32*s;
s=(u32*)malloc(sizeof(u32));
尝试使用CUDA调用(cudamaloc((void**)和s_dev,sizeof(u32));
测试(s_-dev);
尝试调用(cudaMemcpy(s,s_dev,sizeof(u32),cudamemcpydevicetoost));
printf(“s=%d;\n”,s[0]);
返回1;
}
据我所知,如果结果不适合变量,就会得到一个进位,这在这里发生,如果符号位损坏,就会出现溢出,但我使用的是无符号值。
上面的代码试图将
0xffffff
添加到
0x2
中,当然结果不适合32位,所以为什么我不在
\uu uaddc(0,0)
调用后得到1

编辑

英伟达Geforce GT 520mx
Windows 7 Ultimate,64位
Visual Studio 2012

CUDA 7.0

因此,正如@njuffa已经说过的,来自其他源代码的其他指令可以在两次调用之间修改
CC.CF
寄存器,并且不能保证获得寄存器的预期值。
作为一种可能的解决方案,可以使用
\uu add32
功能:

__device__ uint2 __add32 (u32 a, u32 b)
{
    uint2 res;
    asm ("add.cc.u32      %0, %2, %3;\n\t"
         "addc.u32        %1, 0, 0;\n\t"
         : "=r"(res.x), "=r"(res.y)
         : "r"(a), "r"(b));
    return res;
}

res.y
将具有可能的进位,而
res.x
将具有加法的结果。

影响
asm()
语句的唯一数据依赖项是由变量绑定显式表示的数据依赖项。请注意,可以绑定寄存器操作数,但不能绑定条件代码。由于在该代码中,
\uu uaddo(a,b)
的结果立即被覆盖,因此编译器确定它对可观察的结果没有贡献,因此是“死代码”,可以消除。通过使用
cuobjdump--dump-SASS
检查发布版本生成的机器代码(SASS),可以很容易地检查这一点

如果我们有稍微不同的代码,不允许编译器完全删除
\uuuuUADDO()
的代码,那么仍然存在一个问题,即编译器可以在为
\uuuuUADDO()
生成的代码和
\uuuUADDC()
生成的代码之间调度它喜欢的任何指令,由于
\uu uaddo()
,这些指令可能会破坏进位标志的任何设置


因此,如果计划将进位标志用于多字算术,则进位生成指令和进位消耗指令必须出现在同一条
asm()
语句中。可以在中找到一个工作示例,该示例演示了如何添加128位操作数。或者,如果必须使用两个独立的
asm()
语句,则可以将前一个语句中的进位标志设置导出到C变量中,然后将其导入后续的
asm()
语句中。我想不出有多少情况下这是可行的,因为使用进位标志的性能优势可能会丧失。

有关如何在PTX中使用进位传播进行多字算术的示例,请参阅。我从您的答案中使用了
add_uint128
,进位传播正常工作,但是我的套鞋怎么了?顺序
add.cc.u32
addc.cc.u32
与我看到的相同。顺序相同,但我使用的调用不同。我认为寄存器
CC.CF
不应该改变。标志是短暂的。除非在同一
asm
语句中重复使用进位标志,否则不能保证它在后续
asm
语句中仍然可用。如果必须使用多个
asm
语句,则需要将进位标志设置“导出”到一个C变量中,并将其“导入”到以下
asm
语句中。您是说,在我使用进位指令之前,另一个进程或线程可能会更改标志?那似乎是对的。添加到asm语句会有帮助吗?文档中说“为了确保asm不会被删除或移动,您应该使用volatile关键字”。据我所知,与
asm()
语句一起使用的
volatile
关键字只控制
asm()
语句中的代码发生了什么,它不控制两个独立的
asm()
语句之间发生的事情。因此,使用
volatile
无法确保两个单独的
asm()
语句之间的进位标志设置继续存在。