# Parallel Programming HW6 @NYCU, 2022 Fall ###### tags: `2022_PP_NYCU` <!-- | 學號 | 姓名 | | -------- | -------- | | 310552060 |湯智惟 | --> ## Q1 ### Explain your implementation. How do you optimize the performance of convolution? ::: info 在 `hostFE.c` 和 `kernel.c` 中的 *2 和 /2 都用 bit operation 來作加速。 在 `hostFE.c` 中 修改 `filter` 大小,和將 inputImage 的 float 型態轉換成 char 型態,減少傳輸量。 在 `kernel.c` 中修改 convolution 寫法,減少多餘的判斷式。 ::: #### **修改 `filter` 大小** 我有把 filter 外圍多餘的 0 都去除掉,產生一個較小的 filter ,這樣可以在傳輸到 gpu 時傳較少 data,在作 convolution 時也可以減少一些不必要的計算。 例如: ![](https://i.imgur.com/p89lv4P.png =80%x) #### 將 inputImage 的 float 型態轉換成 char 型態 我有把 inputImage 中 ,把原本 `float(4 bytes)` 轉換乘 `char(1 byte)` ,這樣可以減少傳送到 gpu 的 data 量。 ```c // make input image from float to char char *newInputImage = (char *)malloc(imageHeight * imageWidth * sizeof(char)); for (int i = 0; i < imageHeight * imageWidth; i++) { newInputImage[i] = (char)inputImage[i]; } ``` #### 修改 Convolution 寫法 我參考作業提供的 `void serialConv()` 來實做。 針對 內部兩個 for loop 來作修改。 ```c sum = 0; // Reset sum for new source pixel // Apply the filter to the neighborhood for (k = -halffilterSize; k <= halffilterSize; k++) { for (l = -halffilterSize; l <= halffilterSize; l++) { if (i + k >= 0 && i + k < imageHeight && j + l >= 0 && j + l < imageWidth) { sum += inputImage[(i + k) * imageWidth + j + l] * filter[(k + halffilterSize) * filterWidth + l + halffilterSize]; } } } ``` 原始板本每次都需要用 if/else 去作邊界的判斷,這樣會比較沒有效率。 ```c // image x, y int x = get_global_id(0); int y = get_global_id(1); // filter x,y start, end int row_start = y - halfFilterSize >= 0 ? 0 : halfFilterSize - y; int row_end = y + halfFilterSize < imageHeight ? filterWidth - 1 : imageHeight - y; int col_start = x - halfFilterSize >= 0 ? 0 : halfFilterSize - x; int col_end = x + halfFilterSize < imageWidth ? filterWidth - 1 : imageWidth - x; // filter based for (int i = row_start; i <= row_end; i++) { int row = y - halfFilterSize + i; int col = x - halfFilterSize; for (int j = col_start; j <= col_end; j++) { sum += inputImage[row * imageWidth + col + j] * filter[i * filterWidth + j]; } } ``` 修改版本可以事先算出 inputimage 從第幾個 row 和 第幾個 col 開始,這樣可以減少多餘的判斷,加速運算。 --- ## Q2 ### Rewrite the program using CUDA. ### (1) Explain your CUDA implementation ```c __global__ void convolution(int filterWidth, float *filter, int imageHeight, int imageWidth, float *inputImage, float *outputImage) { int i = blockIdx.x * blockDim.x + threadIdx.x; int j = blockIdx.y * blockDim.y + threadIdx.y; // Iterate over the rows of the source image int halffilterSize = filterWidth >> 1; float sum = 0.0f; int k, l; // Apply the filter to the neighborhood for (k = -halffilterSize; k <= halffilterSize; k++) { for (l = -halffilterSize; l <= halffilterSize; l++) { if (j + k >= 0 && j + k < imageHeight && i + l >= 0 && i + l < imageWidth) { sum += inputImage[(j + k) * imageWidth + i + l] * filter[(k + halffilterSize) * filterWidth + l + halffilterSize]; } } } outputImage[j * imageWidth + i] = sum; } extern "C" void hostFE(int filterWidth, float *filter, int imageHeight, int imageWidth, float *inputImage, float *outputImage) { float *d_filter, *d_inputImage, *d_outputImage; int filterSize = filterWidth * filterWidth * sizeof(float); int inputImageSize = imageHeight * imageWidth * sizeof(int); int outputImageSize = inputImageSize; cudaMalloc(&d_filter, filterSize); cudaMalloc(&d_inputImage, inputImageSize); cudaMalloc(&d_outputImage, outputImageSize); // cp mem to device cudaMemcpy(d_filter, filter, filterSize, cudaMemcpyHostToDevice); cudaMemcpy(d_inputImage, inputImage, inputImageSize, cudaMemcpyHostToDevice); int block_size = 16; dim3 threadsPerBlock(block_size, block_size); dim3 numBlocks(imageWidth / block_size, imageHeight / block_size); // kernel convolution<<<threadsPerBlock, numBlocks>>>(filterWidth, d_filter, imageHeight, imageWidth, d_inputImage, d_outputImage); // cp mem to host cudaMemcpy(outputImage, d_outputImage, outputImageSize, cudaMemcpyDeviceToHost); // free mem cudaFree(d_outputImage); cudaFree(d_inputImage); cudaFree(d_filter); } ``` ### (2) plot a chart to show the performance difference between using OpenCL and CUDA | | filter 1 | filter 2 | filter 3 | | -------- | -------- | -------- | ------- | | Opencl Optimized | 0.416 | 0.404 |0.412 | | Opencl | 0.671 | 0.482 |0.674 | | Cuda | 0.850 | 0.565 |0.687 | ![](https://i.imgur.com/IrpCyoC.png) ### (3) explain the result. 我實做 cuda 一般版本的 convolution。 我的 cuda 結果會比 opencl 慢,尤其是在 filter 1 的時候。 以目前查看到的[文獻](https://arxiv.org/vc/arxiv/papers/1005/1005.2581v1.pdf)是 cuda 會比 opencl 快。 但是我的 cuda 結果會比 opencl 慢,可能的原因是我在 cuda 部份實做的不夠好。也有可能是我的 opencl 的程式在傳 memory 的種類分得比較細(global, constant) 讓速度贏過 cuda。