关于stream使用的疑问

为了更好的优化算法,我现在在学习stream的使用,参照的是《CUDA范例精解》。书上给出的代码如下,书上所用的显卡是GTX285,所用的时间为48ms;而我所用的显卡是GTX 580,所用的时间是1200-1300ms之间,为什么同样的程序会差这么多的时间?而且我的卡要优于书中所用的卡,不能理解。


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <stdlib.h>
#define N (1024*1024)
#define FULL_DATA_SIZE (N*20)

__global__ void kernel(int *a,int *b,int *c)
{
int idx=threadIdx.x+blockIdx.x*blockDim.x;
if(idx<N)
{
int idx1=(idx+1)%256;
int idx2=(idx+2)%256;
float as=(a[idx]+a[idx1]+a[idx2])/3.0f;
float bs=(b[idx]+b[idx1]+b[idx2])/3.0f;
c[idx]=(as+bs)/2;
}
}

int main(void)
{
cudaDeviceProp prop;
int whichDevice;
cudaGetDevice(&whichDevice);
cudaGetDeviceProperties(&prop,whichDevice);
if(!prop.deviceOverlap)
{
printf("Device will not handle overlaps,so no speed up from streams\n");
return 0;
}

cudaEvent_t start,stop;
float elapsedTime;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start,0);

cudaStream_t stream0,stream1;
cudaStreamCreate(&stream0);
cudaStreamCreate(&stream1);

int *host_a,*host_b,*host_c;
int *dev_a0,*dev_b0,*dev_c0;
int *dev_a1,*dev_b1,*dev_c1;
cudaMalloc((void**)&dev_a0,N*sizeof(int));
cudaMalloc((void**)&dev_b0,N*sizeof(int));
cudaMalloc((void**)&dev_c0,N*sizeof(int));
cudaMalloc((void**)&dev_a1,N*sizeof(int));
cudaMalloc((void**)&dev_b1,N*sizeof(int));
cudaMalloc((void**)&dev_c1,N*sizeof(int));

cudaHostAlloc((void**)&host_a,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);
cudaHostAlloc((void**)&host_b,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);
cudaHostAlloc((void**)&host_c,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);

for(int i=0;i<FULL_DATA_SIZE;i++)
{
host_a[i]=rand();
host_b[i]=rand();
}

for(int i=0;i<FULL_DATA_SIZE;i+=N*2)
{
cudaMemcpyAsync(dev_a0,host_a+i,N*sizeof(int),cudaMemcpyHostToDevice,stream0);
cudaMemcpyAsync(dev_a1,host_a+i+N,N*sizeof(int),cudaMemcpyHostToDevice,stream1);
cudaMemcpyAsync(dev_b0,host_b+i,N*sizeof(int),cudaMemcpyHostToDevice,stream0);
cudaMemcpyAsync(dev_b1,host_b+i+N,N*sizeof(int),cudaMemcpyHostToDevice,stream1);

kernel<<<N/256,256,0,stream0>>>(dev_a0,dev_b0,dev_c0);
kernel<<<N/256,256,0,stream1>>>(dev_a1,dev_b1,dev_c1);

cudaMemcpyAsync(host_c+i,dev_c0,N*sizeof(int),cudaMemcpyDeviceToHost,stream0);
cudaMemcpyAsync(host_c+i+N,dev_c1,N*sizeof(int),cudaMemcpyDeviceToHost,stream1);
}
cudaStreamSynchronize(stream0);
cudaStreamSynchronize(stream1);
cudaEventRecord(stop,0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&elapsedTime,start,stop);
printf("Time taken: %3.1f ms\n",elapsedTime);
cudaFreeHost(host_a);
cudaFreeHost(host_b);
cudaFreeHost(host_c);
cudaFree(dev_a0);
cudaFree(dev_b0);
cudaFree(dev_c0);
cudaFree(dev_a1);
cudaFree(dev_b1);
cudaFree(dev_c1);

cudaStreamDestroy(stream0);
cudaStreamDestroy(stream1);

return 0;
}


[i][i]

[/i][/i]

楼主您好!

根据您所提供的在1#处的信息,我认为:
如下原因会导致您的可能的性能的下降。

(1)书上是O2之类的优化参数,而您可能不是。
(2)2.x+的默认除法精度比1.x高,计算路径默认较为长而且复杂。
(3)2.0不擅长于浮点数除法,在1.x, 2.0, 2.1, 3.x中进行比较,2.0是float除法性能最差的一个(折算到同频同SP数)。
(4)您的对流发布命令的方式,对2.x来说,可能无法充分让计算和传输同时进行。

对您的建议:
检查是否当前是release编译(假设您是用的VS), 或者检查是否启用O2编译选项。
并 尝试使用–prec-div=false或者尝试考虑-use_fast_math选项。
并 尝试将2个流里的顺序做如下调整:流1传输到device, 流1中的计算,流1中的从device取回,流2中的传输到device, 流2中的计算,流2中的从device取回。
(以及可选的您可以考虑加大到至少3个流(不算默认流))。

请阅读以上可能的原因分析,
以及 请尝试以上建议,看看有无时间改善。

以及 欢迎提供更多的信息。

此外,为了避免其他不相干因素的影响,

还请优先考虑:将您的cudaEventRecord(start,0);放置到您的干活的for(int i=0;i<FULL_DATA_SIZE;i+=N*2)循环前面。

请先尝试这个建议。然后依次尝试2#建议。

楼主还在么?

楼主效果如何?

上文的实验做了么?优化打开了么?以及-use_fast_math等尝试了么?

其实你这个具体例子应该很容易看到改善。不需要精度的话,除以常数3.0f可以直接被优化为乘以0.3333333…。而乘法是全速的,应该较为明显。

当然。。还是你要试试。不实验终究只是纸上谈兵。
不过,这些只是建议,楼主完全有不采纳的自由。

1.首先,对于您提到的O2,-use_fast_math,release模式,我同时启用了,发现速度确实比我之前快了一些,但是提速不明显,速度在970-1000ms之间。
2.我将cudaEventRecord(start,0);放在for(int i=0;i<FULL_DATA_SIZE;i+=N*2)之前,速度确实提高了很多,52ms左右,一下子跟书上的时间接近了;
3.我将compute_20,sm_20改为compute_13,sm_13,速度又提高了一点儿,在47ms左右,有时候会到49.5ms,但是都没有超过50ms。

1.对于您提到的“流1传输到device, 流1中的计算,流1中的从device取回,流2中的传输到device, 流2中的计算,流2中的从device取回。”,这是书上最初的例子,这个速度是没有我上传的这种顺序快的~
2.您提到的“尝试使用–prec-div=false”,这个怎么用啊?我没有找到到哪里可以设置。
3.顺便说一句我现在下载了CUDA 5.0,是不是4.0,4.1,4.2都可以删除了?5.0的速度又快一点儿吗?

(1)首先恭喜楼主从1200ms提速到了~1000ms.
(2)再次恭喜楼主提速到了~50ms,。
并且恭喜楼主找到了原因,您的代码需要一定的进行模块载入/函数查找的时间,请不要将他们计入。
(3)恭喜楼主通过使用compute_13,来强制要求低精确度的计算,从而进一步提速~47ms.

(4)如何安排流中的指令,只是我的个人建议,楼主认为不好/不喜欢或者楼主家的宠物狗不喜欢均可以不采纳。
(5)-use_fast_math=true可以通过使用鼠标的右键在您的项目属性里设置。
(6)对与您的“随便说一句…"的问题,我表示,您可以保留4.0,4.1,4.2也可以删除它们。如果您不能决定是否需要保留他们,我建议您投掷硬币,并根据硬币的正反面来决定是否从硬盘上清除它们。
(7)对5.0的toolkit编译质量以及由此导致速度的提升或者下降,我表示无法提供参考评价。请考虑询问本版的原厂NVIDIA工程师。

恭喜楼主知道了速度还行(~50ms, 当从正确的时刻开始记录的时候)。
建议楼主阅读如上回复。

感谢您莅临CUDAZone China,
祝您周末愉快!

强调一下上文的(5), 当您使用了use_fast_math的时候,您的prec-div会被自动关闭。您无需单独设置。

好的,use_fast_math我知道在哪里,已经修改过了,但是prec-div没有找到在什么地方?

还有一个疑问,为什么我使用两个流,与使用一个流相比,速度基本没有提高呢?下面是我只用一个流的代码。我的运行结果显示所用时间基本一样。


__global__ void kernel(int *a,int *b,int *c)
{
	int idx=threadIdx.x+blockIdx.x*blockDim.x;
	if(idx<N)
	{
		int idx1=(idx+1)%256;
		int idx2=(idx+2)%256;
		float as=(a[idx]+a[idx1]+a[idx2])/3.0f;
		float bs=(b[idx]+b[idx1]+b[idx2])/3.0f;
		c[idx]=(as+bs)/2;
	}
}

int main(void)
{
	cudaDeviceProp prop;
	int whichDevice;
	cudaGetDevice(&whichDevice);
	cudaGetDeviceProperties(&prop,whichDevice);
	if(!prop.deviceOverlap)
	{
		printf("Device will not handle overlaps,so no speed up from streams\n");
		return 0;
	}

	cudaEvent_t start,stop;
	float elapsedTime;
	cudaEventCreate(&start);
	cudaEventCreate(&stop);
	
	cudaStream_t stream;
	cudaStreamCreate(&stream);

	int *host_a,*host_b,*host_c;
	int *dev_a,*dev_b,*dev_c;
	cudaMalloc((void**)&dev_a,N*sizeof(int));
	cudaMalloc((void**)&dev_b,N*sizeof(int));
	cudaMalloc((void**)&dev_c,N*sizeof(int));

	cudaHostAlloc((void**)&host_a,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);
	cudaHostAlloc((void**)&host_b,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);
	cudaHostAlloc((void**)&host_c,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault);

	for(int i=0;i<FULL_DATA_SIZE;i++)
	{
		host_a[i]=rand();
		host_b[i]=rand();
	}
   cudaEventRecord(start,0);
	for(int i=0;i<FULL_DATA_SIZE;i+=N)
	{
		cudaMemcpyAsync(dev_a,host_a+i,N*sizeof(int),cudaMemcpyHostToDevice,stream);
		cudaMemcpyAsync(dev_b,host_b+i,N*sizeof(int),cudaMemcpyHostToDevice,stream);
		kernel<<<N/256,256,0,stream>>>(dev_a,dev_b,dev_c);
		cudaMemcpyAsync(host_c+i,dev_c,N*sizeof(int),cudaMemcpyDeviceToHost,stream);
	}
	cudaStreamSynchronize(stream);
	cudaEventRecord(stop,0);
	cudaEventSynchronize(stop);
	cudaEventElapsedTime(&elapsedTime,start,stop);
	printf("Time taken:  %3.1f ms\n",elapsedTime);
	cudaFreeHost(host_a);
	cudaFreeHost(host_b);
	cudaFreeHost(host_c);
	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_c);

	cudaStreamDestroy(stream);

	return 0;
}

楼主您好,当您看到上文,并且当您已经使用了-use_fast_math,您已经无需指定(阐述于8#过一次)–prec-div=false了。因为已经隐含了。此时后,对您的这里例子,您的除以常数3.0f会被优化为乘法。

如果您依然强烈要求如何手工指定,您可以在您的.cu的属性页的CUDA C/C++的Command Line选项的那个大空白框里,输入–prev-div=false。(或者其他的您喜欢的选项,例如–ice=home, 前提是您知道该输入什么,并且他们可以被nvcc接受.)

感谢您的来访,
祝您周末愉快!

嗯,知道了,O(∩_∩)O谢谢~这个周末还是很忙的,明天要汇报,导师昨天才通知,晚上要加班做PPT呀~

因为您的这个问题已经超出了我的能力。我无法为您回答。建议其他版主/会员回答。

OK.非常感谢您的耐心回答,周末愉快!

for(int i=0;i<FULL_DATA_SIZE;i+=N2)
{
cudaMemcpyAsync(dev_a0,host_a+i,N
sizeof(int),cudaMemcpyHostToDevice,stream0);
cudaMemcpyAsync(dev_a1,host_a+i+N,Nsizeof(int),cudaMemcpyHostToDevice,stream1);
cudaMemcpyAsync(dev_b0,host_b+i,N
sizeof(int),cudaMemcpyHostToDevice,stream0);
cudaMemcpyAsync(dev_b1,host_b+i+N,N*sizeof(int),cudaMemcpyHostToDevice,stream1);

kernel<<<N/256,256,0,stream0>>>(dev_a0,dev_b0,dev_c0);
kernel<<<N/256,256,0,stream1>>>(dev_a1,dev_b1,dev_c1);

cudaMemcpyAsync(host_c+i,dev_c0,Nsizeof(int),cudaMemcpyDeviceToHost,stream0);
cudaMemcpyAsync(host_c+i+N,dev_c1,N
sizeof(int),cudaMemcpyDeviceToHost,stream1);
}
如果你使用1.3的计算能力的话,上面代码很有可能没有异步执行,建议使用visual profiler看看时间轴。

好的。但是我设置成2.0,依然没有感觉速度又改善~

LZ这里分配的block数量远远高于580中sm的数量,即使kernel同步执行了可能也会延长每个kernel的运行时间,在这种情况下没有加速效果是很正常的现象。只有在一个kernel不能将gpu用满的情况下两个流才能带来效果的提升。

你看错了楼主的意图了。

楼主的意思是让他/她的传输和kernel同时进行。而不是让多个kernel同时进行。楼主想节省的是这个时间。

嗯,是的。