Технология NVIDIA CUDA · Весенний семестр, 2016. ... (Kepler, 2304 cores,...

Preview:

Citation preview

Технология NVIDIA CUDA

Михаил Георгиевич Курносов

Email: mkurnosov@gmail.com

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)

Recommended