Essas coisas sobre programação CUDA naqueles anos (3)

1. Visão Geral

Os dois artigos anteriores introduziram os conceitos básicos de CUDA, bem como operações simples de soma em arrays e matrizes:

Essas coisas sobre a programação CUDA naqueles anos (1)
Essas coisas sobre a programação CUDA naqueles anos (dois)

Um dos locais mais utilizados do CUDA é no processamento de imagens bidimensionais, como extração de recursos, correspondência estéreo, reconstrução tridimensional, treinamento e detecção de aprendizado profundo e assim por diante. Portanto, aqui está um exemplo simples para mostrar como o CUDA atinge o objetivo de processar imagens por meio da combinação de blocos de threads e threads:

Finalidade: Divida uma imagem de canal único de 8000*1000 em blocos de 40x40 (ajustáveis), calcule a soma, máximo, mínimo e média dos valores de pixel em cada bloco e salve os resultados do cálculo no lado da CPU.

A descrição acima e os procedimentos a seguir referem-se a: este blog

2. Etapas de implementação

Como o número máximo de threads em cada bloco de thread é 1024, considera-se concluir dois cálculos (cada cálculo é 40 * 20), usar a memória compartilhada para salvar os dados de pixel da imagem recebida em cada bloco e, finalmente, usar return O o algoritmo de redução otimiza a adição.

2.1 Insira uma imagem de canal único de 8000*1000 usando o OpenCV

O que? Nenhuma imagem de canal único de 8000 * 1000? Basta usar a função diretamente resize.

	Mat image = imread("2.jpg", 0);   //读取待检测图片,0表示以灰度图读入
	cv::resize(image, image, cv::Size(1000, 8000));

2.2 Alocando memória para arrays CUDA

Incluindo os dados da imagem a serem processados, os valores máximo e mínimo de pixel de cada bloco após o processamento e a soma dos valores de pixel no bloco. A implementação específica é a seguinte:

	//图像的所有字节数
	size_t memSize = image.cols*image.rows * sizeof(uchar);
	int size = 5000 * sizeof(int);

	//分配内存:GPU图像数据、求和结果数组、最大值结果数组、最小值结果数组
	uchar *d_src = NULL;
	int *d_sum = NULL;
	int *d_max = NULL;
	int *d_min = NULL;
	cudaMalloc((void**)&d_src, memSize);
	cudaMalloc((void**)&d_sum, size);
	cudaMalloc((void**)&d_max, size);
	cudaMalloc((void**)&d_min, size);

	//图像数据拷贝到GPU
	cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);

2.3 Alocar threads e threads, executar funções do kernel

Esta parte primeiro apresenta o processo de execução geral da função do kernel e, em seguida, apresenta o processo de implementação específico da função do kernel em detalhes na próxima seção, que também é a parte principal deste artigo.

	//图像宽、高
	int imgWidth = image.cols;
	int imgHeight = image.rows;

	//dim3 threadsPerBlock(20, 40); //每个block大小为20*40,对应matSum2核函数
	dim3 threadsPerBlock(40, 20); //每个block大小为40*20,对应matSum核函数

	dim3 blockPerGrid(25, 200); //将8000*1000的图片分为25*200个小图像块

	double time0 = static_cast<double>(getTickCount()); //计时器开始
	matSum << <blockPerGrid, threadsPerBlock, 3200 * sizeof(int) >> > (d_src, d_sum, d_max, d_min, imgHeight, imgWidth);

	//等待所有线程执行完毕
	cudaDeviceSynchronize();

	time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束
	cout << "The Run Time is :" << time0 << "s" << endl; //输出运行时间

Existem dois métodos de alocação de thread no programa acima, ambos podem alcançar funções correspondentes, mas os métodos de execução de thread são ligeiramente diferentes. Coloque-o aqui primeiro e discuta-o em detalhes mais tarde.

Além disso, este parâmetro também está envolvido no programa acima 3200 * sizeof(int). O significado específico deste parâmetro refere-se ao tamanho do byte da memória compartilhada entre cada bloco de encadeamento durante a execução da função do kernel. 共享内存:简而言之就是线程块内部各个线程都可以共同使用的内存。Saber disso é o suficiente para este artigo.

O método de declaração da memória compartilhada é estático e dinâmico. declarado estaticamente como:

//声明一个二维浮点数共享内存数组
__shared__ float a[size_x][size_y];

O size_x e size_y aqui são o mesmo que declarar um array c++.Se for um número determinado em tempo de compilação, não pode ser uma variável.

A declaração dinâmica é:

extern __shared__ int tile[];

Observe que as declarações dinâmicas suportam apenas matrizes unidimensionais. Para obter mais informações sobre memória compartilhada, consulte este blog .

2.4 Saída de resultados e fim do programa

Ele emite principalmente o resultado do CUDA para a CPU, depois libera a memória, redefine o CUDA e finaliza o programa.

	//将数据拷贝到CPU
	cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);

	//输出
	cout << "The sum is :" << sum[0] << endl;
	cout << "The max is :" << max[0] << endl;
	cout << "The min is :" << min[0] << endl;

	//释放内存
	cudaFree(d_src);
	cudaFree(d_sum);
	cudaFree(d_max);
	cudaFree(d_min);

	//重置设备
	cudaDeviceReset();

3. O processo de implementação específico da função kernel

Esta seção é a parte mais crítica da execução do algoritmo e o núcleo da aceleração CUDA. O processo de execução da função do kernel é: quando a função do kernel é iniciada, todos os blocos de encadeamento são executados de forma síncrona (não necessariamente totalmente síncrona) e cada bloco de encadeamento é executado independentemente sem afetar um ao outro, mas cada encadeamento dentro do bloco de encadeamento pode realizar interação de dados .

3.1 Definir memória compartilhada

A interação de dados de cada thread no bloco de threads é realizada através da memória compartilhada, de modo a realizar o valor máximo, o valor mínimo e a operação de soma.

	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

3.2 Calcule o índice correspondente a cada thread na imagem

Ele inclui os três aspectos a seguir:
1. Calcular o valor do índice absoluto do bloco de rosca na imagem.
2. O valor de índice absoluto do encadeamento no bloco de encadeamento na imagem.
3. O valor do índice do encadeamento no bloco de encadeamento.

	//根据线程块和线程的索引, 依次对应到图像数组中
	//1、线程块在图像中的索引值
	int blockindex1 = blockIdx.x*blockDim.x + 2*blockIdx.y*blockDim.y*imgWidth;
	int blockindex2 = blockIdx.x*blockDim.x + (2*blockIdx.y*blockDim.y + blockDim.y)*imgWidth;

	//2、线程块中的线程在图像中的索引值
	int index1 = threadIdx.x + threadIdx.y*imgWidth + blockindex1;
	int index2 = threadIdx.x + threadIdx.y*imgWidth + blockindex2;

	//3、线程在线程块中的索引值
	int thread = threadIdx.y*blockDim.x + threadIdx.x;

Para mostrar claramente o processo de cálculo acima, o seguinte diagrama explicativo é desenhado:
insira a descrição da imagem aqui
uma explicação simples para o diagrama acima: porque o bloco de thread alocado (40, 20) não é suficiente para calcular um bloco de imagem (40, 40). Portanto, um bloco de thread precisa ser executado duas vezes para calcular um bloco de imagem.

Nosso objetivo é obter o valor do índice de coordenadas de pixel na imagem correspondente a cada thread no bloco de thread e, em seguida, salvar o valor do pixel correspondente ao valor do índice na memória compartilhada correspondente para cálculo posterior. Incluindo o seguinte processo:

  1. Calcula os índices absolutos dos blocos de thread na imagem, blockindex1 e blockindex2. blockindex2 é o índice na imagem correspondente do primeiro bloco de encadeamento na segunda execução.
  2. Calcula os índices na imagem dos encadeamentos no bloco de encadeamento, index1 e index2. index2 é o índice na imagem correspondente à segunda execução do primeiro thread.
  3. Calcule o índice do encadeamento no bloco de encadeamento, como o valor do índice dos dados subsequentes armazenados na memória compartilhada, encadeamento1 e encadeamento2. thread2 é o índice no bloco de thread correspondente quando o primeiro thread é executado pela segunda vez.

3.3 Salve o valor de cada pixel do bloco de imagem

Transfira todos os valores de pixel no pequeno bloco de imagem 40*40 a ser calculado para a matriz compartilhada duas vezes

	//4、将待计算的40*40小图像块中的所有像素值分两次传送到共享数组中
	//将上半部分的40*20中所有数据赋值到共享数组中
	//将下半部分的40*20中所有数据赋值到共享数组中
	_sum[thread] = dataIn[index1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_max[thread] = dataIn[index1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_min[thread] = dataIn[index1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[index2];

3.4 Calcule o resultado final usando o algoritmo de redução

Use o algoritmo de redução para encontrar a soma, o valor máximo e o valor mínimo de 1600 valores de pixel em um pequeno bloco de imagem de 40*40

	//利用归约算法求出40*40小图像块中1600个像素值中的和、最大值以及最小值
	//使用归约算法后,最大值,最小值以及各像素之和,均保存在线程块中第一个线程对应的索引中。
	for (size_t s=number/2; s>0; s>>=1 )
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + s];
		if (_max[thread] < _max[thread + s]) _max[thread] = _max[thread + s];
		if (_min[thread] > _min[thread + s]) _min[thread] = _min[thread + s];

		__syncthreads(); //所有线程同步
	}

	//将每个线程块的第一个数据结果保存
	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}

3.5 Outro modo de encadeamento

O método de cálculo é semelhante ao anterior, sem mais descrição, veja o código completo na próxima seção.

4. Código de engenharia completo

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <cuda.h>
#include <device_functions.h>
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

//threadsPerBlock(40, 20)
__global__ void matSum(uchar* dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{
    
    
	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

	//根据线程块和线程的索引, 依次对应到图像数组中
	//1、线程块在图像中的索引值
	int blockindex1 = blockIdx.x*blockDim.x + 2*blockIdx.y*blockDim.y*imgWidth;
	int blockindex2 = blockIdx.x*blockDim.x + (2*blockIdx.y*blockDim.y + blockDim.y)*imgWidth;

	//2、线程块中的线程在图像中的索引值
	int index1 = threadIdx.x + threadIdx.y*imgWidth + blockindex1;
	int index2 = threadIdx.x + threadIdx.y*imgWidth + blockindex2;

	//3、线程在线程块中的索引值
	int thread = threadIdx.y*blockDim.x + threadIdx.x;

	//4、将待计算的40*40小图像块中的所有像素值分两次传送到共享数组中
	//将上半部分的40*20中所有数据赋值到共享数组中
	//将下半部分的40*20中所有数据赋值到共享数组中
	_sum[thread] = dataIn[index1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_max[thread] = dataIn[index1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[index2];

	_min[thread] = dataIn[index1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[index2];

	//利用归约算法求出40*40小图像块中1600个像素值中的和、最大值以及最小值
	//使用归约算法后,最大值,最小值以及各像素之和,均保存在线程块中第一个线程对应的索引中。
	for (size_t s=number/2; s>0; s>>=1 )
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + s];
		if (_max[thread] < _max[thread + s]) _max[thread] = _max[thread + s];
		if (_min[thread] > _min[thread + s]) _min[thread] = _min[thread + s];

		__syncthreads(); //所有线程同步
	}

	//将每个线程块的第一个数据结果保存
	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}
}

//threadsPerBlock(20, 40)
__global__ void matSum2(uchar* dataIn, int *dataOutSum, int *dataOutMax, int *dataOutMin, int imgHeight, int imgWidth)
{
    
    
	//定义线程块中各个线程的共享数组:40*40=1600
	const int number = 1600;
	extern __shared__ int _sum[];  //动态共享数组, 用于求和
	__shared__ int _max[number];   //静态共享数组, 用于最大值的求取
	__shared__ int _min[number];   //静态共享数组, 用于最小值的求取

	int blockIndex1 = blockIdx.y*blockDim.y*imgWidth + 2 * blockIdx.x*blockDim.x;
	int blockIndex2 = blockIdx.y*blockDim.y*imgWidth + (2 * blockIdx.x + 1)*blockDim.x;

	int threadIndex1 = blockIndex1 + threadIdx.y*imgWidth + threadIdx.x;
	int threadIndex2 = blockIndex2 + threadIdx.y*imgWidth + threadIdx.x;

	int thread = threadIdx.x + threadIdx.y*blockDim.x;

	_sum[thread] = dataIn[threadIndex1];
	_sum[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	_max[thread] = dataIn[threadIndex1];
	_max[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	_min[thread] = dataIn[threadIndex1];
	_min[thread + blockDim.x*blockDim.y] = dataIn[threadIndex2];

	for (size_t i = number / 2; i > 0; i >>= 1)
	{
    
    
		_sum[thread] = _sum[thread] + _sum[thread + i];
		if (_min[thread] > _min[thread + i]) _min[thread] = _min[thread + i];
		if (_max[thread] < _max[thread + i]) _max[thread] = _max[thread + i];

		__syncthreads(); //所有线程同步
	}

	if (thread == 0)
	{
    
    
		dataOutSum[blockIdx.x + blockIdx.y*gridDim.x] = _sum[0];
		dataOutMax[blockIdx.x + blockIdx.y*gridDim.x] = _max[0];
		dataOutMin[blockIdx.x + blockIdx.y*gridDim.x] = _min[0];
	}
}

int main()
{
    
    
	Mat image = imread("2.jpg", 0); //读取待检测图片
	cv::resize(image, image, cv::Size(1000, 8000));

	int sum[5000];       //求和结果数组
	int max[5000];       //最大值结果数组
	int min[5000];       //最小值结果数组

	//图像的所有字节数
	size_t memSize = image.cols*image.rows * sizeof(uchar);
	int size = 5000 * sizeof(int);

	//分配内存:GPU图像数据、求和结果数组、最大值结果数组、最小值结果数组
	uchar *d_src = NULL;
	int *d_sum = NULL;
	int *d_max = NULL;
	int *d_min = NULL;
	cudaMalloc((void**)&d_src, memSize);
	cudaMalloc((void**)&d_sum, size);
	cudaMalloc((void**)&d_max, size);
	cudaMalloc((void**)&d_min, size);

	//图像数据拷贝到GPU
	cudaMemcpy(d_src, image.data, memSize, cudaMemcpyHostToDevice);

	//图像宽、高
	int imgWidth = image.cols;
	int imgHeight = image.rows;

	//dim3 threadsPerBlock(20, 40); //每个block大小为20*40,对应matSum2核函数
	dim3 threadsPerBlock(40, 20); //每个block大小为40*20,对应matSum核函数

	dim3 blockPerGrid(25, 200); //将8000*1000的图片分为25*200个小图像块

	double time0 = static_cast<double>(getTickCount()); //计时器开始
	matSum << <blockPerGrid, threadsPerBlock, 3200 * sizeof(int) >> > (d_src, d_sum, d_max, d_min, imgHeight, imgWidth);

	//等待所有线程执行完毕
	cudaDeviceSynchronize();

	time0 = ((double)getTickCount() - time0) / getTickFrequency(); //计时器结束
	cout << "The Run Time is :" << time0 << "s" << endl; //输出运行时间

	//将数据拷贝到CPU
	cudaMemcpy(sum, d_sum, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(max, d_max, size, cudaMemcpyDeviceToHost);
	cudaMemcpy(min, d_min, size, cudaMemcpyDeviceToHost);

	//输出
	cout << "The sum is :" << sum[0] << endl;
	cout << "The max is :" << max[0] << endl;
	cout << "The min is :" << min[0] << endl;

	//释放内存
	cudaFree(d_src);
	cudaFree(d_sum);
	cudaFree(d_max);
	cudaFree(d_min);

	//重置设备
	cudaDeviceReset();

	waitKey(0);
	return 0;
}

5. Resultados experimentais

insira a descrição da imagem aqui
Como a ordem de execução dos blocos de thread é inconsistente, os primeiros dados do resultado da soma podem ser diferentes a cada execução, mas os resultados de execução dos valores máximo e mínimo devem ser consistentes a cada vez.

6. Outros

Blog de referência: https://blog.csdn.net/MGotze/article/details/75268746?spm=1001.2014.3001.5501

Acho que você gosta

Origin blog.csdn.net/qq_38589460/article/details/120317152
Recomendado
Clasificación