Здесь следует сделать несколько замечаний:
- При первом вызове ядра CUDA может накапливаться много времени задержки, связанного с настройкой на GPU, поэтому обычный подход состоит в том, чтобы включить «разогревающий» вызов
- Дизайн ядра в вашем вопросе является «резидентным», поэтому оптимальное выполнение должно происходить, когда вы запускаете столько блоков, сколько требуется для полной загрузки вашего графического процессора. Существует API, который вы можете использовать для получения этой информации для вашего графического процессора.
- Выполните синхронизацию в микросекундах, а не миллисекундах
- Создайте свой код в режиме выпуска.
Выполнение всего этого с вашим кодом CUDA дает мне следующее:
int N = 1 << 20;
int device = -1;
cudaGetDevice(&device);
float* x, * y;
cudaMallocManaged(&x, N * sizeof(float));
cudaMallocManaged(&y, N * sizeof(float));
for (int i = 0; i < N; i++) {
x[i] = 1.0f;
y[i] = 2.0f;
}
cudaMemPrefetchAsync(x, N * sizeof(float), device, NULL);
cudaMemPrefetchAsync(y, N * sizeof(float), device, NULL);
int blockSize, numBlocks;
cudaOccupancyMaxPotentialBlockSize(&numBlocks, &blockSize, add);
for(int rep=0; rep<10; rep++) {
auto t1 = std::chrono::high_resolution_clock::now();
add << <numBlocks, blockSize >> > (N, x, y);
cudaDeviceSynchronize();
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
std::cout << rep << " duration CUDA: " << duration <<std::endl;
}
float maxError = 0.0f;
for (int i = 0; i < N; i++)
maxError = fmax(maxError, fabs(y[i] - 12.0f));
std::cout << "Max error: " << maxError << std::endl;
cudaFree(x);
cudaFree(y);
И его сборку и запуск:
$ nvcc -arch=sm_52 -std=c++11 -o not_so_fast not_so_fast.cu
$ ./not_so_fast
Max error Serial: 0
durationSerial: 2762
0 duration CUDA: 1074
1 duration CUDA: 150
2 duration CUDA: 151
3 duration CUDA: 158
4 duration CUDA: 152
5 duration CUDA: 152
6 duration CUDA: 147
7 duration CUDA: 124
8 duration CUDA: 112
9 duration CUDA: 113
Max error: 0
В моей системе первый графический процессор запускается примерно в три раза быстрее быстрый как серийный l oop. Второй и последующие пробеги снова почти в 10 раз быстрее. Ваши результаты могут (и, вероятно, будут) различаться.