我有一个问题归结为对一组矩阵的每个元素执行一些算术。我认为这听起来像是一种可以从转移到GPU上获益的计算。但是,我只是成功地将计算速度降低了10倍!
以下是我的测试系统的具体内容:
下面的代码对我的生产代码,CPU和GPU执行等效的计算。后者在我的机器上一直慢十倍(CPU大约650ms; GPU大约7s)。
我尝试过更改网格和块大小;我增加并减少了传递给GPU的阵列的大小;我通过视觉分析器运行它;我已经尝试过整数数据而不是双数,但无论我做什么,GPU版本总是明显慢于CPU等价物。
那么为什么GPU版本如此之慢以及我上面没有提到的变化,我可以尝试提高其性能吗?
这是我的命令行:nvcc source.cu -o CPUSpeedTest.exe -arch=sm_30
这是source.cu的内容:
#include <iostream>
#include <windows.h>
#include <cuda_runtime_api.h>
void AdjustArrayOnCPU(double factor1, double factor2, double factor3, double denominator, double* array, int arrayLength, double* curve, int curveLength)
{
for (size_t i = 0; i < arrayLength; i++)
{
double adjustmentFactor = factor1 * factor2 * factor3 * (curve[i] / denominator);
array[i] = array[i] * adjustmentFactor;
}
}
__global__ void CudaKernel(double factor1, double factor2, double factor3, double denominator, double* array, int arrayLength, double* curve, int curveLength)
{
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < arrayLength)
{
double adjustmentFactor = factor1 * factor2 * factor3 * (curve[idx] / denominator);
array[idx] = array[idx] * adjustmentFactor;
}
}
void AdjustArrayOnGPU(double array[], int arrayLength, double factor1, double factor2, double factor3, double denominator, double curve[], int curveLength)
{
double *dev_row, *dev_curve;
cudaMalloc((void**)&dev_row, sizeof(double) * arrayLength);
cudaMalloc((void**)&dev_curve, sizeof(double) * curveLength);
cudaMemcpy(dev_row, array, sizeof(double) * arrayLength, cudaMemcpyHostToDevice);
cudaMemcpy(dev_curve, curve, sizeof(double) * curveLength, cudaMemcpyHostToDevice);
CudaKernel<<<100, 1000>>>(factor1, factor2, factor3, denominator, dev_row, arrayLength, dev_curve, curveLength);
cudaMemcpy(array, dev_row, sizeof(double) * arrayLength, cudaMemcpyDeviceToHost);
cudaFree(dev_curve);
cudaFree(dev_row);
}
void FillArray(int length, double row[])
{
for (size_t i = 0; i < length; i++) row[i] = 0.1 + i;
}
int main(void)
{
const int arrayLength = 10000;
double arrayForCPU[arrayLength], curve1[arrayLength], arrayForGPU[arrayLength], curve2[arrayLength];;
FillArray(arrayLength, curve1);
FillArray(arrayLength, curve2);
///////////////////////////////////// CPU Version ////////////////////////////////////////
LARGE_INTEGER StartingTime, EndingTime, ElapsedMilliseconds, Frequency;
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&StartingTime);
for (size_t iterations = 0; iterations < 10000; iterations++)
{
FillArray(arrayLength, arrayForCPU);
AdjustArrayOnCPU(1.0, 1.0, 1.0, 1.0, arrayForCPU, 10000, curve1, 10000);
}
QueryPerformanceCounter(&EndingTime);
ElapsedMilliseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
ElapsedMilliseconds.QuadPart *= 1000;
ElapsedMilliseconds.QuadPart /= Frequency.QuadPart;
std::cout << "Elapsed Milliseconds: " << ElapsedMilliseconds.QuadPart << std::endl;
///////////////////////////////////// GPU Version ////////////////////////////////////////
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start);
for (size_t iterations = 0; iterations < 10000; iterations++)
{
FillArray(arrayLength, arrayForGPU);
AdjustArrayOnGPU(arrayForGPU, 10000, 1.0, 1.0, 1.0, 1.0, curve2, 10000);
}
cudaEventRecord(stop);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
std::cout << "CUDA Elapsed Milliseconds: " << elapsedTime << std::endl;
cudaEventDestroy(start);
cudaEventDestroy(stop);
return 0;
}
以下是CUDASpeedTest.exe输出的示例
Elapsed Milliseconds: 565
CUDA Elapsed Milliseconds: 7156.76
答案 0 :(得分:2)
对于大多数使用CUDA的开发人员来说,接下来的内容可能会非常明显,但对于其他人来说可能是有价值的 - 比如我自己 - 对这项技术不熟悉。
GPU代码比CPU等效慢十倍,因为GPU代码表现出完美的性能破坏特性。
GPU代码花费大部分时间在GPU上分配内存,将数据复制到设备,执行非常非常简单的计算(无论其运行的处理器类型如何都非常快)然后将数据复制回来从设备到主机。
如注释中所述,如果正在处理的数据结构的大小存在上限,则GPU上的缓冲区可以只分配一次并重用。在上面的代码中,这将GPU从CPU运行时间从10:1降低到4:1。
剩余的性能差异可归结为CPU由于其简单性而能够在非常短的时间内以串行方式执行所需的计算数百万次。在上面的代码中,计算涉及从数组中读取值,一些乘法,最后一个赋值 到一个数组元素。这个简单的东西必须进行数百万次 之前并行这样做的好处超过了将数据传输到GPU并返回的必要时间损失。在我的测试系统中,一百万个数组元素是收支平衡点,GPU和CPU在(大约)相同的时间内执行。