Программа состоит из множества инструкций к микропроцессору. Микропроцессор способен изменить порядок их выполнения, выделить среди инструкций группы, которые можно исполнять параллельно. При этом, результат вычислений не изменится. Так же микропроцессор способен предсказывать следующие инструкций. Ниже мы рассмотрим почему это бывает существенно.
Данный уровень параллелизма эффективен в случае если вы хотите выжать максимум из своего железа. Однако, код после этого становится не переносимым.
В большинстве современных ПК рассматривается следующая иерархия памяти:
- Регистры процессора, организованные в регистровый файл — наиболее быстрый доступ (порядка 1 такта), но размером лишь в несколько сотен или, редко, тысяч байт.
- Кэш процессора 1го уровня (L1) — время доступа порядка нескольких тактов, размером в десятки килобайт
- Кэш процессора 2го уровня (L2) — большее время доступа (от 2 до 10 раз медленнее L1), около полумегабайта или более
- Кэш процессора 3го уровня (L3) — время доступа около сотни тактов, размером в несколько мегабайт (в массовых процессорах используется недавно)
- ОЗУ системы — время доступа от сотен до, возможно, тысячи тактов, но огромные размеры в несколько гигабайт, вплоть до сотен. Время доступа к ОЗУ может варьироваться для разных его частей в случае комплексов класса NUMA (с неоднородным доступом в память)
- Дисковое хранилище — многие миллионы тактов, если данные не были закэшированны или забуферизованны заранее, размеры до нескольких терабайт
Операция | Такты | Время мс | Отношение |
---|---|---|---|
Один такт процессора с частотой 3 ГГц | 1 | ||
Обращение в кэш перого уровня L1 | 0.5 | ||
Неверно предсказанный переход | 5 | ||
Обращение в кэш второго уровня L2 | 7 | 14x L1 | |
Захват мьютекса | 100 | ||
Обращение в оперативную память | 100 | 20x L2 , 200x L1 | |
Snappy сжатие 1 Кбайта данных | 3000 | ||
Передача 2 Кбайт через 1 Гбит | 10000 | 0.01 | |
Прочитать произвольные 4 Кбайт данных с SSD | 150000 | 0.15 | |
Прочитать последовательно 1 Мбайт данных из памяти | 250000 | 0.25 | |
Прочитать последовательно 1 Мбайт данных с SSD | 1000000 | 1 | 4x память |
Прочитать последовательно 1 Мбайт данных из сети | 10000000 | 10 | |
Прочитать последовательно 1 Мбайт данных с диска | 20000000 | 20 | 80x память, 40х SSD |
Использование пары или большего количества физических процессоров в одной компьютерной системе. Термин также относится к способности системы поддержать больше чем один процессор и/или способность распределить задачи между ними. Существует много вариантов данного понятия, и определение многопроцессорности может меняться в зависимости от контекста, главным образом в зависимости от того, как определены процессоры (много ядер в одном кристалле, множество чипов в одном корпусе, множество корпусов в одном системном модуле, и т. д.).
Сама парадигма приложений использующих графические ускорители появилась в 2003 году. В основном этим занимались энтузиасты. Однако, компания NVidia рассмотрела в этом потенциал. Потенциал так же увидели ряд нефтяных компаний, перед которыми стояли проблемы обработки больших объемов данных и масштабных расчетов. Эти компании так же могли инвестировать средства в разработку.
После создания первых карт для вычислений, встал вопрос создания удобного языка программирования для GPU. Добавив расширение для языка программирования С и С++. Программирование для графических процессоров вышло на новый уровень. Резко возросло количество научных публикаций. Применение GPU стало распространятся на многие сферы. Именно момент внесения расширения для языков программирования, можно считать рождением архитектуры CUDA для GPU.
Компания NVidia так же развивает свои библиотеки для удобства разработки под эти устройства, в каждую новая редакцию библиотек вносятся новые алгоритмы и более оптимальные решения старых. Например, библиотека cuBLAS. cuBLAS это реализация библиотеки BLAS(Basic Linear Algebra Subprograms- Базовые Подпрограммы Линейной Алгебры) стандарт де-факто интерфейса программирования приложений для создания библиотек, выполняющих основные операции линейной алгебры, такие как умножение векторов и матриц.
Кроме того, центральные процессоры используют SIMD (одна инструкция выполняется над многочисленными данными) блоки для векторных вычислений, а видеочипы применяют SIMT (одна инструкция и несколько потоков) для скалярной обработки потоков. SIMT не требует, чтобы разработчик преобразовывал данные в векторы, и допускает произвольные ветвления в потоках.
Так же стоит отметить при использовании GPU разработчику доступно несколько видов памяти: регистры, локальная, глобальная, разделяемая, константная и текстурная память. Каждая из этих типов памяти имеет определённое назначение, которое обуславливается её техническими параметрами (скорость работы, уровень доступа на чтение и запись).
Количество ядер в последних версих графических процессоров может достигать 2496.
Операция транспонирование матрицы выглядит следующим образом,
а функция транспонированная на CUDA.
__global__ void transposeNaive(float *odata, const float *idata)
{
int x = blockIdx.x * TILE_DIM + threadIdx.x;
int y = blockIdx.y * TILE_DIM + threadIdx.y;
int width = gridDim.x * TILE_DIM;
for (int j = 0; j < TILE_DIM; j+= BLOCK_ROWS)
odata[x*width + (y+j)] = idata[(y+j)*width + x];
}
Конечно, это не самый удачный вариант можно воспользоваться библиотечными функциями.
Ниже приведена архитектура устройства:
В основе архитектуры Intel MIC (большое количечство ядер в одной микросхеме) лежит классическая архитектура x86, на ускорителе исполняется ОС Linux. Для программирования MIC предполагается использовать OpenMP, OpenCL, Intel Cilk Plus, специализированные компиляторы Intel Fortran, Intel C++. Также предоставляются математические библиотеки.
Сопроцессоры Intel Xeon Phi(архитектура Intel MIC) могут иметь до 61 ядра, 244 потоков и обеспечивать производительность до 1,2 терафлопс. Они представлены во множестве конфигураций для удовлетворения различных требований к аппаратным и программным средствам, рабочим нагрузкам, производительности и эффективности.
Вы наверняка заметили, что у Intel Xeon Phi гораздо меньше ядер, чем у обычного графического процессора. Но нельзя сравнивать ядро MIC с CUDA в соотношении один к одному. Одно ядро Intel Xeon Phi – это четырёхпоточный модуль с 512-бит SIMD. Для верного сопоставления не стоит обращать внимание на маркетинговое определение понятия "ядра".
Нужно отметить тот факт, что написать приложение для архитектуры Intel MIC, проще чем на CUDA. Однако, нужно приложить усилия при написания программ, что бы достигнуть максимальной производительности.
//FTYPE = float
#pragma omp parallel for
for (int i = 0; i < n; i++)
for (int j = 0; j<i; j++)
{
const FTYPE c = A[i*n + j];
A[i*n + j] = A[j*n + i];
A[j*n + i] = c;
}
будет приведена к следующему виду
//FTYPE = float
void Transpose(FTYPE* const A, const int n, const int* const plan)
{
// nEven is a multiple of TILE
const int nEven = n - n%TILE;
const int wTiles = nEven / TILE; // Complete tiles in each dimens.
const int nTilesParallel = wTiles*(wTiles - 1)/2; // # блоков матриц
#pragma omp parallel
{
#pragma omp for schedule(guided)
for (int k = 0; k < nTilesParallel; k++)
{
//Для матриц больших размерностей большая часть работы будет в данном цикле
const int ii = plan[2*k + 0];
const int jj = plan[2*k + 1];
// Транспонирование блоков
for (int j = jj; j < jj+TILE; j++)
{
#pragma simd
for (int i = ii; i < ii+TILE; i++)
{
const FTYPE c = A[i*n + j];
A[i*n + j] = A[j*n + i];
A[j*n + i] = c;
}
}
}
//Транспонирование блоков вдоль главной диагонали
#pragma omp for schedule(static)
for (int ii = 0; ii < nEven; ii += TILE)
{
const int jj = ii;
for (int j = jj; j < jj+TILE; j++)
#pragma simd
for (int i = ii; i < j; i++)
{
const FTYPE c = A[i*n + j];
A[i*n + j] = A[j*n + i];
A[j*n + i] = c;
}
}
// Транспонирование "Шубы" правой и нижней части матрицы.
#pragma omp for schedule(static)
for (int j = 0; j < nEven; j++)
{
for (int i = nEven; i < n; i++)
{
const FTYPE c = A[i*n + j];
A[i*n + j] = A[j*n + i];
A[j*n + i] = c;
}
}
}
// Транспонирование нижнего правого угла
for (int j = nEven; j < n; j++)
{
for (int i = nEven; i < j; i++)
{
const FTYPE c = A[i*n + j];
A[i*n + j] = A[j*n + i];
A[j*n + i] = c;
}
}
}
Второй вариант работает более чем в 2 раза быстрее на сопроцессоре Intel Xeon Phi (архитектура Intel MIC). Данная операция была исследована Андреем Владимировым из Colfax Reseach (США)
Ниже приведена архитектура устройства:
Двунаправленная кольцевая шина обеспечивает передачу данных между компонентами сопроцессора. Восемь контроллеров памяти обслуживают 16 каналов GDDR5. Каждый контроллер памяти включает два независимых канала доступа к памяти, при этом все контроллеры памяти сопроцессора действуют независимо друг от друга.