CUDA by example Chapter10 流

基于WebGL架构的3D可视化平台—平面图导航(一) 前言 利用CampusBuilder来搭建自己的虚拟世界过程有这样一个问题:如何快速聚焦到虚拟场景的某一位置。当然我们可以创建几个按钮对应查找我们需要去的位置(参照物)并聚焦,但是按钮并不是很炫酷也不能很好的反馈给我们一些信息。接下来我们就用平面导航图来解决这一问题。 第一步:使用CampusBuilder搭建模拟场景,CampusBuilder操作简单,分分钟就可以上手。 ... 阅读详情

为什么要使用CUDA流

前面章节实现的GPU并行性都是把同一个任务分成可以并行执行的子任务,利用GPU中数量众多的cuda核来实现单个任务的并行性。而如果要实现任务并行性,就需要使用CUDA流来实现。

页锁存主机内存

1. 页锁存内存的概念及用途

  • 页所存内存也称为固定内存或者不可分页内存,它有一个重要属性:
    操作系统将不会对这块内存分页并交换到磁盘上,从而保证了该内存始终驻留在物理内存中。
  • 由于GPU知道主机内存的物理地址,因此可以通过DMA(Direct Memory Access)不需要CPU参与地在GPU和主机之间复制数据。正是因为CPU不参与数据复制,CPU很可能在DMA执行过程中移动可分页的数据,这就可能对DMA操作造成延迟。
    事实上,当使用可分页内存进行复制时,复制操作将执行两遍:第一遍从可分页内存复制到一块"临时的"页锁定内存,然后再从这个页锁定内存复制到GPU上。
  • 页锁定内存的使用将会在特定场合提高运行效率。
  • 注意:当使用固定内存时,将会失去虚拟内存的所有功能,即系统将会更快地耗尽内存资源,因为没有办法将内存交换到磁盘中。

2. 页锁存内存的分配
通过cudaHostAlloc()分配页锁定的主机内存,通过cudaFreeHost()释放页锁定内存

//cudaHostAlloc()前两个参数是页锁定内存的地址和分配的大小
//最后一个参数的取值范围是一组标志,可以通过这些标志来修改cudaHostAlloc()的行为cudaHostAllocDefault将获得默认行为
cudaHostAlloc((void** )&a, size*sizeof(*a), cudaHostAllocDefault);

//...执行一些复制操作

//释放页锁定内存
cudaFreeHost(a);
  1. 页锁定内存的测试
// 代码10.2页锁存主机内存
//时间:2019.07.30
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>

float cuda_malloc_test(int size, bool up)
{
	cudaEvent_t start, stop;
	int *a, *dev_a;
	float elapsedTime;

	cudaEventCreate(&start);
	cudaEventCreate(&stop);

	a = (int *)malloc(size*sizeof(*a));
	cudaMalloc((void **)&dev_a, size*sizeof(*dev_a));

	cudaEventRecord(start, 0);

	for (int i = 0; i < 100; i++)
	{
		if (up)
		{
			cudaMemcpy(dev_a, a, size*sizeof(*dev_a), cudaMemcpyHostToDevice);
		}
		else
		{
			cudaMemcpy(a, dev_a, size*sizeof(*a), cudaMemcpyDeviceToHost);
		}
	}

	cudaEventRecord(stop, 0);
	cudaEventSynchronize(stop);
	cudaEventElapsedTime(&elapsedTime, start, stop);

	free(a);
	cudaFree(dev_a);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	return elapsedTime;
}



float cuda_host_alloc_test(int size, bool up)
{
	cudaEvent_t start, stop;
	int *a, *dev_a;
	float elapsedTime;

	cudaEventCreate(&start);
	cudaEventCreate(&stop);

	cudaHostAlloc((void **)&a,size*sizeof(*a),cudaHostAllocDefault);
	cudaMalloc((void **)&dev_a, size*sizeof(*dev_a));

	cudaEventRecord(start, 0);

	for (int i = 0; i < 100; i++)
	{
		if (up)
		{
			cudaMemcpy(dev_a, a, size*sizeof(*dev_a), cudaMemcpyHostToDevice);
		}
		else
		{
			cudaMemcpy(a, dev_a, size*sizeof(*a), cudaMemcpyDeviceToHost);
		}
	}

	cudaEventRecord(stop, 0);
	cudaEventSynchronize(stop);
	cudaEventElapsedTime(&elapsedTime, start, stop);

	cudaFreeHost(a);
	cudaFree(dev_a);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	return elapsedTime;
}


#define SIZE 10*1024*1024
int main()
{
	float elapsedTime;
	float MB = (float)100 * SIZE*sizeof(int) / 1024 / 1024;

	elapsedTime = cuda_malloc_test(SIZE, true);
	printf("Time using cudaMalloc:%3.1f ms\n", elapsedTime);
	printf("\t MB/s during copy up:%3.1f M/s\n", MB / (elapsedTime / 1000));

	elapsedTime = cuda_malloc_test(SIZE, false);
	printf("Time using cudaMalloc:%3.1f ms\n", elapsedTime);
	printf("\t MB/s during copy down:%3.1f M/s\n", MB / (elapsedTime / 1000));

	elapsedTime = cuda_host_alloc_test(SIZE, true);
	printf("Time using cudaHostAlloc:%3.1f ms\n", elapsedTime);
	printf("\t MB/s during copy up:%3.1f M/s\n", MB / (elapsedTime / 1000));

	elapsedTime = cuda_host_alloc_test(SIZE, false);
	printf("Time using cudaHostAlloc:%3.1f ms\n", elapsedTime);
	printf("\t MB/s during copy down:%3.1f M/s\n", MB / (elapsedTime / 1000));

	system("pause");

}

在这里插入图片描述
发现使用页锁存内存对性能有明显的提升!

CUDA流

CUDA流表示一个GPU操作队列,并且该队列中的操作将以指定的顺序执行。可以在流中添加一些操作,例如核函数的启动、内存复制,以及事件的启动和结束等。将这些操作添加到流的顺序也就是它们的执行顺序。可以将每个流视为GPU上的一个任务,并且这些任务可以并行执行。

注意到,在第六章介绍cuda事件时,cudaEventRecord()一共有两个参数,第一个参数是要记录的事件,第二个参数用于指定插入事件的流。

cudaEvent_t start;
cudaEventCreate(&start);
cudaEventRecord(start,0);

使用单个CUDA流

1. 设备硬件要求
GPU必须支持设备重叠功能才能使用CUDA流。支持设备重叠功能的GPU能够在执行一个CUDA C核函数的同时,还能在设备与主机之间执行复制操作。我们最终将使用多个流来实现这种计算一数据传输的重叠,以提高运行效率。

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

2. 初始化流

	//初始化流
	cudaStream_t stream;
	cudaStreamCreate(&stream);//创建一个流

3. 用异步复制取代同步复制
cudaMencpy()将以同步的方式执行,当函数返回时,复制操作就已经完成。
而cudaMemcpyAsync()是CUDA提供的异步复制函数。在调用cudaMemcpyAsync()时,只是放置一个请求,表示在流中执行一次内存复制操作,这个流是通过参数stream来指定的。当函数返回时,我们无法确定复制操作是否已经启动,更无法保证它是否已经结束。任何传递给cudaMemcpyAsync()的主机内存指针都必须是通过cudaHostAlloc()分配的也锁定内存,即只能以异步方式对页锁定内存进行复制操作。

		cudaMemcpyAsync(dev_a, host_a + i, N*sizeof(int), cudaMemcpyHostToDevice, stream);
		cudaMemcpyAsync(dev_b, host_b + i, N*sizeof(int), cudaMemcpyHostToDevice, stream);

4. 用异步核函数调用取代同步调用

kernal << <N / 256, 256, 0, stream >> >(dev_a, dev_b, dev_c);

5.主机与GPU同步
主机在继续执行之前,要首先等待GPU执行完成。此时,可以调用cudaStreamSynchronize()并指定想要等待的流
6. 销毁对GPU操作进行排队的流
在退出应用程序之前,要调用cudaStreamDestroy()销毁对GPU操作进行排队的流。

cudaStreamDestroy(stream);

7. 完整代码
示例代码假设在这样的情形下运行:GPU内存远远少于主机内存,由于整个缓冲区无法一次性填充到GPU,因此需要分块进行填充,分块计算,分块取回。

// 代码10.4使用单个cuda流
//时间:2019.07.30
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>

#define N 1024*1024
#define FULL_DATA_SIZE 20*N

__global__ void kernal(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()
{
	cudaDeviceProp prop;
	int whichDevice;
	cudaGetDevice(&whichDevice);
	cudaGetDeviceProperties(&prop, whichDevice);
	if (!prop.deviceOverlap)
	{
		printf("Device will not handle overlap,so no speed up from streams\n");
		return 0;
	}

	cudaEvent_t start, stop;
	float elapsedTime;

	//启动计时器
	cudaEventCreate(&start);
	cudaEventCreate(&stop);
	cudaEventRecord(start, 0);

	//初始化流
	cudaStream_t stream;
	cudaStreamCreate(&stream);

	int *host_a, *host_b, *host_c;
	int *dev_a, *dev_b, *dev_c;
	//在GPU上分配内存
	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();
	}

	//分块计算,在整体数据上循环,每个数据块大小为N
	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);
		//执行核函数
		kernal << <N / 256, 256, 0, stream >> >(dev_a, dev_b, dev_c);

		//将数据从设备复制到锁定内存
		cudaMemcpyAsync(host_c + i, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost, stream);
	}

	//将GPU与主机同步
	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);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	system("pause");
	return 0;
}

在这里插入图片描述

使用多个CUDA流

我们用两个流**“分块”计算并且实现内存复制与核函数的重叠**。
在这里插入图片描述
在一些新的NVIDIA GPU中同时支持核函数和两次内存复制操作,一次是从主机到设备,另一次是从设备到主机。这样能使应用程序的整体性能获得再一步提升。

// 代码10.6使用多个cuda流
//时间:2019.07.30
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>

#define N 1024*1024
#define FULL_DATA_SIZE 20*N

__global__ void kernal(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()
{
	cudaDeviceProp prop;
	int whichDevice;
	cudaGetDevice(&whichDevice);
	cudaGetDeviceProperties(&prop, whichDevice);
	if (!prop.deviceOverlap)
	{
		printf("Device will not handle overlap,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;
	//在GPU上分配内存
	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();
	}

	//分块计算,在整体数据上循环,每个数据块大小为N
	for (int i = 0; i < FULL_DATA_SIZE; i += 2*N)
	{
		//第一个流
		cudaMemcpyAsync(dev_a0, host_a + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0);
		cudaMemcpyAsync(dev_b0, host_b + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0);
		//执行核函数
		kernal << <N / 256, 256, 0, stream0 >> >(dev_a0, dev_b0, dev_c0);
		//将数据从设备复制到锁定内存
		cudaMemcpyAsync(host_c + i, dev_c0, N*sizeof(int), cudaMemcpyDeviceToHost, stream0);


		//第二个流
		cudaMemcpyAsync(dev_a1, host_a + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1);
		cudaMemcpyAsync(dev_b1, host_b + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1);
		//执行核函数
		kernal << <N / 256, 256, 0, stream1 >> >(dev_a1, dev_b1, dev_c1);
		//将数据从设备复制到锁定内存
		cudaMemcpyAsync(host_c + i + N, dev_c1, N*sizeof(int), cudaMemcpyDeviceToHost, stream1);
	}

	//将GPU与主机同步
	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);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	system("pause");
	return 0;
}

在这里插入图片描述
实践表明,使用两个流确实比使用一个流效率更高。

GPU工作调度机制

程序员编程时可以将流视为有序的操作序列,其中既包含内存复制操作,又包含核函数调用。然而在硬件中并没有流的概念,而是包含一个或多个引擎来执行内存复制操作,以及一个引擎来执行核函数。这些引擎彼此独立地对操作进行排队。
将上一节两个流映射到GPU引擎:
在这里插入图片描述
注意到,在两个流的示例中,流队列的加入顺序为:

		//第一个流
		cudaMemcpyAsync(dev_a0, host_a + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0);
		cudaMemcpyAsync(dev_b0, host_b + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0);
		//执行核函数
		kernal << <N / 256, 256, 0, stream0 >> >(dev_a0, dev_b0, dev_c0);
		//将数据从设备复制到锁定内存
		cudaMemcpyAsync(host_c + i, dev_c0, N*sizeof(int), cudaMemcpyDeviceToHost, stream0);


		//第二个流
		cudaMemcpyAsync(dev_a1, host_a + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1);
		cudaMemcpyAsync(dev_b1, host_b + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1);
		//执行核函数
		kernal << <N / 256, 256, 0, stream1 >> >(dev_a1, dev_b1, dev_c1);
		//将数据从设备复制到锁定内存
		cudaMemcpyAsync(host_c + i + N, dev_c1, N*sizeof(int), cudaMemcpyDeviceToHost, stream1);

应用程序首先将第0个流的所有操作放入队列,然后是第1个流的所有操作。下图说明了流内部的依赖性,其中从复制操作到核函数的箭头表示:复制操作要等核函数执行完成之后才能开始。
在这里插入图片描述
在硬件上执行的时间线如下:
在这里插入图片描述
由于第0个流中将c复制回主机的操作要等待核函数执行完成,因此第一个流中将a和b复制到GPU的操作虽然是完全独立的,但却被阻塞了。

高效地使用多个CUDA流

如果同时调度某个流的所有操作(即按照流深度优先的方式调用),那么很容易在无意中阻塞另一个流的复制操作或者核函数执行。要解决这个问题,在将操作放入流的队列时应该采用宽度优先方式,而非深度优先方式。
流的深度优先是指:先添加第0个流的所有操作(a复制,b复制,核函数,c复制),再添加第1个流的所有操作。
流的广度优先是指:将a复制添加到第0个流,将a复制添加到第1个流;将b复制添加到第0个流,将b复制添加到第1个流;将核函数添加到第0个流,将核函数添加到第1个流;将c复制添加到第0个流,将c复制添加到第1个流。
代码修改为:

		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);
		//执行核函数
		kernal << <N / 256, 256, 0, stream0 >> >(dev_a0, dev_b0, dev_c0);
		kernal << <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);

新的硬件上执行时间线如下:
在这里插入图片描述

完整代码:

// 代码10.7高效地使用多个cuda流
//时间:2019.07.30
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <iostream>

#define N 1024*1024
#define FULL_DATA_SIZE 20*N

__global__ void kernal(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()
{
	cudaDeviceProp prop;
	int whichDevice;
	cudaGetDevice(&whichDevice);
	cudaGetDeviceProperties(&prop, whichDevice);
	if (!prop.deviceOverlap)
	{
		printf("Device will not handle overlap,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;
	//在GPU上分配内存
	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();
	}

	//分块计算,在整体数据上循环,每个数据块大小为N
	for (int i = 0; i < FULL_DATA_SIZE; i += 2 * N)
	{
		
		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);
		//执行核函数
		kernal << <N / 256, 256, 0, stream0 >> >(dev_a0, dev_b0, dev_c0);
		kernal << <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);
		
	}

	//将GPU与主机同步
	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);
	cudaEventDestroy(start);
	cudaEventDestroy(stop);

	system("pause");
	return 0;
}

在这里插入图片描述
实际代码测试注意到,宽度优先的效率不及深度优先,个人猜想这可能是新版本的CUDA对其做了相关优化。

matlab数学建模练习(2)练习电子科技大学数学实验 【代码】matlab数学建模练习(2)练习电子科技大学数学实验。 阅读详情

相关推荐

6种常用数据分析方法详解:从描述统计到时间序列

数据分析是连接数据与业务决策的桥梁。面对海量数据,掌握描述性统计与对比分析可快速了解现状与差距;相关分析与回归分析能揭示变量间的关联与影响大小;聚类分析用于用户分群,时间序列分析则预测未来走势。这些方法广泛应用于用户画像、销量预测、活动效果评估等场景,是数据分析师和运营人员必备的技能。系统掌握这6种核心方法的使用场景、原理逻辑与实操步骤,能够帮助读者建立完整分析框架,真正用数据驱动业务增长。

weixin_34405354的博客 335

CUDA By Example(八)——流

CUDA 流操作,使用Stream加速程序执行

hhhcbw的博客,欢迎各位来访 2823

运放+三极管恒流源电路设计实战:从原理到仿真一步到位(附Multisim文件)

本文详细解析了运放与三极管构建恒流源电路的核心原理,重点阐述了负反馈机制如何实现精准的电流控制。通过Multisim仿真实战,从电路搭建、参数计算到负载调整率与动态响应分析,一步步指导设计过程,并提供常见故障的调试技巧与PCB设计要点,帮助工程师从理论平稳过渡到可靠实践。

7z8x2c4v6b9的博客 711

《CUDA By Example》【Chapter 10】流?

10.1 概述 并行性分数据并行性和任务并行性。GPU是SIMD天生的数据并行,同时也能支持类似于CPU多线程应用程序中的任务并行性。 GPU任务并行性不像CPU上那样灵活,但仍然可以进一步提高GPU上的运行速度。本章,介绍CUDA流,以及如何通过流在GPU上同时执行多个任务。 了解如何分配也锁定(Page-Locked)类型的主机内存 了解CUDA流的概念 了解如何使用CUDA流...

wondervictor的博客 324

AGM Supra软件从零到一:国产CPLD工程创建全流程拆解

本文详细拆解了AGM Supra软件从零开始创建国产CPLD工程的全流程,包括开发环境配置、工程创建、Quartus项目转换、TCL脚本执行与编译等关键步骤。特别强调了中文路径避坑、器件型号选择等实用技巧,帮助开发者高效完成CPLD开发任务。

weixin_29158413的博客 410

GD32单片机引脚复用指南:如何让JTAG接口的PA15/PB3发挥第二功能?

本文详细介绍了GD32F103Cx系列单片机如何通过AFIO模块进行引脚复用,特别是将默认用于JTAG调试接口的PA15、PB3等引脚释放为普通IO或复用功能引脚。文章从原理出发,结合代码实践,讲解了如何配置调试端口重映射,将JTAG引脚安全地转换为GPIO或定时器通道,并提供了常见问题排查与最佳实践,帮助开发者高效利用有限的引脚资源。

nept的博客 810

CUDA多个流的使用

CUDA中使用多个流并行执行数据复制和核函数运算可以进一步提高计算性能。以下程序使用2个流执行运算: #include "cuda_runtime.h" #include #include #include #define N (1024*1024) #define FULL_DATA_SIZE N*20 __global__ voi

牧野的博客 1万+

GD32F103入门实践(一)配置环境,搭建工程模板,烧写程序

写在前面 本系列博客是我初学GD32F103的操作实录,我个人是个小白,在学习过程中真的也遇到了不少坑,因此我希望能记录下来,帮助到同样踩坑的你。 我的芯片是GD32F103ZE,软件是Keil,烧写用的是JLink。 材料准备 首先我在兆易创新官网(兆易创新),下载相关的文件。点击资料下载,在搜索框里输入“GD32F10”可以得到相关的资料,其他芯片类似。比较重要的是GD32F10x AddOn和GD32F10x Firmware Library(固件库),这两个必须下载。 下载Keil MDK5,这

qq_41208487的博客 3万+

CUDA by example Chapter3 CUDA C简介

CUDA C简介CPU上的Hello WorldGPU上的Hello World__global__修饰符:向kernal()中传递参数cudaMalloc()函数:cudaFree()函数:cudaMemcpy()函数:查询设备信息cudaGetDeviceCount()函数:结构体cudaDevicePropcudaGetDeviceProperties()函数:设备属性的使用:选择一个符合条...

wolfcsharp的博客 394

OpenCV-Python实战(1)——OpenCV简介与图像处理基础

OpenCV 是一个的跨平台计算机视觉库,可以运行在 Linux、Windows 和 Mac OS 操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时也提供了 Python 接口,实现了图像处理和计算机视觉方面的很多通用算法。在本文中,将介绍 OpenCV 库,包括它的主要模块和典型应用场景,同时使用 OpenCV-Python 实战讲解图像处理基础要点。

盼小辉丶的博客 27万+

Qt —— 在Win11下“Qt for Android“开发环境,通过USB在真机(华为Meta60 PRO)上调试运行,并打包APK并在真实手机运行

Qt —— 在Win11下“Qt for Android“开发环境,打包APK并在真实手机运行

315

CUDA by example Chapter5 线程协作

线程协作并行线程块的分解使用线程实现GPU上的矢量求和 并行线程块的分解 使用线程实现GPU上的矢量求和

wolfcsharp的博客 388

深度拆解 ‘High-Frequency Trading’ (HFT) 系统:解析如何在 100 纳秒内完成从网卡到 C++ 策略的响应

在100纳秒内完成从网卡到C++策略的响应,这是一项对技术栈每一个层面都进行极限优化的系统工程。它要求我们深入理解计算机体系结构、操作系统、网络协议和C++语言的底层机制。从物理距离的极致缩短,到高性能网卡和内核旁路技术,再到C++代码中每一个字节、每一个CPU周期的精打细算,HFT系统展现了人类对计算速度的永恒追求。这项技术不仅仅应用于金融交易,其背后的低延迟理念和实现方法,如DPDK、无锁编程、硬件加速等,正在逐渐渗透到其他对延迟敏感的领域,例如电信网络、边缘计算和工业自动化。

weixin_41455464的博客 1039

CUDA by example Chapter4 CUDA C并行编程

CUDA C 并行编程CPU矢量求和运算GPU矢量求和运算尖括号语法<<CPU矢量求和运算 /* //代码4.2.1.1基于CPU的矢量求和运算 //时间:2019.07.20 */ #include "cuda_runtime.h" #include "device_launch_parameters.h" #include <iostream> #define N 1...

wolfcsharp的博客 369

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

《CUDA By Example》【Chapter 05】线程协作 ?

概述 本章介绍代码在各个并行副本之间的通信和协作。 1,了解不同线程之间的通信机制; 2,了解并行执行线程的同步机制; 5.2 并行线程块的分解 add&lt;&lt;&lt;N,1&gt;&gt;&gt;( dev_a, dev_b, dev_c); //第一个参数为想要启动的线程块数量; //第二个参数为CUDA运行时在每个线程块中创建的线程数量; //共创建了N个线程块,N*1...

wondervictor的博客 294

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

CUDA by example Chapter9 原子性

原子性在CPU上计算直方图在GPU上计算直方图一些重要函数及技巧使用GPU全局内存原子操作计算直方图使用GPU共享内存原子操作计算直方图 与在操作系统中的线程竞争相似,GPU编程各个线程之间也存在竞态条件,为了保证程序按照我们设计的逻辑运行,原子性操作必不可少。 本章以计算直方图为例,说明在GPU上使用原子操作的方法。 在CPU上计算直方图 这是平凡的 // 代码9.4.1在CPU上计算直方图 /...

wolfcsharp的博客 292

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

《CUDA By Example》【Chapter 04】CUDA C 并行编程

GPU计算的应用前景在很大程度上取决于能否从很多问题中发掘出大规模并行性。 add_loop_cpu.cu #include &amp;amp;amp;amp;quot;../common/book.h&amp;amp;amp;amp;quot; #define N 10 void add( int *a, int *b, int *c ) { int tid = 0; // this is CPU zero, so we start at ze...

wondervictor的博客 299

《CUDA By Example》【Chapter 06】常量内存与事件 ?

6.1 本章目标 高级些的功能,通过GPU上特殊的内存区域来加速应用程序的执行。其中一种内存区域:常量内存(Constant Memory)。介绍一种增强CUDA C应用程序性能的方法,了解如何通过事件来测量应用程序的性能。 如何在CUDA C中使用常量内存 了解常量内存的性能性能 学习使用CUDA事件来测量应用程序的性能 6.2 常量内存 由于GPU有很多ALU, 所以计算能力往...

wondervictor的博客 330

CUDA by example Chapter7 纹理内存

纹理内存纹理内存简介热传导模拟简单的传热模型代码中温度更新的计算GPU不使用纹理内存的热传导模拟计算GPU使用一维纹理内存的热传导模拟计算GPU使用二维纹理内存的热传导模拟计算 纹理内存简介 和常量内存一样,纹理内存是另一种类型的只读内存。纹理内存是专门为那些在内存访问模式中存在大量空间局部性的图形应用程序而设计的。假如在某个计算应用程序中,一个线程读取的位置与邻近线程读取的位置"非常接近",使用...

wolfcsharp的博客 599

《CUDA By Example》【Chapter 07】纹理内存 ?

7.1 本章目标 纹理内存和常量内存一样,都是只读内存,用来减少带宽流量,提升性能。 1,了解纹理内存的特性 2,了解如何在CUDA C中使用一维纹理内存 3,了解如何在CUDA C中使用二维纹理内存 7.2 纹理内存简介(Texture Memory) 7.3 热传导模拟...

wondervictor的博客 357

CUDA by example Chapter11 多GPU系统上的CUDA C

多GPU系统上的CUDA C零拷贝主机内存通过零拷贝内存实现点积运算零拷贝内存的性能使用多个GPU 零拷贝主机内存 零拷贝主机内存是另一种固定内存,它也不能被CPU从物理内存交换出去或者重新定位。同时,这种内存除了可以用于主机与GPU之间的内存复制外,还可以在CUDA C核函数中直接访问这种类型的主机内存。由于这种内存不需要复制到GPU,因此也称为零拷贝内存。 通过零拷贝内存实现点积运算 1. 测...

wolfcsharp的博客 400

《CUDA By Example》【Chapter 11】多GPU系统上的CUDA C?

11.1 概述 了解如何分配和使用零拷贝内存(zero-copy memory) 了解如何在同一个应用程序中使用多个GPU 了解如何分配和使用可移动的固定内存(Portable pinned Memory) 11.2 零拷贝主机内存 固定内存(页锁定内存)能确保不会交换出物理内存。我们通过调用cudaHostAlloc()来分配这种内存,并且传递参数cudaHostAllocDefa...

wondervictor的博客 613

《CUDA By Example》【Chapter 09】原子性?

9.1 概述 了解不同NVIDIA GPU的计算功能集 了解原子操作以及为什么需要使用 了解如何在CUDA C核函数中执行带有原子操作的运算 9.2 计算功能集 不同架构的CPU有不同的功能和指令集(如MMX,SSE,SSE2),对于CUDA支持的GPU也一样。NVIDIA将GPU支持的各种功能统称为计算功能集(Compute Capability)。 9.2.1 NVIDIA...

wondervictor的博客 365

《CUDA By Example》【Chapter 03】CUDA C简介

3.2 第一个CUDA程序 simple_kernel.cu #include &amp;amp;quot;../common/book.h&amp;amp;quot; __global__ void kernel( void ) { } int main( void ) { kernel&amp;amp;amp;lt;&amp;amp;amp;lt;&amp;amp;amp;lt;1,1&amp;amp;amp;gt;&amp;amp

wondervictor的博客 362

CUDA by example Chapter6 常量内存与事件

常量内存与事件常量内存光线跟踪在GPU上实现光线追踪通过常量内存来实现光线追踪使用事件来测量性能 常量内存 使用常量内存的原因: GPU中含有数量庞大的计算单元,因此性能瓶颈通常并不在于芯片的数学计算吞吐量,而是在于芯片的内存带宽。 在某些情况下,使用常量内存可以显著减少内存通信量 光线跟踪 光信跟踪是从三维对象场景中生成二维图像的一种方式。将跟踪从像素中投射出的光线穿过场景,直到光线命中某个物体...

wolfcsharp的博客 536

《CUDA By Example》【Chapter 08】图形互操作性?

8.1 本章目的 本书重点为通用计算。GPU的成功要归功于实时计算复杂的渲染任务。能否同一个应用程序中GPU既执行渲染计算,又执行通用计算? CUDA C支持通用计算与渲染模式之间的互操作,而且非常容易实现。CUDA C可以无缝的与OpenGL和DirectX这两种实时渲染API进行交互。 目标: 什么是图形互操作性 如何设置某个CUDA设备的图形互操作性 如何在CUDA ...

wondervictor的博客 369
上一篇: CUDA by example Chapter9 原子性
下一篇: VS2013 编译安装openssl及工程配置
Leo-Ma
博客等级 码龄11年 1701粉丝 124原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值