我的印象是,在负零点处添加正零应产生正零。引用IEEE 754 2008:
当具有相反符号的两个操作数的总和(或具有相同符号的两个操作数的差异)恰好为零时,除了roundTowardNegative之外,在所有舍入方向属性中该和(或差)的符号应为+0;在该属性下,精确零和(或差)的符号应为-0。但是,即使x为零,x + x = x - ( - x)也保持与x相同的符号。
然而,在CUDA的情况下,编译器似乎过于积极地优化在Release版本中添加正零。普通的C / C ++(或C#/ .NET)正在按预期工作。我已经查看了编译器为不同版本生成的PTX代码,并且发布版本确实缺少add.f32指令。
我在这里遗漏了什么吗?
__global__ void convertToPositiveZero(float* dst, int size)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
if (index < size)
{
dst[index] += 0;
}
}
// Host code
int size = 100;
float* zzh = (float*)malloc(size * sizeof(float));
zzh[0] = -0.0f;
zzh[1] = 0.0f;
assert(0x80000000 == *((int*)&zzh[0]));
if (0x80000000 != *((int*)&zzh[0]))
{
printf("Expected negative zero.\n");
exit(-1);
}
assert(0x00000000 == *((int*)&zzh[1]));
float* zzd;
cudaMalloc(&zzd, size * sizeof(float));
cudaMemcpy(zzd, zzh, size * sizeof(float), cudaMemcpyHostToDevice);
convertToPositiveZero<<<1, 100>>>(zzd, size);
cudaMemcpy(zzh, zzd, size * sizeof(float), cudaMemcpyDeviceToHost);
//zzh[0] += 0.0f;
assert(0x00000000 == *((int*)&zzh[0]));
if (0x00000000 != *((int*)&zzh[0]))
{
printf("Expected positive zero.\n");
exit(-1);
}
assert(0x00000000 == *((int*)&zzh[1]));
printf("Done.\n");
答案 0 :(得分:1)
您的问题似乎是由于nvcc
将FADD
和FMUL
融合到FMAD
操作时所执行的优化。
我能够在发布模式下重现您的问题。由CUDA 5.5和sm=2.1
编译的生成的反汇编代码是
code for sm_21
Function : _Z21convertToPositiveZeroPfi
.headerflags @"EF_CUDA_SM20 EF_CUDA_PTX_SM(EF_CUDA_SM20)
/*0000*/ MOV R1, c[0x1][0x100];
/*0008*/ S2R R0, SR_CTAID.X;
/*0010*/ S2R R2, SR_TID.X;
/*0018*/ IMAD R0, R0, c[0x0][0x8], R2;
/*0020*/ ISETP.GE.AND P0, PT, R0, c[0x0][0x28], PT;
/*0028*/ @P0 BRA.U 0x60;
/*0030*/ @!P0 MOV32I R3, 0x4;
/*0038*/ @!P0 IMAD R2.CC, R0, R3, c[0x0][0x20];
/*0040*/ @!P0 IMAD.HI.X R3, R0, R3, c[0x0][0x24];
/*0048*/ @!P0 LD.E R0, [R2];
/*0050*/ @!P0 F2F.F32.F32 R0, R0;
/*0058*/ @!P0 ST.E [R2], R0;
/*0060*/ EXIT ;
正如您在PTX文件中也注意到的那样,没有浮点添加操作。现在,如果使用-fmad=false
选项进行编译,则反汇编代码将变为
code for sm_21
Function : _Z21convertToPositiveZeroPfi
.headerflags @"EF_CUDA_SM20 EF_CUDA_PTX_SM(EF_CUDA_SM20)
/*0000*/ MOV R1, c[0x1][0x100];
/*0008*/ S2R R0, SR_CTAID.X;
/*0010*/ S2R R2, SR_TID.X;
/*0018*/ IMAD R0, R0, c[0x0][0x8], R2;
/*0020*/ ISETP.GE.AND P0, PT, R0, c[0x0][0x28], PT;
/*0028*/ @P0 BRA.U 0x60;
/*0030*/ @!P0 MOV32I R3, 0x4;
/*0038*/ @!P0 IMAD R2.CC, R0, R3, c[0x0][0x20];
/*0040*/ @!P0 IMAD.HI.X R3, R0, R3, c[0x0][0x24];
/*0048*/ @!P0 LD.E R0, [R2];
/*0050*/ @!P0 FADD R0, R0, RZ;
/*0058*/ @!P0 ST.E [R2], R0;
/*0060*/ EXIT ;
如您所见,恢复了FADD
操作的存在,并且还恢复了0
的“正确”符号。