[英]vector step addition slower on cuda
我試圖在CUDA C ++代碼上運行向量步驟添加功能,但對於大小為5,000,000的大型浮點數組,它運行速度比我的CPU版本慢。 以下是我所說的相關CUDA和cpu代碼:
#define THREADS_PER_BLOCK 1024
typedef float real;
__global__ void vectorStepAddKernel2(real*x, real*y, real*z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < size)
{
x[i*xstep] = alpha* y[i*ystep] + beta*z[i*zstep];
}
}
cudaError_t vectorStepAdd2(real *x, real*y, real* z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
cudaError_t cudaStatus;
int threadsPerBlock = THREADS_PER_BLOCK;
int blocksPerGrid = (size + threadsPerBlock -1)/threadsPerBlock;
vectorStepAddKernel2<<<blocksPerGrid, threadsPerBlock>>>(x, y, z, alpha, beta, size, xstep, ystep, zstep);
// cudaDeviceSynchronize waits for the kernel to finish, and returns
// any errors encountered during the launch.
cudaStatus = cudaDeviceSynchronize();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching vectorStepAddKernel!\n", cudaStatus);
exit(1);
}
return cudaStatus;
}
//CPU function:
void vectorStepAdd3(real *x, real*y, real* z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
for(int i=0;i<size;i++)
{
x[i*xstep] = alpha* y[i*ystep] + beta*z[i*zstep];
}
}
當3個陣列中的每個陣列的大小為5,000,000且大小= 50,000(即,以這種逐步方式將50,000個元素一起添加)時,調用vectorStepAdd2導致比vectorStepAdd3更慢的計算。
關於如何加速GPU代碼的任何想法? 我的設備是特斯拉M2090 GPU
謝謝
回答你的問題“關於如何加速GPU代碼的任何想法?”
首先讓我先說明所提議的操作X = alpha * Y + beta * Z
不需要每字節數據傳輸的大量計算強度。 結果,我無法在這個特定代碼上超過CPU時間。 但是,為了加速這段代碼,可能需要考慮2個想法:
使用頁鎖定內存進行數據傳輸操作。 這為GPU版本的數據傳輸時間減少了大約2倍,這主宰了GPU版本的整體執行時間。
使用@njuffa 在此提出的cudaMemcpy2D的跨步復制技術。 結果是2倍:我們可以將數據傳輸量減少到只有計算所需的數量, 然后我們可以重新編寫內核以按照注釋中的建議連續操作數據(同樣由njuffa) 。 這使得數據傳輸時間進一步提高了3倍,內核計算時間提高了約10倍。
此代碼提供了以下操作的示例:
#include <stdio.h>
#include <stdlib.h>
#define THREADS_PER_BLOCK 1024
#define DSIZE 5000000
#define WSIZE 50000
#define XSTEP 47
#define YSTEP 43
#define ZSTEP 41
#define TOL 0.00001f
#define cudaCheckErrors(msg) \
do { \
cudaError_t __err = cudaGetLastError(); \
if (__err != cudaSuccess) { \
fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
msg, cudaGetErrorString(__err), \
__FILE__, __LINE__); \
fprintf(stderr, "*** FAILED - ABORTING\n"); \
exit(1); \
} \
} while (0)
typedef float real;
__global__ void vectorStepAddKernel2(real *x, real *y, real *z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < size)
{
x[i*xstep] = alpha* y[i*ystep] + beta*z[i*zstep];
}
}
__global__ void vectorStepAddKernel2i(real *x, real *y, real *z, real alpha, real beta, int size)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < size)
{
x[i] = alpha* y[i] + beta*z[i];
}
}
void vectorStepAdd2(real *x, real *y, real *z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
int threadsPerBlock = THREADS_PER_BLOCK;
int blocksPerGrid = (size + threadsPerBlock -1)/threadsPerBlock;
vectorStepAddKernel2<<<blocksPerGrid, threadsPerBlock>>>(x, y, z, alpha, beta, size, xstep, ystep, zstep);
cudaDeviceSynchronize();
cudaCheckErrors("kernel2 fail");
}
void vectorStepAdd2i(real *x, real *y, real *z, real alpha, real beta, int size)
{
int threadsPerBlock = THREADS_PER_BLOCK;
int blocksPerGrid = (size + threadsPerBlock -1)/threadsPerBlock;
vectorStepAddKernel2i<<<blocksPerGrid, threadsPerBlock>>>(x, y, z, alpha, beta, size);
cudaDeviceSynchronize();
cudaCheckErrors("kernel3 fail");
}
//CPU function:
void vectorStepAdd3(real *x, real*y, real* z, real alpha, real beta, int size, int xstep, int ystep, int zstep)
{
for(int i=0;i<size;i++)
{
x[i*xstep] = alpha* y[i*ystep] + beta*z[i*zstep];
}
}
int main() {
real *h_x, *h_y, *h_z, *c_x, *h_x1;
real *d_x, *d_y, *d_z, *d_x1, *d_y1, *d_z1;
int dsize = DSIZE;
int wsize = WSIZE;
int xstep = XSTEP;
int ystep = YSTEP;
int zstep = ZSTEP;
real alpha = 0.5f;
real beta = 0.5f;
float et;
/*
h_x = (real *)malloc(dsize*sizeof(real));
if (h_x == 0){printf("malloc1 fail\n"); return 1;}
h_y = (real *)malloc(dsize*sizeof(real));
if (h_y == 0){printf("malloc2 fail\n"); return 1;}
h_z = (real *)malloc(dsize*sizeof(real));
if (h_z == 0){printf("malloc3 fail\n"); return 1;}
c_x = (real *)malloc(dsize*sizeof(real));
if (c_x == 0){printf("malloc4 fail\n"); return 1;}
h_x1 = (real *)malloc(dsize*sizeof(real));
if (h_x1 == 0){printf("malloc1 fail\n"); return 1;}
*/
cudaHostAlloc((void **)&h_x, dsize*sizeof(real), cudaHostAllocDefault);
cudaCheckErrors("cuda Host Alloc 1 fail");
cudaHostAlloc((void **)&h_y, dsize*sizeof(real), cudaHostAllocDefault);
cudaCheckErrors("cuda Host Alloc 2 fail");
cudaHostAlloc((void **)&h_z, dsize*sizeof(real), cudaHostAllocDefault);
cudaCheckErrors("cuda Host Alloc 3 fail");
cudaHostAlloc((void **)&c_x, dsize*sizeof(real), cudaHostAllocDefault);
cudaCheckErrors("cuda Host Alloc 4 fail");
cudaHostAlloc((void **)&h_x1, dsize*sizeof(real), cudaHostAllocDefault);
cudaCheckErrors("cuda Host Alloc 5 fail");
cudaMalloc((void **)&d_x, dsize*sizeof(real));
cudaCheckErrors("cuda malloc1 fail");
cudaMalloc((void **)&d_y, dsize*sizeof(real));
cudaCheckErrors("cuda malloc2 fail");
cudaMalloc((void **)&d_z, dsize*sizeof(real));
cudaCheckErrors("cuda malloc3 fail");
cudaMalloc((void **)&d_x1, wsize*sizeof(real));
cudaCheckErrors("cuda malloc4 fail");
cudaMalloc((void **)&d_y1, wsize*sizeof(real));
cudaCheckErrors("cuda malloc5 fail");
cudaMalloc((void **)&d_z1, wsize*sizeof(real));
cudaCheckErrors("cuda malloc6 fail");
for (int i=0; i< dsize; i++){
h_x[i] = 0.0f;
h_x1[i] = 0.0f;
c_x[i] = 0.0f;
h_y[i] = (real)(rand()/(real)RAND_MAX);
h_z[i] = (real)(rand()/(real)RAND_MAX);
}
cudaEvent_t t_start, t_stop, k_start, k_stop;
cudaEventCreate(&t_start);
cudaEventCreate(&t_stop);
cudaEventCreate(&k_start);
cudaEventCreate(&k_stop);
cudaCheckErrors("event fail");
// first test original GPU version
cudaEventRecord(t_start);
cudaMemcpy(d_x, h_x, dsize * sizeof(real), cudaMemcpyHostToDevice);
cudaCheckErrors("cuda memcpy 1 fail");
cudaMemcpy(d_y, h_y, dsize * sizeof(real), cudaMemcpyHostToDevice);
cudaCheckErrors("cuda memcpy 2 fail");
cudaMemcpy(d_z, h_z, dsize * sizeof(real), cudaMemcpyHostToDevice);
cudaCheckErrors("cuda memcpy 3 fail");
cudaEventRecord(k_start);
vectorStepAdd2(d_x, d_y, d_z, alpha, beta, wsize, xstep, ystep, zstep);
cudaEventRecord(k_stop);
cudaMemcpy(h_x, d_x, dsize * sizeof(real), cudaMemcpyDeviceToHost);
cudaCheckErrors("cuda memcpy 4 fail");
cudaEventRecord(t_stop);
cudaEventSynchronize(t_stop);
cudaEventElapsedTime(&et, t_start, t_stop);
printf("GPU original version total elapsed time is: %f ms.\n", et);
cudaEventElapsedTime(&et, k_start, k_stop);
printf("GPU original kernel elapsed time is: %f ms.\n", et);
//now test CPU version
cudaEventRecord(t_start);
vectorStepAdd3(c_x, h_y, h_z, alpha, beta, wsize, xstep, ystep, zstep);
cudaEventRecord(t_stop);
cudaEventSynchronize(t_stop);
cudaEventElapsedTime(&et, t_start, t_stop);
printf("CPU version total elapsed time is: %f ms.\n", et);
for (int i = 0; i< dsize; i++)
if (fabsf((float)(h_x[i]-c_x[i])) > TOL) {
printf("cpu/gpu results mismatch at i = %d, cpu = %f, gpu = %f\n", i, c_x[i], h_x[i]);
return 1;
}
// now test improved GPU version
cudaEventRecord(t_start);
// cudaMemcpy2D(d_x1, sizeof(real), h_x, xstep * sizeof(real), sizeof(real), wsize, cudaMemcpyHostToDevice);
// cudaCheckErrors("cuda memcpy 5 fail");
cudaMemcpy2D(d_y1, sizeof(real), h_y, ystep * sizeof(real), sizeof(real), wsize, cudaMemcpyHostToDevice);
cudaCheckErrors("cuda memcpy 6 fail");
cudaMemcpy2D(d_z1, sizeof(real), h_z, zstep * sizeof(real), sizeof(real), wsize, cudaMemcpyHostToDevice);
cudaCheckErrors("cuda memcpy 7 fail");
cudaEventRecord(k_start);
vectorStepAdd2i(d_x1, d_y1, d_z1, alpha, beta, wsize);
cudaEventRecord(k_stop);
cudaMemcpy2D(h_x1, xstep*sizeof(real), d_x1, sizeof(real), sizeof(real), wsize, cudaMemcpyDeviceToHost);
cudaCheckErrors("cuda memcpy 8 fail");
cudaEventRecord(t_stop);
cudaEventSynchronize(t_stop);
cudaEventElapsedTime(&et, t_start, t_stop);
printf("GPU improved version total elapsed time is: %f ms.\n", et);
cudaEventElapsedTime(&et, k_start, k_stop);
printf("GPU improved kernel elapsed time is: %f ms.\n", et);
for (int i = 0; i< dsize; i++)
if (fabsf((float)(h_x[i]-h_x1[i])) > TOL) {
printf("gpu/gpu improved results mismatch at i = %d, gpu = %f, gpu imp = %f\n", i, h_x[i], h_x1[i]);
return 1;
}
printf("Results:i CPU GPU GPUi \n");
for (int i = 0; i< 20*xstep; i+=xstep)
printf(" %d %f %f %f %f %f\n",i, c_x[i], h_x[i], h_x1[i]);
return 0;
}
如上所述,我仍然無法擊敗CPU時間,我將其歸因於我自己缺乏編碼技能,或者這個操作從根本上說沒有足夠的計算復雜性在GPU上有趣。 不過這里有一些樣本結果:
GPU original version total elapsed time is: 13.352256 ms.
GPU original kernel elapsed time is: 0.195808 ms.
CPU version total elapsed time is: 2.599584 ms.
GPU improved version total elapsed time is: 4.228288 ms.
GPU improved kernel elapsed time is: 0.027392 ms.
Results:i CPU GPU GPUi
0 0.617285 0.617285 0.617285
47 0.554522 0.554522 0.554522
94 0.104245 0.104245 0.104245
....
我們可以看到,與原始內核相比,改進的內核整體減少了約3倍,幾乎所有這些都是由於數據復制時間的減少。 數據復制時間的減少是由於改進的2D memcpy,我們只需要復制我們實際使用的數據。 (沒有頁面鎖定的內存,這些數據傳輸時間大約是兩倍)。 我們還可以看到內核計算時間比原始內核的CPU計算快10倍,比改進內核的CPU計算快約100倍。 然而,考慮到數據傳輸時間,我們無法克服CPU速度。
最后一條評論是cudaMemcpy2D操作的“成本”仍然相當高。 對於向量大小減少100倍,我們只看到復制時間縮短了3倍。 因此,跨步訪問仍然使得使用GPU的成本相對較高。 如果我們只是簡單地傳輸50,000個連續元素的向量,我們預計復制時間幾乎會線性減少100倍(與5000000個元素的原始復制向量相比)。 這意味着復制的時間不到1毫秒,我們的GPU版本將比CPU快,至少這個天真的單線程CPU代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.