Технология nvidia cuda · Весенний семестр, 2016. ... (kepler, 2304 cores,...
TRANSCRIPT
Технология NVIDIA CUDA
Михаил Георгиевич Курносов
Email: [email protected]
WWW: http://www.mkurnosov.net
Курс «Параллельные и распределенные вычисления»
Школа анализа данных Яндекс (Новосибирск)
Весенний семестр, 2016
GPU – Graphics Processing Unit
Graphics Processing Unit (GPU) – графический процессор, специализированный многопроцессорный ускоритель с общей памятью
Большая часть площади чипа занята элементарными ALU/FPU/Load/Storeмодулями
Устройство управления (control unit) относительно простое по сравнению с CPU
GPU управляется с CPU: копирование данных между оперативной памятью узла и GPU, запуск программ и др.
NVIDIA GeForce GTX 780 (Kepler, 2304 cores, GDDR5 3 GB)
AMD Radeon HD 8970(2048 cores, GDDR5 3 GB)
Гибридные вычислительные узлы
Несколько GPU
CPU управляет GPU через драйвер
Узкое место –передача данных между памятью CPU и GPU
GPU не является bootable-устройством
74 системы из Top500 (Nov. 2014) оснащены ускорителями (NVIDIA, ATI, Intel Xeon Phi, PEZY SC )
CPU1
Core1 Core2 Core3 Core4
GPU1
Cores
GPU Memory
Memory (DDR3) Memory (DDR3)
QPI
I/O Hub
QPI QPI
PCI Express
CPU1
Core1 Core2 Core3 Core4
GPU2
Cores
GPU Memory
NVIDIA CUDA
NVIDIA CUDA – программно-аппаратная платформа для организации параллельных вычислений на графических процессорах
NVIDIA CUDA SDK:o архитектура виртуальной машины CUDAo компилятор C/C++o драйвер GPU
ОС: GNU/Linux, Apple Mac OS X, Microsoft Windows
2006 – CUDA 1.0
2016 – CUDA 7.5 (Unified memory, Multi-GPU)
Микроархитектуры: Tesla (GeForce 8), Fermi (GeForce 400, 500), Kepler (GeForce 600, 700), Maxwell (GeForce 700, 800, 900)
http://developer.nvidia.com/cuda
Хост (host) – узел с CPU и его память
Устройство (device) – графический процессор
и его память
Ядро (kernel) – это фрагмент программы,
предназначенный для выполнения
на GPU
CUDA-программа и ее входные данные находятся в памяти хоста
Программа компилируется и запускается на хосте
В CUDA-программе выполняются следующие шаги:
1. Копирование данных из памяти хоста в память устройства
2. Запуск ядра (kernel) на устройстве
3. Копирование данных из памяти устройства в память хоста
Основные понятия NVIDIA CUDA
RAM
CPU
RAM
GPU
DeviceHost
PCI Express
Server/workstation
CUDA Hello World
/* * hello.cu: * */
#include <stdio.h>
__global__ void mykernel() {
}
int main(){
/* Запуск ядра на GPU – один поток */mykernel<<<1,1>>>();printf("Hello, CUDA World!\n");return 0;
}
Спецификатор __global__ сообщает
компилятору, что функция предназначена
для выполнения на GPU
“<<< >>>” – запуск заданного числа
потоков на GPU
NVIDIA GeForce 680 (GK104, Kepler microarch.)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
4 Graphics Processing Clusters (GPC)2 Streaming Multiprocessor (SMX)
Streaming Multiprocessor (SMX)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Информация об устройстве
./deviceQuery Starting...
CUDA Device Query (Runtime API) version (CUDART static linking)
Detected 1 CUDA Capable device(s)
Device 0: "GeForce GTX 680"CUDA Driver Version / Runtime Version 7.5 / 7.5CUDA Capability Major/Minor version number: 3.0Total amount of global memory: 2048 MBytes (2147287040 bytes)( 8) Multiprocessors, (192) CUDA Cores/MP: 1536 CUDA CoresGPU Max Clock rate: 1058 MHz (1.06 GHz)Memory Clock rate: 3004 MhzMemory Bus Width: 256-bitL2 Cache Size: 524288 bytesWarp size: 32Maximum number of threads per multiprocessor: 2048Maximum number of threads per block: 1024Max dimension size of a thread block (x,y,z): (1024, 1024, 64)Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)...
/opt/NVIDIA_CUDA-7.5_Samples
Вычислительные потоки CUDA
Номер потока (thread index) – это трехкомпонентный
вектор (координаты потока)
Потоки логически сгруппированы в одномерный,
двухмерный или трёхмерный блок (thread block)
Количество потоков в блоке ограничено (в Kepler 1024)
Блоки распределяются по потоковым
мультипроцессорам SMX
Предопределенные переменные
o threadIdx.{x, y, z} – номер потока
o blockDim.{x, y, z} – размерность блока
o blockIdx.{x, y, z} – номер блока
Thread Block
Thread Thread Thread
Thread Thread Thread
Вычислительные потоки CUDA
Grid of thread blocks
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
gridDim.y
gridDim.x
blockDim.xblockDim.z
blockDim.y
Выполнение CUDA-программы
CUDA Program
Block 0 Block 1 Block 2
Block 3 Block 4 Block 5
GPU 1
Block 0 Block 1
Block 2 Block 3
Block 4 Block 5
SMX 1 SMX 2
GPU 2
Block 0 Block 1 Block 2
Block 3 Block 4 Block 5
SMX 1 SMX 2 SMX 3
Сложение векторов (CPU version)
/* * Host code (CPU version)*/void vadd(float *a, float *b, float *c, int n){
for (int i = 0; i < n; i++)c[i] = a[i] + b[i];
}
Сложение векторов (GPU version)
#include <cuda_runtime.h>
enum { NELEMS = 1024 * 1024 };
int main(){
/* Allocate vectors on host */size_t size = sizeof(float) * NELEMS;float *h_A = malloc(size);float *h_B = malloc(size);float *h_C = malloc(size);if (h_A == NULL || h_B == NULL || h_C == NULL) {
fprintf(stderr, "Allocation error.\n");exit(EXIT_FAILURE);
}
for (int i = 0; i < NELEMS; ++i) {h_A[i] = rand() / (float)RAND_MAX;h_B[i] = rand() / (float)RAND_MAX;
}
Сложение векторов (GPU version)
/* Allocate vectors on device */float *d_A = NULL, *d_B = NULL, *d_C = NULL;if (cudaMalloc((void **)&d_A, size) != cudaSuccess) {
fprintf(stderr, "Allocation error\n");exit(EXIT_FAILURE);
}if (cudaMalloc((void **)&d_B, size) != cudaSuccess) {
fprintf(stderr, "Allocation error\n");exit(EXIT_FAILURE);
}if (cudaMalloc((void **)&d_C, size) != cudaSuccess) {
fprintf(stderr, "Allocation error\n");exit(EXIT_FAILURE);
}
Сложение векторов (GPU version)
/* Copy the host vectors to device */if (cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice) != cudaSuccess) {
fprintf(stderr, "Host to device copying failed\n");exit(EXIT_FAILURE);
}if (cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice) != cudaSuccess) {
fprintf(stderr, "Host to device copying failed\n");exit(EXIT_FAILURE);
}
/* Launch the kernel */int threadsPerBlock = 1024;int blocksPerGrid =(NELEMS + threadsPerBlock - 1) / threadsPerBlock;vadd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, NELEMS);if (cudaGetLastError() != cudaSuccess) {
fprintf(stderr, "Failed to launch kernel!\n");exit(EXIT_FAILURE);
}
𝑏𝑙𝑜𝑐𝑘𝑠 =𝑁
𝑡ℎ𝑟𝑒𝑎𝑑𝑠=
𝑁 + 𝑡ℎ𝑟𝑒𝑎𝑑𝑠 − 1
𝑡ℎ𝑟𝑒𝑎𝑑𝑠
Сложение векторов (GPU version)
/* Copy the device vectors to host */if (cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost) != cudaSuccess) {
fprintf(stderr, "Device to host copying failed\n");exit(EXIT_FAILURE);
}
for (int i = 0; i < NELEMS; ++i) {if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5) {
fprintf(stderr, "Result verification failed at element %d!\n", i);exit(EXIT_FAILURE);
}}
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);free(h_A);free(h_B);free(h_C);cudaDeviceReset();return 0;
}
…
Thread Block 2
Thread 0 Thread 1 Thread …
Сложение векторов (GPU version)
__global__ void vadd(const float *a, const float *b, float *c, int n){
int i = blockDim.x * blockIdx.x + threadIdx.x;if (i < n)
c[i] = a[i] + b[i];}
Thread Block 0
Thread 0 Thread 1 … Thread … …
Thread Block 1
Thread 0 Thread 1 Thread …
…
Thread Block 3
Thread 0 Thread 1 Thread …… ……
…
Grid of blocks:
Сложение векторов (scalability)
NELEMScngpu1
GeForce GTX 680cngpu2
GeForce GT 630
cngpu3GeForce GTS 250
(threadsPerBlock = 512)
2^20
CPU version (sec.): 0.001658GPU version (sec.): 0.000161Memory ops. (sec.): 0.002341Memory bw. (MiB/sec.): 5125.94CPU perf (MFLOPS): 603.15GPU perf (MFLOPS): 6213.78Speedup: 10.30Speedup (with mem ops.): 0.66
CPU version (sec.): 0.002311GPU version (sec.): 0.001054Memory ops. (sec.): 0.002514Memory bw. (MiB/sec.): 4773.49CPU perf (MFLOPS): 432.71GPU perf (MFLOPS): 948.72Speedup: 2.19Speedup (with mem ops.): 0.65
CPU version (sec.): 0.002195GPU version (sec.): 0.001993Memory ops. (sec.): 0.009481Memory bw. (MiB/sec.): 1265.70CPU perf (MFLOPS): 455.61GPU perf (MFLOPS): 501.77Speedup: 1.10Speedup (with mem ops.): 0.19
10 * 2^20
CPU version (sec.): 0.015133GPU version (sec.): 0.000892Memory ops. (sec.): 0.021551Memory bw. (MiB/sec.): 5568.15CPU perf (MFLOPS): 660.81GPU perf (MFLOPS): 11211.72Speedup: 16.97Speedup (with mem ops.): 0.67
CPU version (sec.): 0.014987GPU version (sec.): 0.009118Memory ops. (sec.): 0.021882Memory bw. (MiB/sec.): 5484.00CPU perf (MFLOPS): 667.25GPU perf (MFLOPS): 1096.72Speedup: 1.64Speedup (with mem ops.): 0.48
CPU version (sec.): 0.017394GPU version (sec.): 0.006778Memory ops. (sec.): 0.101824Memory bw. (MiB/sec.): 1178.51CPU perf (MFLOPS): 574.91GPU perf (MFLOPS): 1475.36Speedup: 2.57Speedup (with mem ops.): 0.16
NVIDIA GeForce 680 (GK104, Kepler)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
Streaming Multiprocessor (SM)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Архитектура SIMT (Single-Instruction, Multiple-Thread)
Блоки потоков распределяются по SM
Если число блоков меньше количества SM, то часть процессоров GPU простаивает
Порядок выполнения блоков заранее неизвестен –алгоритмы для GPU не должны быть чувствительны к порядку выполнения блоков
CUDA Program
Block 0 Block 1 Block 2
Block 3 Block 4 Block 5
GPU 1
Block 0 Block 1
Block 2 Block 3
Block 4 Block 5
SMX 1 SMX 2
GPU 2
Block 0 Block 1 Block 2
Block 3 Block 4 Block 5
SMX 1 SMX 2 SMX 3
NVIDIA GeForce 680 (GK104, Kepler)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
Streaming Multiprocessor (SM)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Архитектура SIMT (Single-Instruction, Multiple-Thread)
Блоки назначенные на SM логически разбиваются на группы (warps) по 32 потока
Потоки всегда одинаково распределяются по warp-ам (детерминировано, на базе номера потока)
Если размер блока не кратен размеру группы, то последняя группа (warp) потоков блокируется
http://mc.stanford.edu/cgi-bin/images/3/34/Darve_cme343_cuda_3.pdf
NVIDIA GeForce 680 (GK104, Kepler)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
Streaming Multiprocessor (SM)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Архитектура SIMT (Single-Instruction, Multiple-Thread)
Планировщик (warp scheduler) запускает на выполнение группу потоков, у которых «готовы» входные данные
Все активные потоки группы в каждый момент времени выполняют одну и ту же инструкцию
Планировщик выполняет переключение контекста между группами потоков
http://mc.stanford.edu/cgi-bin/images/3/34/Darve_cme343_cuda_3.pdf
NVIDIA GeForce 680 (GK104, Kepler)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
Streaming Multiprocessor (SM)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Архитектура SIMT (Single-Instruction, Multiple-Thread)
Control Flow Divergence
Все потоки группы в каждый момент времени выполняют одну и ту же инструкцию
Что если потоки управления расходятся?
__global__ void kernel(){
if (val < 50) {// branch 1
} else {// branch 2
}}
Потоки группы выполняют обе ветви
Потоки, которые не должны выполнять ветвь, блокируются
NVIDIA GeForce 680 (GK104, Kepler)
http://www.nvidia.com/content/PDF/product-specifications/GeForce_GTX_680_Whitepaper_FINAL.pdf
Streaming Multiprocessor (SM)192 cores, 32 special function units, 32 LD/ST units, 4 warp schedulers
Архитектура SIMT (Single-Instruction, Multiple-Thread)
Control Flow Divergence
__global__ void kernel(){
if (val < 50) {// branch 1// branch 1// branch 1
} else {// branch 2// branch 2
}}
T F T T T FT F
x x x x x
x x x
Информация об устройстве (cngpu1)
./deviceQuery Starting...
CUDA Device Query (Runtime API) version (CUDART static linking)
Detected 1 CUDA Capable device(s)
Device 0: "GeForce GTX 680"CUDA Driver Version / Runtime Version 7.5 / 7.5CUDA Capability Major/Minor version number: 3.0Total amount of global memory: 2048 MBytes (2147287040 bytes)( 8) Multiprocessors, (192) CUDA Cores/MP: 1536 CUDA CoresGPU Max Clock rate: 1058 MHz (1.06 GHz)Memory Clock rate: 3004 MhzMemory Bus Width: 256-bitL2 Cache Size: 524288 bytesWarp size: 32Maximum number of threads per multiprocessor: 2048Maximum number of threads per block: 1024Max dimension size of a thread block (x,y,z): (1024, 1024, 64)Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)...
/opt/NVIDIA_CUDA-7.5_Samples
Иерархия потоков
Сетка потоков (1D, 2D, 3D) – совокупность блоков потоков
Сетка потоков, CUDA-программа, выполняется GPU
Блок потоков (1D, 2D, 3D) – совокупность потоков
Блоки распределяются по потоковым мультипроцессорам
Device 0: "GeForce GTX 680"Max dimension size of a thread block (x,y,z): (1024, 1024, 64)Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)...
Иерархия потоков
Предопределенные переменные
o threadIdx.{x, y, z} –номер потока в блоке
o blockDim.{x, y, z} –размерность блока
o blockIdx.{x, y, z} –номер блока в сетке
Здание структуры сетки
mykernel<<<B,T>>>(arg1, … );
o B – структура сеткиo T – структура блока
Grid of thread blocks
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
Thread Block
Thread Thread Thread
Thread Thread Thread
gridDim.y
gridDim.x
blockDim.xblockDim.z
blockDim.y
Сложение матриц: CPU
C A B
= +
void matadd_host(float *a, float *b, float *c, int m, int n){
for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {
int idx = i * n + j;c[idx] = a[idx] + b[idx];
} }
}
Сложение матриц: CUDA 1D
Каждый поток вычисляет один элемент c[i * n + j] матрицы
Одномерные сетка блоков потоков и одномерные блоки потоков
__global__ void matadd(const float *a, const float *b, float *c, int m, int n){
int idx = blockIdx.x * blockDim.x + threadIdx.x;if (idx < m * n)
c[idx] = a[idx] + b[idx];}
{ int threadsPerBlock = 1024;int blocksPerGrid =(ROWS * COLS + threadsPerBlock - 1) / threadsPerBlock;matadd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, ROWS, COLS);
}
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
blockIdx.x = 0 blockIdx.x = 1 blockIdx.x = 2
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
threadIdx.x1D-сетка из 3-х 1D-блоков потоков
Thread global ID
C A B
= +
Сложение матриц: CUDA 2D
Каждый поток вычисляет один элемент c[i * n + j] матрицы
2D-сетка блоков потоков и 2D-блоки потоков
__global__ void matadd(const float *a, const float *b, float *c, int m, int n){
int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y;int idx = y * n + x;if (idx < m * n) c[idx] = a[idx] + b[idx];
}
{int threadsPerBlockDim = 32;dim3 blockDim(threadsPerBlockDim, threadsPerBlockDim, 1); // Grid: X x Y x Z=1int blocksPerGridDimX = ceilf(COLS / (float)threadsPerBlockDim);int blocksPerGridDimY = ceilf(ROWS / (float)threadsPerBlockDim);dim3 gridDim(blocksPerGridDimX, blocksPerGridDimY, 1); // Blocks: X x Y x Z=1matadd<<<gridDim, blockDim>>>(d_A, d_B, d_C, ROWS, COLS);
}
(0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0)
(0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1)
(0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2)
(0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0)
(0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1)
(0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2)
threadIdx.xthreadIdx.yblockIdx.x = 0 blockIdx.x = 1 blockIdx.x = 2 blockIdx.x = 3
blockIdx.y = 0
blockIdx.y = 1
Умножение матриц (CPU)
void sgemm_host(float *a, float *b, float *c, int n){
for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {
float s = 0.0;for (int k = 0; k < n; k++)
s += a[i * n + k] * b[k * n + j];c[i * n + j] = s;
} }
}
Умножение матриц (CUDA)
Каждый поток вычисляет один элемент результирующей матрицы C Общее число потоков N * N
http://users.wfu.edu/choss/CUDA/docs/Lecture%205.pdf
__global__ void sgemm(const float *a, const float *b, float *c, int n){
int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < n && col < n) {float s = 0.0;for (int k = 0; k < n; k++)
s += a[row * n + k] * b[k * n + col];c[row * n + col] = s;
}}{
int threadsPerBlockDim = 32;dim3 blockDim(threadsPerBlockDim, threadsPerBlockDim, 1); int blocksPerGridDimX = ceilf(N / (float)threadsPerBlockDim);int blocksPerGridDimY = ceilf(N / (float)threadsPerBlockDim);dim3 gridDim(blocksPerGridDimX, blocksPerGridDimY, 1);sgemm<<<gridDim, blockDim>>>(d_A, d_B, d_C, N);
}
Иерархия памяти (comp. capability >= 3.0)
Глобальная память (global memory)o Относительно медленнаяo Кешируетсяo Содержимое сохраняется между запусками ядер
Память констант (constant memory)o Содержит константы и аргументы ядерo Кешируетсяo 64 KiB / GPU (8 KiB const. cache per SM)
Разделяемая память (shared memory)o Быстраяo Некешируется
Локальная память (local memory)o Часть глобальной памятиo Кешируется
Регистры (32 bit, 65536 / block)
Иерархия памяти (comp. capability >= 3.0)
Объявление ПамятьОбласть
видимостиВремя жизни
int localVar; register thread thread
int localArray[10]; local thread thread
__shared__ int sharedVar; shared block block
__device__ int globalVar; global grid program
__constant__ int constVar; constant grid program
Иерархия памяти (comp. capability >= 3.0)
Объявление ПамятьНакладные
расходы
int localVar; register 1x
int localArray[10]; local 100x
__shared__ int sharedVar; shared 1x
__device__ int globalVar; global 100x
__constant__ int constVar; constant 1x
Иерархия памяти
Использование иерархии памяти (thread local)
// Загружаем данные из глобальной памяти в регистрfloat localA = dA[blockIdx.x * blockDim.x + threadIdx.x];
// Вычисления над данными в регистрахfloat res = f(localA);
// Записываем результат в глобальную памятьdA[blockIdx.x * blockDim.x + threadIdx.x] = res;
Использование иерархии памяти (block local)
// Загружаем данные из глобальной памяти в разделяемую__shared__ float sharedA[BLOCK_SIZE]; int idx = blockIdx.x * blockDim.x + threadIdx.x;sharedA[threadIdx.x] = dA[idx];
__syncthreads(); // барьерная синхронизация потоков блока
// Вычисления над данными в разделяемой памятиfloat res = f(shared[threadIdx.x]);
// Записываем результат в глобальную памятьdA[idx] = res;
Умножение матриц (CPU)
void sgemm_host(float *a, float *b, float *c, int n){
for (int i = 0; i < n; i++) {for (int j = 0; j < n; j++) {
float s = 0.0;for (int k = 0; k < n; k++)
s += a[i * n + k] * b[k * n + j];c[i * n + j] = s;
} }
}
Умножение матриц (CUDA naive)
Каждый поток вычисляет один элемент результирующей матрицы C Общее число потоков N * N
http://users.wfu.edu/choss/CUDA/docs/Lecture%205.pdf
__global__ void sgemm(const float *a, const float *b, float *c, int n){
int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < n && col < n) {float s = 0.0;for (int k = 0; k < n; k++)
s += a[row * n + k] * b[k * n + col];c[row * n + col] = s;
}}{
int threadsPerBlockDim = 32;dim3 blockDim(threadsPerBlockDim, threadsPerBlockDim, 1); int blocksPerGridDimX = ceilf(N / (float)threadsPerBlockDim);int blocksPerGridDimY = ceilf(N / (float)threadsPerBlockDim);dim3 gridDim(blocksPerGridDimX, blocksPerGridDimY, 1);sgemm<<<gridDim, blockDim>>>(d_A, d_B, d_C, N);
}
(0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0)
(0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1)
(0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2)
(0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0) (0,0) (1,0) (2,0)
(0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1) (0,1) (1,1) (2,1)
(0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2) (0,2) (1,2) (2,2)
threadIdx.xthreadIdx.yblockIdx.x = 0 blockIdx.x = 1 blockIdx.x = 2 blockIdx.x = 3
blockIdx.y = 0
blockIdx.y = 1
Каждый поток обращается к глобальной памяти
(n loads + 1 store)
Умножение матриц c разделяемой памятью (tiled)
Каждый элемент матрицы С вычисляется одним потоком Вычисления разбиты на несколько стадий – по числу
подматриц
c[i,j] = a[i,0]*b[0,j] + a[i,1]*b[1,j] + a[i,2]*b[2,j] + a[i,3]*b[3,j] +
a[i,4]*b[4,j] + a[i,5]*b[5,j] + a[i,6]*b[6,j] + a[i,7]*b[7,j] +
a[i,8]*b[8,j] + a[i,9]*b[9,j] + a[i,10]*b[10,j] + a[i,11]*b[11,j] +
a[i,12]*b[12,j] + a[i,13]*b[13,j] + a[i,14]*b[14,j] + a[i,15]*b[15,j];
На каждой стадии загружаем подматрицы в разделяемую память и вычисляем часть результатоввсеми потоками блока
B
CAAstart = blockIdx.y * blockDim.y * NAstep = blockDim.x
Bstart = blockIdx.x * blockDim.xBstep = N * blockDim.x
__global__ void sgemm_tailed(const float *a, const float *b, float *c, int n){
int tail_size = blockDim.x;int tx = threadIdx.x;int ty = threadIdx.y;int bx = blockIdx.x;int by = blockIdx.y;
// Result for c[i, j]float sum = 0.0;
// Index of first tail (sub-matrix) in A int Astart = by * n * tail_size;int Aend = Astart + n - 1;int Astep = tail_size;
// Index of first tail (sub-matrix) in Bint Bstart = bx * tail_size; int Bstep = n * tail_size;
Умножение матриц c разделяемой памятью (tiled)
B
CA
__shared__ float as[BLOCK_SIZE][BLOCK_SIZE];__shared__ float bs[BLOCK_SIZE][BLOCK_SIZE];
int ai = Astart;int bi = Bstart;while (ai <= Aend) {
// Load tail to shared memory - each thread load one itemas[ty][tx] = a[ai + ty * n + tx];bs[ty][tx] = b[bi + ty * n + tx];
// Wait all threads__syncthreads();
// Compute partial result for (int k = 0; k < tail_size; k++)
sum += as[ty][k] * bs[k][tx];
// Wait for all threads before overwriting of as and bs__syncthreads();
ai += Astep;bi += Bstep;
}
int Cstart = by * n * tail_size + bx * tail_size;c[Cstart + ty * n + tx] = sum;
}
Умножение матриц c разделяемой памятью (tiled)
Умножение матриц
Tailed versionCUDA kernel launch with 1024 (32 32) blocks of 1024 (32 32) threadsCPU version (sec.): 3.517567GPU version (sec.): 0.009149Memory ops. (sec.): 0.002338Memory bw. (MiB/sec.): 5133.26CPU GFLOPS: 0.61GPU GFLOPS: 234.72Speedup: 384.47Speedup (with mem ops.): 306.23
Naive version CUDA kernel launch with 1024 (32 32) blocks of 1024 (32 32) threadsCPU version (sec.): 2.824214GPU version (sec.): 0.023105Memory ops. (sec.): 0.002345Memory bw. (MiB/sec.): 5117.08CPU GFLOPS: 0.76GPU GFLOPS: 92.94Speedup: 122.23Speedup (with mem ops.): 110.97
Naive versionCUDA kernel launch with 1024 (32 32) blocks of 1024 (32 32) threadsCPU version (sec.): 3.101753GPU version (sec.): 0.254254Memory ops. (sec.): 0.002534Memory bw. (MiB/sec.): 4735.76CPU GFLOPS: 0.69GPU GFLOPS: 8.45Speedup: 12.20Speedup (with mem ops.): 12.08
Tailed versionCUDA kernel launch with 1024 (32 32) blocks of 1024 (32 32) threadsCPU version (sec.): 3.009662GPU version (sec.): 0.089633Memory ops. (sec.): 0.002516Memory bw. (MiB/sec.): 4769.42CPU GFLOPS: 0.71GPU GFLOPS: 23.96Speedup: 33.58Speedup (with mem ops.): 32.66
x2.8x2.5
GeForce GTX 680 GeForce GT 630
Гистограмма цветов изображения
https://commons.wikimedia.org/wiki/File:Image_and_histogram.png?uselang=ru
Задано изображение – двумерный массив целых чисел из интервала [0..255]
Необходимо построить гистограмму, таблицу hist[0..255], элемент hist[i] которой равен числу пикселей с цветом i
Гистограмма цветов изображения (CPU)
// Выделение памяти под изображениеsize_t size = sizeof(uint8_t) * width * height;uint8_t *image = (uint8_t *)malloc(size);if (image == NULL) {
fprintf(stderr, "Allocation error.\n");exit(EXIT_FAILURE);
}// Инициализация изображения srand(0);for (size_t i = 0; i < size; i++)
image[i] = (rand() / (double)RAND_MAX) * 255;
// Инициализация гистограммыint hist[256];memset(hist, 0, sizeof(*hist) * 256);
Гистограмма цветов изображения (CPU)
void hist_host(uint8_t *image, int width, int height, int *hist)
{for (int i = 0; i < height; i++)
for (int j = 0; j < width; j++)hist[image[i * width + j]]++;
}
Нерегулярный шаблон доступа к памяти –
массиву hist
Гистограмма цветов изображения (GPU)
Каждый поток обрабатывает один пиксель изображения
// Гистограмма в памяти GPUint *d_hist = NULL;cudaMalloc((void **)&d_hist, sizeof(*d_hist) * 256);cudaMemset(d_hist, 0, sizeof(*d_hist) * 256);
uint8_t *d_image = NULL;cudaMalloc((void **)&d_image, size);cudaMemcpy(d_image, image, size, cudaMemcpyHostToDevice);
int threadsPerBlock = 1024;int blocksPerGrid = (size + threadsPerBlock - 1) / threadsPerBlock;hist_gpu<<<blocksPerGrid, threadsPerBlock>>>(d_image, width,
height, d_hist);
cudaMemcpy(hist, d_hist, sizeof(*d_hist) * 256, cudaMemcpyDeviceToHost);
Гистограмма цветов изображения (GPU)
Каждый поток обрабатывает один пиксель изображения
__global__ void hist_gpu(uint8_t *image, int width, int height, int *hist)
{size_t size = width * height;int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < size) {
hist[image[i]]++;}
}
Гистограмма цветов изображения: тест
$ ./histSum (CPU) = 104857600.000000CUDA kernel launch with 102400 blocks of 1024 threadsSum (GPU) = 3928061.000000CPU version (sec.): 0.052969GPU version (sec.): 0.068165Memory ops. (sec.): 0.000019Speedup: 0.78Speedup (with mem ops.): 0.78
$ ./histSum (CPU) = 104857600.000000CUDA kernel launch with 102400 blocks of 1024 threadsSum (GPU) = 3931478.000000CPU version (sec.): 0.052965GPU version (sec.): 0.068171Memory ops. (sec.): 0.000020Speedup: 0.78Speedup (with mem ops.): 0.78
Гистограмма цветов изображения (GPU)
Каждый поток обрабатывает один пиксель изображения
__global__ void hist_gpu(uint8_t *image, int width, int height, int *hist)
{size_t size = width * height;int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < size) {
hist[image[i]]++;}
}
Потоки одновременно читают и записывают данные в одни и те же ячейки таблицы hist[0..255] – data race
CUDA Atomic Functions
Атомарная функция (CUDA atomic function) – функция, выполняющая операцию read-modify-write (RMW) над 32 или 64 битным значением в глобальной или разделяемой памяти GPU
int atomicAdd(int* address, int val); unsigned long long int atomicAdd(unsigned long long int* address, unsigned long long int val); float atomicAdd(float* address, float val); // compute capability >= 2.0
int atomicSub(int* address, int val); int atomicMin(int* address, int val); int atomicAnd(int* address, int val); int atomicXor(int* address, int val); int atomicCAS(int* address, int compare, int val); ...
https://docs.nvidia.com/cuda/cuda-c-programming-guide/#atomic-functions
atomicAdd для double (CAS-based)
__device__ double atomicAdd(double* address, double val){
unsigned long long int* addr = (unsigned long long int*)address;unsigned long long int old = *addr, assumed;
do {assumed = old;old = atomicCAS(addr, assumed,
__double_as_longlong(val +__longlong_as_double(assumed)));
} while (assumed != old);
return __longlong_as_double(old);}
int atomicCAS(int* address, int compare, int val);
1. Загружает в old значение по адресу address2. Записывает обратно значение:
(old == compare) ? val : old3. Возвращает old
Гистограмма цветов изображения (GPU)
__global__ void hist_gpu_atomic(uint8_t *image, int width, int height, int *hist)
{size_t size = width * height;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < size) {atomicAdd(&hist[image[i]], 1);
}}
Гистограмма цветов изображения (GPU)
__global__ void hist_gpu_atomic(uint8_t *image, int width, int height, int *hist)
{size_t size = width * height;int i = blockIdx.x * blockDim.x + threadIdx.x;int stride = blockDim.x * gridDim.x;
while (i < size) {atomicAdd(&hist[image[i]], 1);i += stride;
}}
Если пикселей больше числа потоков
Глобальная память (Fermi/Kepler)
Compute Capability >= 2.0
Длина строки кеш-памяти L1: 128 байт
Длина строки кеш-памяти L2: 32 байта
Операция чтения данных (load)
o Default: Поиск в L1, затем в L2, далее в GMEM
o Альтернатива (–Xptxas –dlcm=cg): Поиск L2, далее в GMEM
Операция записи данных (store)
Invalidate L1, write-back L2
Операция обращения к памяти (load/store) выполняется всеми потоками группы (warp)
Потоки предоставляют адреса
GPU:
1. Определяет сколько запросов к памяти необходимо выполнить потокам warp’a
2. Группирует запросы (coalesce) в один (1x latency vs. 32x latency)
Поддерживаемые размеры слов: 1, 2, 4, 8 и 16 байт
Адрес слова должен быть выровнен на его размер:
o float – на границу в 4 байта
o double – на границу в 8 байт
cudaMalloc() – адрес корректно выравнен для любого предопределенного типа (по меньшей мере на границу в 256 байт)
Глобальная память
Caching load: aligned data
Потоки группы (warp) запросили 32 смежных корректно выравненных слова по 4 байта
Всего группе требуется: 128 байт
Данные передаются за один запрос из L1
http://gpgpu.org/wp/wp-content/uploads/2013/09/06-opti-memory.pdf
Non-caching load: aligned data
Потоки группы (warp) запросили 32 смежных корректно выравненных слова по 4 байта
Всего группе требуется: 128 байт
Адреса попали в 4 сегмента (четыре L2-строки)
Данные передаются за один запрос
Caching load: aligned data, permuted
Потоки группы (warp) запросили 32 несмежных выравненных слова по 4 байта
Всего группе требуется: 128 байт
Адреса попали в 1 строку L1
Данные передаются за один запрос
Non-caching load: aligned data, permuted
Потоки группы (warp) запросили 32 несмежных выравненных слова по 4 байта
Всего группе требуется: 128 байт
Данные передаются за один запрос
Caching load: misaligned data
Потоки группы (warp) запросили 32 смежных не выравненных слова по 4 байта
Всего группе требуется: 128 байт
Передается 256 байт – две строки L1
Non-caching load: misaligned data
Потоки группы (warp) запросили 32 смежных не выравненных слова по 4 байта
Всего группе требуется: 128 байт
Данные попали в 5 сегментов (5 строк L2)
Передается 160 байт – 5 строк L2
Caching load: same 4 byte
Потоки группы (warp) запросили одно и тоже 4-х байтовое слово
Группе требуется 4 байта
Данные попали в 1 строку L1 (128 байт), передается 128 байт
Non-caching load: same 4 byte
Потоки группы (warp) запросили одно и тоже 4-х байтовое слово
Группе требуется 4 байта
Данные попали в 1 сегмент L2 (32 байта), передается 32 байта
Сaching load: scattered
Потоки группы (warp) запросили 32 слова с шагом > 128 байт
Группе требуется 128 байт
Данные попали в N строк L1 (128 байт), передается 128N байт
Non-caching load: scattered
Потоки группы (warp) запросили 32 слова с шагом > 128 байт
Группе требуется 128 байт
Данные попали в N сегментов L2 (32 байта), передается 32N байт
Пример 1
__global__ void saxpy_1(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
y[i] = y[i] + a * x[i];}
Load y[i], Load x[i], Store y[i]
Количество строк кеш-памяти?
Пример 1
__global__ void saxpy_1(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
y[i] = y[i] + a * x[i];}
Количество строк кеш-памяти?
o Потоки обращаются по последовательным адресам
o Одна загрузка 128 байтовой строки для y
o Одна загрузка 128 байтовой строки для x
o 128 байт на запись y
X
128 байт
Пропускная способность:Bus utilization / bw efficiency =
128 / 128 = 100 %
Пример 2
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
if (i % 2 == 0) y[i] = y[i] + a * x[i];}
Только четные индексы: Load y[i], Load x[i], Store y[i]
Количество строк кеш-памяти?
Пример 2
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
if (i % 2 == 0) y[i] = y[i] + a * x[i];}
X
128 байт
Количество строк кеш-памяти?
o Потоки обращаются по последовательным адресам
o Одна загрузка 128 байтовой строки для y
o Одна загрузка 128 байтовой строки для x
o 128 байт на запись y
Пропускная способность используется менее эффективно:
Bus utilization / bw efficiency = 128 / 64 = 50 %
Пример 3
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
if (i % 2 == 0) y[i] = y[i] + a * x[i];else y[i] = y[i] - a * x[i];
}
Количество строк кеш-памяти?
Пример 3
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N)
if (i % 2 == 0) y[i] = y[i] + a * x[i]; else y[i] = y[i] - a * x[i];
}
Количество строк кеш-памяти?
Первая ветвь (16 потоков): загрузка 2-х строк по 128 байт
Вторая ветвь (16 потоков): данные читаются из кеша
X
128 байт
Пропускная способность используется менее эффективно:
Bus utilization / bw efficiency = 128 / 64 = 50 %
Пример 4
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N) {
float yy = y[i]; float xx = x[i]; // Load to regsif (i % 2 == 0) yy = yy + a * xx;else yy = yy - a * xx; y[i] = yy; // Store
}}
Пример 4
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N) {
float yy = y[i]; float xx = x[i]; // Load to regsif (i % 2 == 0) yy = yy + a * xx;else yy = yy - a * xx; y[i] = yy; // Store
}}
Потоки обращаются по последовательным адресам
Две загрузки 128 байт, одна запись 128 байт
128 байт на запись y
X
128 байт
Пропускная способность:Bus utilization / bw efficiency =
128 / 128 = 100 %
Пример 5
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N / 2)
y[i] = y[i] + a * x[i]else if (i < N)
y[i] = y[i] i a * x[i]}
Пример 5
__global__ void saxpy_2(float* x, float* y, float a, unsigned long N) {
unsigned long i = threadIdx.x + blockDim.x * blockIdx.x;if (i < N / 2)
y[i] = y[i] + a * x[i]else if (i < N)
y[i] = y[i] i a * x[i]}
Все потоки группы (warp) попадают в одну из ветвей: две загрузки 128 байт, запись 128 байт
Потоки группы пересекают границу (N / 2):
o Первая ветвь (16 потоков): загрузка 2-х строк по 128 байт
o Вторая ветвь (16 потоков): данные читаются из кеша
X
128 байт
Пропускная способность:Bus utilization / bw efficiency =
128 / 128 = 100 %
Пропускная способность используется менее
эффективно:Bus utilization / bw efficiency =
128 / 64 = 50 %
Оптимизация структур
Array of Structures (AOS) vs. Structure of Arrays (SOA)
struct point {float x; float y; float z; float w;};struct point *points;cudaMalloc(&(void**)points, N * sizeof(*points));
struct points {float *x; float *y; float *z; float *w;};struct points points;cudaMalloc(&(void**)points.x, N * sizeof(float));cudaMalloc(&(void**)points.y, N * sizeof(float));cudaMalloc(&(void**)points.z, N * sizeof(float));cudaMalloc(&(void**)points.w, N * sizeof(float));
VS.
Оптимизация структур
Array of Structures (AOS) vs. Structure of Arrays (SOA)
struct point {float x; float y; float z; float w;};struct point *points;cudaMalloc(&(void**)points, N * sizeof(*points));
struct points {float *x; float *y; float *z; float *w;};struct points points;cudaMalloc(&(void**)points.x, N * sizeof(float));cudaMalloc(&(void**)points.y, N * sizeof(float));cudaMalloc(&(void**)points.z, N * sizeof(float));cudaMalloc(&(void**)points.w, N * sizeof(float));
VS.
Обращения по несмежным адресам, больше обращений к памяти (строкам)
Обращения по смежным адресам, меньше обращений к памяти (строкам)
Задача N-тел (NBody)
const float eps = 0.0001f;const float dt = 0.01f;const int N = 128 * 1024;
#define coord struct bodystruct body {
float x;float y;float z;
};
__global__ void integrate(coord *new_p, coord *new_v, coord *p, coord *v, int n, float dt)
{int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n)
return;
// Каждый поток вычисляет силу, действующую на тело indexcoord body_pos = p[index]; coord body_vel = v[index]; coord f;f.x = 0;f.y = 0;f.z = 0;
Задача N-тел (NBody)
for (int i = 0; i < n; i++) {coord pi = p[i]; coord r;// Вектор от тела p[i] к телу body_posr.x = pi.x - body_pos.x; r.y = pi.y - body_pos.y; r.z = pi.z - body_pos.z;
float invDist = 1.0 / sqrtf(r.x * r.x + r.y * r.y + r.z * r.z + eps * eps);float s = invDist * invDist * invDist;// Корректируем силу - вносим вклад тела if.x += r.x * s;f.y += r.y * s;f.z += r.z * s;
}
// Корректируем параметры тела: скорость, положениеbody_vel.x += f.x * dt;body_vel.y += f.y * dt;body_vel.z += f.z * dt;body_pos.x += body_vel.x * dt;body_pos.y += body_vel.y * dt;body_pos.z += body_vel.z * dt;
new_p[index] = body_pos;new_v[index] = body_vel;
}
Задача N-тел (NBody)
int main(){
double tgpu = 0, tmem = 0;size_t size = sizeof(coord) * N;coord *p = (coord *)malloc(size); coord *v = (coord *)malloc(size);coord *d_p[2] = {NULL, NULL}; coord *d_v[2] = {NULL, NULL};init_rand(p, N); init_rand(v, N);
tmem = -wtime();cudaMalloc((void **)&d_p[0], size);cudaMalloc((void **)&d_p[1], size);cudaMalloc((void **)&d_v[0], size);cudaMalloc((void **)&d_v[1], size);cudaMemcpy(d_p[0], p, size, cudaMemcpyHostToDevice);cudaMemcpy(d_v[0], v, size, cudaMemcpyHostToDevice);tmem += wtime();
tgpu = -wtime();int threadsPerBlock = 1024;dim3 block(threadsPerBlock);dim3 grid((N + threadsPerBlock - 1) / threadsPerBlock); int index = 0;for (int i = 0; i < 2; i++, index ^= 1)
integrate<<<grid, block>>>(d_p[index ^ 1], d_v[index ^ 1], d_p[index], d_v[index], N, dt);cudaDeviceSynchronize();tgpu += wtime();
Задача N-тел (NBody)
tmem -= wtime();cudaMemcpy(p, d_p[index], size, cudaMemcpyDeviceToHost);cudaMemcpy(v, d_v[index], size, cudaMemcpyDeviceToHost);tmem += wtime();
printf("sizeof(coord) = %d\n", sizeof(coord));printf("GPU version (sec.): %.6f\n", tgpu);printf("Memory ops. (sec.): %.6f\n", tmem);printf(" Total time (sec.): %.6f\n", tgpu + tmem);
cudaFree(d_p[0]);cudaFree(d_p[1]);cudaFree(d_v[0]);cudaFree(d_v[1]);free(p);free(v);cudaDeviceReset();return 0;
}sizeof(coord) = 12GPU version (sec.): 2.382872Memory ops. (sec.): 0.252231Total time (sec.): 2.635103
Задача N-тел (NBody): float3
const float eps = 0.0001f;const float dt = 0.01f;const int N = 128 * 1024;
#define coord float3
__global__ void integrate(coord *new_p, coord *new_v, coord *p, coord *v, int n, float dt)
{int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n)
return;
// Каждый поток вычисляет силу, действующую на тело indexcoord body_pos = p[index]; coord body_vel = v[index]; coord f;f.x = 0;f.y = 0;f.z = 0;
Задача N-тел (NBody): padding
const float eps = 0.0001f;const float dt = 0.01f;const int N = 128 * 1024;
#define coord struct bodystruct body {
float x;float y;float z; float __padding;
};
__global__ void integrate(coord *new_p, coord *new_v, coord *p, coord *v, int n, float dt)
{int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n)
return;
// Каждый поток вычисляет силу, действующую на тело indexcoord body_pos = p[index]; coord body_vel = v[index]; coord f;f.x = 0;f.y = 0;f.z = 0;
sizeof(coord) = 16GPU version (sec.): 1.946044Memory ops. (sec.): 0.228796Total time (sec.): 2.174840
Задача N-тел (NBody): float4
const float eps = 0.0001f;const float dt = 0.01f;const int N = 128 * 1024;
#define coord float4
__global__ void integrate(coord *new_p, coord *new_v, coord *p, coord *v, int n, float dt)
{int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n)
return;
// Каждый поток вычисляет силу, действующую на тело indexcoord body_pos = p[index]; coord body_vel = v[index]; coord f;f.x = 0;f.y = 0;f.z = 0;
sizeof(coord) = 16GPU version (sec.): 1.720787Memory ops. (sec.): 0.226160Total time (sec.): 1.946947
Задача N-тел (NBody): float4 + shared memory
__global__ void integrate(coord *new_p, coord *new_v, coord *p, coord *v, int n, float dt){
int index = blockIdx.x * blockDim.x + threadIdx.x; if (index >= n)
return;
coord body_pos = p[index];coord body_vel = v[index];coord f;f.x = 0;f.y = 0;f.z = 0;
__shared__ coord sp[block_size];
// Assert: n % block_size == 0for (int ind = 0; ind < n; ind += block_size) {
sp[threadIdx.x] = p[ind + threadIdx.x];
__syncthreads();
Задача N-тел (NBody): float4 + shared memory
for (int i = 0; i < block_size; i++) {// Vector from p[i] to bodycoord r;r.x = sp[i].x - body_pos.x; r.y = sp[i].y - body_pos.y; r.z = sp[i].z - body_pos.z;
float invDist = 1.0 / sqrtf(r.x * r.x + r.y * r.y + r.z * r.z + eps * eps);float s = invDist * invDist * invDist;// Add force of body if.x += r.x * s;f.y += r.y * s;f.z += r.z * s;
}__syncthreads();
}// Correct velocitybody_vel.x += f.x * dt; body_vel.y += f.y * dt; body_vel.z += f.z * dt;body_pos.x += body_vel.x * dt; body_pos.y += body_vel.y * dt; body_pos.z += body_vel.z * dt;
new_p[index] = body_pos; new_v[index] = body_vel;}
GPU version (sec.): 2.055868Memory ops. (sec.): 0.249816Total time (sec.): 2.305684
Редукция (reduction)
Задан массив v[0..n - 1] из n элементов и ассоциативная операция
Необходимо вычислить результаты редукции
r = v[0] v[1] … v[n - 1]
Пример:
v[0..5] = [5, 8, 3, 12, 1, 7]
r = ((((5 + 8) + 3) + 12) + 1) + 7 = 36
Последовательная версия (float)
void reduce_cpu(float *v, int n, float *sum){
float s = 0.0;for (int i = 0; i < n; i++)
s += v[i]; *sum = s;
}
int n = 10000;for (size_t i = 0; i < n; i++)
v[i] = i + 1.0;
reduce_cpu(v, n, &sum);
# Real sum = 50005000Sum (CPU) = 50002896.000000, err = 2104.000000
Floating-point Summation // http://www.drdobbs.com/floating-point-summation/184403224
David Goldberg. What Every Computer Scientist Should Know About Floating-Point Arithmetic // ACM Computing Surveys, Vol. 23, #1, March 1991, pp. 5-48.
Последовательная версия (int)
void reduce_cpu(int *v, int n, int *sum){
int s = 0.0;for (int i = 0; i < n; i++)
s += v[i]; *sum = s;
}
Параллельная редукция v1
Каждый блок потоков обрабатывает часть массива v[0..n - 1]
Поток 0 каждого блока сохраняет результат редукции в массив per_block_sum[0..blocks - 1]
Для выполнения редукции массива per_block_sum[] нужен еще один шаг на GPU или CPU:
Block 0 Block 1 Block 2
per_block_sum[]
cudaMemcpy(sums, per_block_sum, sizeof(int) * blocks, cudaMemcpyDeviceToHost);int sum_gpu = 0;for (int i = 0; i < blocks; i++)
sum_gpu += sums[i];https://docs.nvidia.com/cuda/samples/6_Advanced/reduction/doc/reduction.pdf
Параллельная редукция v1
Каждый поток по своему номеру tid определяет с кем ему взаимодействовать
На шаге 1 каждый 2-й поток взаимодействует с соседом справа на расстоянии s = 1
На шаге 2 каждый 4-й поток взаимодействует с соседом справа на расстоянии s = 2
На шаге 3 каждый 8-й поток взаимодействует с соседом справа на расстоянии s = 4
…
Всего шагов O(logn)https://docs.nvidia.com/cuda/samples/6_Advanced/reduction/doc/reduction.pdf
for (int s = 1; s < blockDim.x; s *= 2) {if (tid % (2 * s) == 0)
sdata[tid] += sdata[tid + s];}
Параллельная редукция v1const int block_size = 1024;const int n = 4 * (1 << 20);
int main(){
size_t size = sizeof(int) * n;int *v = (int *)malloc(size);for (size_t i = 0; i < n; i++)
v[i] = i + 1;
int sum;reduce_cpu(v, n, &sum);
/* Allocate on device */int threads_per_block = block_size;int blocks = (n + threads_per_block - 1) / threads_per_block;int *dv, *per_block_sum;int *sums = (int *)malloc(sizeof(int) * blocks);tmem = -wtime();cudaMalloc((void **)&per_block_sum, sizeof(int) * blocks);cudaMalloc((void **)&dv, size);cudaMemcpy(dv, v, size, cudaMemcpyHostToDevice);tmem += wtime();
Параллельная редукция v1
/* Compute per block sum: stage 1 */tgpu = -wtime();reduce_per_block<<<blocks, threads_per_block>>>(dv, n, per_block_sum);cudaDeviceSynchronize();tgpu += wtime();
tmem = -wtime();cudaMemcpy(sums, per_block_sum, sizeof(int) * blocks, cudaMemcpyDeviceToHost);tmem += wtime();
/* Compute block sum: stage 2 */tgpu -= wtime();int sum_gpu = 0;for (int i = 0; i < blocks; i++)
sum_gpu += sums[i];tgpu += wtime();
printf("CPU version (sec.): %.6f\n", tcpu);printf("GPU version (sec.): %.6f\n", tgpu);printf("GPU bandwidth (GiB/s): %.2f\n", 1.0e-9 * size / (tgpu + tmem));printf("Speedup: %.2f\n", tcpu / tgpu);printf("Speedup (with mem ops.): %.2f\n", tcpu / (tgpu + tmem));
Параллельная редукция v1
__global__ void reduce_per_block(int *v, int n, int *per_block_sum){
__shared__ int sdata[block_size];int tid = threadIdx.x;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {sdata[tid] = v[i];__syncthreads();
for (int s = 1; s < blockDim.x; s *= 2) {if (tid % (2 * s) == 0)
sdata[tid] += sdata[tid + s];__syncthreads();
}if (tid == 0)
per_block_sum[blockIdx.x] = sdata[0];}
}
CUDA kernel launch with 4096 blocks of 1024 threadsSum (CPU) = 2097152Sum (GPU) = 2097152CPU version (sec.): 0.005956GPU version (sec.): 0.002198GPU bandwidth (GiB/s): 7.56Speedup: 2.71Speedup (with mem ops.): 2.68
Параллельная редукция v1
__global__ void reduce_per_block(int *v, int n, int *per_block_sum){
__shared__ int sdata[block_size];int tid = threadIdx.x;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {sdata[tid] = v[i];__syncthreads();
for (int s = 1; s < blockDim.x; s *= 2) {if (tid % (2 * s) == 0)
sdata[tid] += sdata[tid + s];__syncthreads();
}if (tid == 0)
per_block_sum[blockIdx.x] = sdata[0];}
}
Результат условия зависит от номера потока – часть потоков деактивируется
(control flow divergence)
Параллельная редукция v2
for (int s = 1; s < blockDim.x; s *= 2) {if (tid % (2 * s) == 0)
sdata[tid] += sdata[tid + s];__syncthreads();
}
for (int s = 1; s < blockDim.x; s *= 2) {int index = 2 * s * tid;if (index < blockDim.x)
sdata[index] += sdata[index + s];__syncthreads();
}
Сontrol flow divergence!
https://docs.nvidia.com/cuda/samples/6_Advanced/reduction/doc/reduction.pdf
Параллельная редукция v2
__global__ void reduce_per_block(int *v, int n, int *per_block_sum){
__shared__ int sdata[block_size];int tid = threadIdx.x;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {sdata[tid] = v[i];__syncthreads();
for (int s = 1; s < blockDim.x; s *= 2) {int index = 2 * s * tid;
if (index < blockDim.x)sdata[index] += sdata[index + s];
__syncthreads();}if (tid == 0)
per_block_sum[blockIdx.x] = sdata[0];}
}
CUDA kernel launch with 4096 blocks of 1024 threadsSum (CPU) = 2097152Sum (GPU) = 2097152CPU version (sec.): 0.006481GPU version (sec.): 0.001968GPU bandwidth (GiB/s): 8.44Speedup: 3.29Speedup (with mem ops.): 3.26
Параллельная редукция v2
__global__ void reduce_per_block(int *v, int n, int *per_block_sum){
__shared__ int sdata[block_size];int tid = threadIdx.x;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {sdata[tid] = v[i];__syncthreads();
for (int s = 1; s < blockDim.x; s *= 2) {int index = 2 * s * tid;
if (index < blockDim.x)sdata[index] += sdata[index + s];
__syncthreads();}if (tid == 0)
per_block_sum[blockIdx.x] = sdata[0];}
}
Обращение к разделяемой памяти выполняется через 32 параллельно функционирующих банка
Каждый банк 4 байта (настраивается)
Номер банка = адрес % 32
Одновременный доступ к одному банку сериализуется!
TID S = 1 S = 2 S = 4
0 0-1 // banks 0, 1 0-2 // banks 0, 1 0-4 // banks 0, 1
1 2-3 // banks 2, 3 4-6 // banks 2, 3
2 4-5 // banks 4, 5 8-10 // banks 4, 5
3 6-7 // banks 6, 7 12-14 // banks 6, 7 24-28 // banks 24, 28
4 8-9 // banks 8, 9 16-18 // banks 8, 9 32-36 // banks 0, 2
…
8 32-34 // banks 0, 2
9 36-38 // banks 4, 6
http://gpgpu.org/wp/wp-content/uploads/2013/09/08-opti-smem-instr.pdf
Shared memory bank conflicts
http://acceleware.com/blog/maximizing-shared-memory-bandwidth-nvidia-kepler-gpus
Параллельная редукция v3
for (int s = blockDim.x / 2; s > 0; s >>= 1) {if (tid < s)
sdata[tid] += sdata[tid + s];__syncthreads();
}
for (int s = 1; s < blockDim.x; s *= 2) {int index = 2 * s * tid;if (index < blockDim.x)
sdata[index] += sdata[index + s];__syncthreads();
}
Bank conflicts
Параллельная редукция v3
CUDA kernel launch with 4096 blocks of 1024 threadsSum (CPU) = 2097152Sum (GPU) = 2097152CPU version (sec.): 0.004661GPU version (sec.): 0.001163GPU bandwidth (GiB/s): 14.17Speedup: 4.01Speedup (with mem ops.): 3.94
__global__ void reduce_per_block(int *v, int n, int *per_block_sum){
__shared__ int sdata[block_size];int tid = threadIdx.x;int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {sdata[tid] = v[i];__syncthreads();
for (int s = blockDim.x / 2; s > 0; s >>= 1) {if (tid < s)
sdata[tid] += sdata[tid + s];__syncthreads();
}if (tid == 0)
per_block_sum[blockIdx.x] = sdata[0];}
}
Conway's Game of Life
Игра «Жизнь» (Game of Life, Дж. Конвей, 1970)
Игровое поле — размеченная на клетки плоскость
Каждая клетка может находиться в двух состояниях:
«живая» и «мёртвая», и имеет восемь соседей
Распределение живых клеток в начале игры называется
первым поколением. Каждое следующее поколение
рассчитывается на основе предыдущего:
1) в мертвой клетке, рядом с которой три живые клетки,
зарождается жизнь
2) если у живой клетки есть две или три живые соседки,
то эта клетка продолжает жить; в противном случае
(соседей < 2 или > 3) клетка умирает
http://en.wikipedia.org/wiki/Conway's_Game_of_Life
Периодические граничные условия (periodic boundary conditions)
Как вычислять состояния граничных ячеек (ячеек слева, справа, снизу, сверху может не существовать)?
Одно из решений –периодические граничные условия (periodic boundary conditions)
Игровое поле бесконечно продолжается по всем направлениям
В массиве требуется хранить теневые ячейки (ghost cells, shadow cells)
https://www.pdc.kth.se/education/tutorials/summer-school/mpi-exercises/mpi-lab-1-program-structure-and-point-to-point-communication-
in-mpi/background-for-the-game-of-life
0
1
N + 1
N
0 1 N + 1N
Последовательная реализация (1_gol/gol.c)
#define IND(i, j) ((i) * (N + 2) + (j))
enum {N = 1024,ITERS_MAX = 1 << 10
};
typedef uint8_t cell_t;
int main(int argc, char* argv[]){
// Grid with periodic boundary conditions (ghost cells)size_t ncells = (N + 2) * (N + 2);size_t size = sizeof(cell_t) * ncells;cell_t *grid = malloc(size);cell_t *newgrid = malloc(size);
// Initial populationsrand(0);for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)grid[IND(i, j)] = rand() % 2;
0
1
N + 1
N
0 1 N + 1N
0
1
N + 1
N
0 1 N + 1N
grid[N + 2][N + 2] newgrid[N + 2][N + 2]
Последовательная реализация (продолжение)
double t = wtime();int iter;for (iter = 0; iter < ITERS_MAX; iter++) {
// Copy ghost columnsfor (int i = 1; i <= N; i++) {
grid[IND(i, 0)] = grid[IND(i, N)]; // left ghost columngrid[IND(i, N + 1)] = grid[IND(i, 1)]; // right ghost column
}// Copy ghost rowsfor (int i = 0; i <= N + 1; i++) {
grid[IND(0, i)] = grid[IND(N, i)]; // top ghost rowgrid[IND(N + 1, i)] = grid[IND(1, i)]; // bottom ghost row
}
0
1
N + 1
N
0 1 N + 1N
Последовательная реализация (продолжение)
for (int i = 1; i <= N; i++) {for (int j = 1; j <= N; j++) {
int nneibs = grid[IND(i + 1, j)] + grid[IND(i - 1, j)] +grid[IND(i, j + 1)] + grid[IND(i, j - 1)] +grid[IND(i + 1, j + 1)] + grid[IND(i - 1, j - 1)] +grid[IND(i - 1, j + 1)] + grid[IND(i + 1, j - 1)];
cell_t state = grid[IND(i, j)];cell_t newstate = state;if (state == 1 && nneibs < 2)
newstate = 0;else if (state == 1 && (nneibs == 2 || nneibs == 3))
newstate = 1;else if (state == 1 && nneibs > 3)
newstate = 0;else if (state == 0 && nneibs == 3)
newstate = 1;newgrid[IND(i, j)] = newstate;
}}cell_t *p = grid; grid = newgrid; newgrid = p;
}t = wtime() - t;
Последовательная реализация (окончание)
size_t total = 0;for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++)total += grid[IND(i, j)];
}printf("Game of Life: N = %d, iterations = %d\n", N, iter);printf("Total alive cells: %lu\n", total);printf("Iters per sec.: %.2f\n", iter / t);printf("Total time (sec.): %.6f\n", t);
free(grid);free(newgrid);return 0;
}
Game of Life: N = 1024, iterations = 1024Total alive cells: 47026Iters per sec.: 280.05Total time (sec.): 3.656496
Cluster Oak / cngpu1 (Intel Core i5-3320M)
Модификация последовательной реализации (2_gol_state)
Game of Life: N = 1024, iterations = 1024Total alive cells: 47026Iters per sec.: 448.07Total time (sec.): 2.285372 Speedup x1.6
Cluster Oak / cngpu1 (Intel Core i5-3320M)
int states[2][9] = {{0, 0, 0, 1, 0, 0, 0, 0, 0}, /* New states for a dead cell */{0, 0, 1, 1, 0, 0, 0, 0, 0} /* New states for an alive cell */
};
for (int i = 1; i <= N; i++) {for (int j = 1; j <= N; j++) {
int nneibs = grid[IND(i + 1, j)] + grid[IND(i - 1, j)] +grid[IND(i, j + 1)] + grid[IND(i, j - 1)] +grid[IND(i + 1, j + 1)] + grid[IND(i - 1, j - 1)] +grid[IND(i - 1, j + 1)] + grid[IND(i + 1, j - 1)];
cell_t state = grid[IND(i, j)];newgrid[IND(i, j)] = states[state][nneibs];
}}
Реализация на CUDA (2_gol_cuda)
#define IND(i, j) ((i) * (N + 2) + (j))enum {
N = 1024,ITERS_MAX = 1 << 10,BLOCK_SIZE = 16
};
typedef uint8_t cell_t;
int main(int argc, char* argv[]){
// Grid with periodic boundary conditions (ghost cells)size_t ncells = (N + 2) * (N + 2);size_t size = sizeof(cell_t) * ncells;cell_t *grid = (cell_t *)malloc(size);
// Initial populationsrand(0);for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)grid[IND(i, j)] = rand() % 2;
Реализация на CUDA (2_gol_cuda)
cell_t *d_grid, *d_newgrid;double tmem = -wtime();cudaMalloc((void **)&d_grid, size);cudaMalloc((void **)&d_newgrid, size);cudaMemcpy(d_grid, grid, size, cudaMemcpyHostToDevice);tmem += wtime();
// 1d drids for copying ghost cellsdim3 block(BLOCK_SIZE, 1, 1);dim3 cols_grid((N + block.x - 1) / block.x, 1, 1);dim3 rows_grid((N + 2 + block.x - 1) / block.x, 1, 1);
// 2d grid for updating cells: one thread per celldim3 block2d(BLOCK_SIZE, BLOCK_SIZE, 1);int nblocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE; dim3 grid2d(nblocks, nblocks, 1);
Реализация на CUDA (2_gol_cuda)
double t = wtime();int iter = 0;for (iter = 0; iter < ITERS_MAX; iter++) {
// Copy ghost cells: 1d grid for rows, 1d grid for columnscopy_ghost_cols<<<cols_grid, block>>>(d_grid, N);copy_ghost_rows<<<rows_grid, block>>>(d_grid, N);
// Update cells: 2d gridupdate_cells<<<grid2d, block2d>>>(d_grid, d_newgrid, N);
// Swap gridscell_t *p = d_grid; d_grid = d_newgrid; d_newgrid = p;
}cudaDeviceSynchronize();t = wtime() - t;
tmem -= wtime();cudaMemcpy(grid, d_grid, size, cudaMemcpyDeviceToHost);tmem += wtime();
Реализация на CUDA (2_gol_cuda)
__global__ void copy_ghost_rows(cell_t *grid, int n){
int i = blockIdx.x * blockDim.x + threadIdx.x;if (i <= n + 1) {
// Bottom ghost row: [N + 1][0..N + 1] <== [1][0..N + 1]grid[IND(N + 1, i)] = grid[IND(1, i)];// Top ghost row: [0][0..N + 1] <== [N][0..N + 1]grid[IND(0, i)] = grid[IND(N, i)];
}}
__global__ void copy_ghost_cols(cell_t *grid, int n){
int i = blockIdx.x * blockDim.x + threadIdx.x + 1;if (i <= n) {
// Right ghost column: [1..N][N + 1] <== [1..N][1]grid[IND(i, N + 1)] = grid[IND(i, 1)];// Left ghost column: [1..N][1] <== [1..N][N]grid[IND(i, 0)] = grid[IND(i, N)];
}}
Реализация на CUDA (2_gol_cuda)
__global__ void update_cells(cell_t *grid, cell_t *newgrid, int n){
int i = blockIdx.y * blockDim.y + threadIdx.y + 1;int j = blockIdx.x * blockDim.x + threadIdx.x + 1;
if (i <= n && j <= n) {int states[2][9] = {
{0, 0, 0, 1, 0, 0, 0, 0, 0}, /* New states for a dead cell */{0, 0, 1, 1, 0, 0, 0, 0, 0} /* New states for an alive cell */
};
int nneibs = grid[IND(i + 1, j)] + grid[IND(i - 1, j)] +grid[IND(i, j + 1)] + grid[IND(i, j - 1)] +grid[IND(i + 1, j + 1)] + grid[IND(i - 1, j - 1)] +grid[IND(i - 1, j + 1)] + grid[IND(i + 1, j - 1)];
cell_t state = grid[IND(i, j)];newgrid[IND(i, j)] = states[state][nneibs];
}}
Реализация на CUDA (2_gol_cuda)
size_t total = 0;for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++)total += grid[IND(i, j)];
}printf("Game of Life: N = %d, iterations = %d\n", N, iter);printf("Total alive cells: %lu\n", total);printf("Iterations time (sec.): %.6f\n", t);printf("GPU memory ops. time (sec.): %.6f\n", tmem);printf("Iters per sec.: %.2f\n", iter / t);printf("Total time (sec.): %.6f\n", t + tmem);
free(grid);cudaFree(d_grid);cudaFree(d_newgrid); return 0;
}
Game of Life: N = 1024, iterations = 1024Total alive cells: 47026Iterations time (sec.): 0.911321GPU memory ops. time (sec.): 0.238265Iters per sec.: 1123.64Total time (sec.): 1.149586 Speedup 1.99
Cluster Oak / cngpu1 (GeForce GTX 680)
Реализация на CUDA v2 (2_gol_cuda_constant)
__constant__ int states[2][9] = {{0, 0, 0, 1, 0, 0, 0, 0, 0}, /* New states for a dead cell */{0, 0, 1, 1, 0, 0, 0, 0, 0} /* New states for an alive cell */
};
__global__ void update_cells(cell_t *grid, cell_t *newgrid, int n){
int i = blockIdx.y * blockDim.y + threadIdx.y + 1;int j = blockIdx.x * blockDim.x + threadIdx.x + 1;
if (i <= n && j <= n) { int nneibs = grid[IND(i + 1, j)] + grid[IND(i - 1, j)] +
grid[IND(i, j + 1)] + grid[IND(i, j - 1)] +grid[IND(i + 1, j + 1)] + grid[IND(i - 1, j - 1)] +grid[IND(i - 1, j + 1)] + grid[IND(i + 1, j - 1)];
cell_t state = grid[IND(i, j)];newgrid[IND(i, j)] = states[state][nneibs];
}}
Реализация на CUDA v2 (2_gol_cuda_constant)
__constant__ int states[2][9] = {{0, 0, 0, 1, 0, 0, 0, 0, 0}, /* New states for a dead cell */{0, 0, 1, 1, 0, 0, 0, 0, 0} /* New states for an alive cell */
};
__global__ void update_cells(cell_t *grid, cell_t *newgrid, int n){
int i = blockIdx.y * blockDim.y + threadIdx.y + 1;int j = blockIdx.x * blockDim.x + threadIdx.x + 1;
if (i <= n && j <= n) { int nneibs = grid[IND(i + 1, j)] + grid[IND(i - 1, j)] +
grid[IND(i, j + 1)] + grid[IND(i, j - 1)] +grid[IND(i + 1, j + 1)] + grid[IND(i - 1, j - 1)] +grid[IND(i - 1, j + 1)] + grid[IND(i + 1, j - 1)];
cell_t state = grid[IND(i, j)];newgrid[IND(i, j)] = states[state][nneibs];
}}
Game of Life: N = 1024, iterations = 1024Total alive cells: 47026Iterations time (sec.): 0.221800GPU memory ops. time (sec.): 0.231555Iters per sec.: 4616.77Total time (sec.): 0.453355 Speedup 5.0
Cluster Oak / cngpu1 (GeForce GTX 680)
CUDA GPU Occupancy Calculatorhttp://developer.download.nvidia.com/compute/cuda/CUDA_Occupancy_calculator.xls
В программеблоки
1d: 1x162d: 16x16
Реализация на CUDA v2 (2_gol_cuda_occupancy)
enum {BLOCK_1D_SIZE = 1024, BLOCK_2D_SIZE = 32
};
int main(int argc, char* argv[]){
// ...// 1d grids for copying ghost cellsdim3 block(BLOCK_1D_SIZE, 1, 1);dim3 cols_grid((N + block.x - 1) / block.x, 1, 1);dim3 rows_grid((N + 2 + block.x - 1) / block.x, 1, 1);
// 2d grid for updating cells: one thread per celldim3 block2d(BLOCK_2D_SIZE, BLOCK_2D_SIZE, 1);int nblocks = (N + BLOCK_2D_SIZE - 1) / BLOCK_2D_SIZE; dim3 grid2d(nblocks, nblocks, 1);// ...
}
Реализация на CUDA v2 (2_gol_cuda_occupancy)
enum {BLOCK_1D_SIZE = 1024, BLOCK_2D_SIZE = 32
};
int main(int argc, char* argv[]){
// ...// 1d grids for copying ghost cellsdim3 block(BLOCK_1D_SIZE, 1, 1);dim3 cols_grid((N + block.x - 1) / block.x, 1, 1);dim3 rows_grid((N + 2 + block.x - 1) / block.x, 1, 1);
// 2d grid for updating cells: one thread per celldim3 block2d(BLOCK_2D_SIZE, BLOCK_2D_SIZE, 1);int nblocks = (N + BLOCK_2D_SIZE - 1) / BLOCK_2D_SIZE; dim3 grid2d(nblocks, nblocks, 1);// ...
}
Game of Life: N = 1024, iterations = 1024Total alive cells: 47026Iterations time (sec.): 0.169622GPU memory ops. time (sec.): 0.238457Iters per sec.: 6036.95Total time (sec.): 0.408079 Speedup 5.7
Cluster Oak / cngpu1 (GeForce GTX 680)