如 cuda c编程指南 http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#arithmetic-instructions中的表2所述,32位浮点数的每个多处理器的每个时钟周期的操作数为128,而它是4用于64位浮点加法,即64位浮点加法慢32倍。
但是,由于我使用以下代码来测试速度差异,双版本最多比浮动慢2倍(即使编译标志也没有太大变化 - 设备调试),有没有人知道原因?
#define N 100000000
typedef double Real;
// Device code
__global__ void VecAdd(Real* A, Real* B, Real* C)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < N) {
C[i] = A[i] + B[i];
}
}
// Host code
int main()
{
size_t size = N * sizeof(Real);
// Allocate input vectors h_A and h_B in host memory
Real* h_A = (Real*)malloc(size);
Real* h_B = (Real*)malloc(size);
Real* h_C = (Real*)malloc(size);
// Initialize input vectors
for (int i = 0; i < N; i++)
{
h_A[i] = 1.0f + i * 0.1f;
h_B[i] = 100.0f + i * 0.1f;
}
// Allocate vectors in device memory
Real* d_A;
cudaMalloc(&d_A, size);
Real* d_B;
cudaMalloc(&d_B, size);
Real* d_C;
cudaMalloc(&d_C, size);
// Copy vectors from host memory to device memory
cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
// Invoke kernel
int threadsPerBlock = 256;
int blocksPerGrid =
(N + threadsPerBlock - 1) / threadsPerBlock;
// Time measurement starts
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
cudaEventSynchronize(start);
for (int i = 0; i < 10000; i++)
{
VecAdd << <blocksPerGrid, threadsPerBlock >> >(d_A, d_B, d_C);
}
// Time measurement ends
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Time to generate: %3.8f ms\n", elapsedTime);
cudaEventDestroy(start);
cudaEventDestroy(stop);
// Copy result from device memory to host memory
// h_C contains the result in host memory
cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
// Free device memory
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
// Free host memory
free(h_A);
free(h_B);
free(h_C);
}
我使用Visual Studio 2013和CUDA toolkit 8.0,我的系统是带有GeForce 1080的64位Windows 10,驱动程序版本372.90。
修改 在阅读@talonmies的答案后,我将 N 更改为1000,内核函数更改为
__global__ void VecAdd(Real* A, Real* B, Real* C)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < N) {
Real a = A[i];
Real b = B[i];
Real c = 0.0f;
for (int j = 0; j < 100000; j++)
{
c += (a + b);
}
C[i] = c;
}
}
现在浮动版本(3700ms)比双版本(38570ms)快10倍左右,然而,它仍远离理论值32,有人可以解释一下吗? PS。它没有标志--device-debug,因为使用它时浮动版本要慢得多,而且最多只比双版本快两倍。
修改 @einpoklum,这是ptx文件。我不确定ptx文件的含义,但我认为内核中的循环没有被nvcc优化掉,因为如果我为浮点版本设置内核循环次数为10000,并且延迟变为390ms,那么&# 39; s约为循环数100000的延迟的十分之一。
//
// Generated by NVIDIA NVVM Compiler
//
// Compiler Build ID: CL-20732876
// Cuda compilation tools, release 8.0, V8.0.26
// Based on LLVM 3.4svn
//
.version 5.0
.target sm_20
.address_size 32
// .globl _Z6VecAddPfS_S_
.visible .entry _Z6VecAddPfS_S_(
.param .u32 _Z6VecAddPfS_S__param_0,
.param .u32 _Z6VecAddPfS_S__param_1,
.param .u32 _Z6VecAddPfS_S__param_2
)
{
.reg .pred %p<3>;
.reg .f32 %f<57>;
.reg .b32 %r<20>;
ld.param.u32 %r5, [_Z6VecAddPfS_S__param_0];
ld.param.u32 %r6, [_Z6VecAddPfS_S__param_1];
ld.param.u32 %r7, [_Z6VecAddPfS_S__param_2];
mov.u32 %r8, %ctaid.x;
mov.u32 %r9, %ntid.x;
mov.u32 %r10, %tid.x;
mad.lo.s32 %r1, %r8, %r9, %r10;
setp.gt.s32 %p1, %r1, 999;
@%p1 bra BB0_4;
cvta.to.global.u32 %r2, %r7;
cvta.to.global.u32 %r12, %r5;
shl.b32 %r13, %r1, 2;
add.s32 %r14, %r12, %r13;
cvta.to.global.u32 %r15, %r6;
add.s32 %r16, %r15, %r13;
ld.global.f32 %f5, [%r16];
ld.global.f32 %f6, [%r14];
add.f32 %f1, %f6, %f5;
mov.f32 %f56, 0f00000000;
mov.u32 %r19, 100000;
BB0_2:
add.f32 %f7, %f1, %f56;
add.f32 %f8, %f1, %f7;
add.f32 %f9, %f1, %f8;
add.f32 %f10, %f1, %f9;
add.f32 %f11, %f1, %f10;
add.f32 %f12, %f1, %f11;
add.f32 %f13, %f1, %f12;
add.f32 %f14, %f1, %f13;
add.f32 %f15, %f1, %f14;
add.f32 %f16, %f1, %f15;
add.f32 %f17, %f1, %f16;
add.f32 %f18, %f1, %f17;
add.f32 %f19, %f1, %f18;
add.f32 %f20, %f1, %f19;
add.f32 %f21, %f1, %f20;
add.f32 %f22, %f1, %f21;
add.f32 %f23, %f1, %f22;
add.f32 %f24, %f1, %f23;
add.f32 %f25, %f1, %f24;
add.f32 %f26, %f1, %f25;
add.f32 %f27, %f1, %f26;
add.f32 %f28, %f1, %f27;
add.f32 %f29, %f1, %f28;
add.f32 %f30, %f1, %f29;
add.f32 %f31, %f1, %f30;
add.f32 %f32, %f1, %f31;
add.f32 %f33, %f1, %f32;
add.f32 %f34, %f1, %f33;
add.f32 %f35, %f1, %f34;
add.f32 %f36, %f1, %f35;
add.f32 %f37, %f1, %f36;
add.f32 %f38, %f1, %f37;
add.f32 %f39, %f1, %f38;
add.f32 %f40, %f1, %f39;
add.f32 %f41, %f1, %f40;
add.f32 %f42, %f1, %f41;
add.f32 %f43, %f1, %f42;
add.f32 %f44, %f1, %f43;
add.f32 %f45, %f1, %f44;
add.f32 %f46, %f1, %f45;
add.f32 %f47, %f1, %f46;
add.f32 %f48, %f1, %f47;
add.f32 %f49, %f1, %f48;
add.f32 %f50, %f1, %f49;
add.f32 %f51, %f1, %f50;
add.f32 %f52, %f1, %f51;
add.f32 %f53, %f1, %f52;
add.f32 %f54, %f1, %f53;
add.f32 %f55, %f1, %f54;
add.f32 %f56, %f1, %f55;
add.s32 %r19, %r19, -50;
setp.ne.s32 %p2, %r19, 0;
@%p2 bra BB0_2;
add.s32 %r18, %r2, %r13;
st.global.f32 [%r18], %f56;
BB0_4:
ret;
}
答案 0 :(得分:5)
您显示的内核代码永远不会受到算术指令吞吐量的限制,因此永远不会在Pascal GPU上暴露算法指令吞吐量差异的单精度和双精度。
内存带宽更可能是该代码的性能限制(您的代码需要两个负载和每个FLOP存储)。您看到单精度和双精度之间的比率大约为2的原因可能是类型的大小比例,而不是更多。