Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах Лекция 2 Томский политехнический университет Аксёнов Сергей Владимирович к.т.н., доцент каф.ОСУ ТПУ Оценка времени исполнения ядра на GPU-1 cudaEventCreate(*cudaEvent_t event ) – создание события event cudaEventRecord(cudaEvent_t, cudaStream stream) – привязка события event к текущей позиции в потоке cudaStream cudaEventSynchronize(cuda_Event_t event) – ожидание завершения работы GPU. Синхронизация по событию event. cudaEventElapsedTime(float * ms, cudaEvent_t start, cudaEvent_t stop); cudaEventDestroy(cudaEvent_t event) – Удаление события event Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 2 Оценка времени исполнения ядра на GPU-2 cudaEvent_t start, stop; float gpu_time = 0; cudaEventCreate(&start); cudaEventCreate(&stop); cudaEventRecord(start, 0); cudaKernel<<blocks, threads>>(args); cudaEventRecord(stop, 0); cudaEventSynchronize(stop); cudaEventElapsedTime(&gpu_time, start, stop); printf(“Время исполнения ядра:%f мс.”,gpu_time); cudaEventDestroy(start); cudaEventDestroy(stop); Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 3 Иерархия памяти Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 4 Огранизация памяти в GPU Тип памяти Расположение Доступ для GPU Уровень доступа Время жизни Регистры Мультипроцессор R/W Нить Нить Локальная DRAM Нить Нить Разделяемая Мультипроцессор R/W Все нити блока Блок Глобальная DRAM R/W Все нити и CPU Выделяется CPU Константная DRAM R/O Все нити и CPU Выделяется CPU Текстурная DRAM R/O Все нити и CPU Выделяется CPU R/W Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 5 Оптимизация производительности Для каждого потока можно оптимизировать: чтение операндов выполнение инструкции запись результата Для повышения производительности необходимо: уменьшить число арифметических инструкций с низкой скоростью работы максимизировать использование доступной пропускной способности для каждого типа памяти Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 6 Производительность арифметических инструкций 4 clock cycles: floating point сложение, умножение, умножение-сложение (multiply-add) integer сложение 24-bit integer умножение (__mul24) побитовые операции, сравнение, минимум, максимум, преобразование типов 16 clock cycles: вычисление обратного числа, 1 / sqrt(x), __logf(x) умножение 32-bit integers Целочисленное деление и взятие остатка по модулю следует заменять битовыми операциями везде, где это возможно. 32 clock cycles: sinf(x), __cosf(x), __expf(x) (доступны только из кода, выполняемого на устройстве) Рекомендуется использовать везде, где это возможно, floating point данные и floating point версии арифметических функций Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 7 Пример использования константной памяти __constant__ int maxValueDev; __constant__ int dataDev[10]; __global__ void KernelFunction(int *a, int *b, int *c) { int index = blockIdx.x*blockDim.x + threadIdx.x; if (a[index]>maxValueDev) { int factor = index % 10; c[index] = a[index] * dataDev[factor] - b[index]; } } void main { … int dataRAM[10]; for (int i=0; i<10; i++) dataRAM[i] = i; int maxValueRAM = 5; cudaMemcpyToSymbol(dataDev, dataRAM, 10*sizeof(int), 0, cudaMemcpyHostToDevice); cudaMemcpyToSymbol(&maxValueDev, &maxValueRAM, sizeof(int), 0, cudaMemcpyHostToDevice); … KernelFunction<<<blocks, threads>>>(dev_a, dev_b, dev_c); … } Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 8 Оптимизация работы с глобальной памятью: выравнивание хранения данных struct myStruct { float x; //4 bytes short y; //2 bytes int z; //4 bytes }; //10 bytes struct __align__(16) myStruct() { float x; //4 bytes short y; //2 bytes int z; //4 bytes }; //10 bytes Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 9 Оптимизация работы с глобальной памятью: Объединение запросов нитей полугруппы Требования к объединению запросов нитей полугруппы: 1.Все нити обращаются к 32 битовым словам (всего обращения дают блок 64 байта) или все нити обращаются к 64 битовым словам (блок обращения 128 байт). 2.Получившийся блок выровнен по своему размеру, адрес блока 64 байта кратен 64, адрес блока 128 байт кратен 128. 3.Все слова, к которым обращаются нити лежат в пределах этого блока 4.Все нити обращаются последовательно. Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 10 Оптимизация работы с глобальной памятью: Оптимум Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 11 Оптимизация работы с памятью: Не оптимально Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 12 Транспонирование матрицы: глобальная память __global__ void transposeMatrixSlow(float* inputMatrix, float* outputMatrix, int width, int height) { int xIndex = blockDim.x * blockIdx.x + threadIdx.x; int yIndex = blockDim.y * blockIdx.y + threadIdx.y; if ((xIndex < width) && (yIndex < height)) { //Линейный индекс элемента строки исходной матрицы int inputIdx = xIndex + width * yIndex; //Линейный индекс элемента столбца матрицы-результата int outputIdx = yIndex + height * xIndex; } } outputMatrix[outputIdx] = inputMatrix[inputIdx]; Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 13 Оптимизация работы с разделяемой памятью: Оптимум Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 14 Оптимизация работы с разделяемой памятью: Не оптимально Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 15 Транспонирование матрицы: разделяемая память __global__ void transposeMatrixFast(float* outputMatrix, int width, int height) { __shared__ float temp[BLOCK_DIM][BLOCK_DIM]; int xIndex = blockIdx.x * blockDim.x + threadIdx.x; int yIndex = blockIdx.y * blockDim.y + threadIdx.y; inputMatrix, float* if ((xIndex < width) && (yIndex < height)) { int idx = yIndex * width + xIndex; temp[threadIdx.y][threadIdx.x] = inputMatrix[idx]; } __syncthreads(); xIndex = blockIdx.y * blockDim.y + threadIdx.x; yIndex = blockIdx.x * blockDim.x + threadIdx.y; if ((xIndex < height) && (yIndex < width)) { int idx = yIndex * height + xIndex; outputMatrix[idx] = temp[threadIdx.x][threadIdx.y]; } } Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 16 Результаты: Транспонирование матрицы 2048 x 1536 Технологии высокопроизводительных вычислений на GPU и гибридных вычислительных системах - Лекция 2 17