Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методы Защиты Асушников / лабы Васильева / 5 / Ot2[гаммирование с зацепкой].pdf
Скачиваний:
43
Добавлен:
15.06.2014
Размер:
781.47 Кб
Скачать

собой, а для 256 бит он отличен. Далее происходит собственно шифрование/дешифрование на основе предпросчитанных таблиц и расширенного ключа, при этом последний раунд отличается от предыдущих – нет вертикального перемешивания столбцов (оно не увеличивает криптостойкость).

1.2 Оптимизация под CUDA

Алгоритм сам по себе очень просто распараллеливается (зоны шифрования независимы), но для CUDA этого мало – нужно выбрать оптимальное количество тредов в блоке и количество блоков в гриде, выбрать количество используемых регистров в треде, оптимизировать местоположение исходных данных в глобальной памяти для минимизации количества запросов к ней из нитей одного варпа и по возможности задействовать константную память.

1.2.1 Устройство CUDA

На логическом уровне все выглядит следующем образом [6]. Верхний уровень ядра GPU состоит из блоков, которые группируются в сетку или грид (grid) размерностью N1 * N2 * N3, причем у всех выпущенных на данный момент видеокарт N3 равен единице. Это изображено на рисунке 2.

Рисунок 2 – Ядро GPU

Любой блок в свою очередь состоит из тредов (threads), которые являются непосредственными исполнителями вычислений. Треды в блоке сформированы в виде трехмерного массива (рисунок 3).

Рисунок 3 – Блок GPU

Физически вычислительная архитектура CUDA основана на понятии мультипроцессора и концепции одна команда на множество данных (Single Instruction Multiple Data, SIMD) [7].

5

Мультипроцессор – это многоядерный SIMD процессор, позволяющий в каждый определенный момент времени выполнять на всех ядрах только одну инструкцию. Каждое ядро мультипроцессора скалярное, т.е. оно не поддерживает векторные операции в чистом виде.

В мире CUDA под девайсом (device) подразумевается GPU, а под хостом (host) – CPU. Исполняемая на девайсе программа называется ядром. На рисунке 4 показано как выглядит девайс.

Рисунок 4 – Девайс Один блок гарантировано исполняется на одном мультипроцессоре девайса, но один

мультипроцессор может выполнять несколько различных блоков.

Блок задач (тредов) выполняется на мультипроцессоре частями, или пулами, называемыми варпами (warps). Размер варпа на текущий момент в видеокартах с поддержкой CUDA равен 32 тредам. Задачи внутри варпа исполняются в SIMD стиле, т.е. во всех потоках внутри warp одновременно может выполняться только одна инструкция. Тред может быть исполнен только тогда, когда для всех тредов, входящих в варп будут загружены необходимые для вычисления данные.

Здесь следует сделать одну оговорку. В архитектурах, доступных на данный момент времени, количество процессоров внутри одного мультипроцессора равно 8, а не 32. Из этого следует, что не весь warp исполняется одновременно, он разбивается на 4 части, которые

6

выполняются последовательно (т.к. процессоры скалярные). Поэтому пишется, что минимальное время простейших операций 4 такта.

Но, во-первых, разработчики CUDA не регламентируют жестко размер warp. В своих работах они упоминают параметр warp size, а не число 32. Во-вторых, с логической точки зрения именно warp является тем минимальным объединением потоков, про который можно говорить, что все потоки внутри него выполняются одновременно.

Память также разнообразна (рисунок 5).

Рисунок 5 – Структура памяти

Регистровая память (register) является самой быстрой из всех видов [8]. В CUDA нет явных способов использования регистровой памяти, всю работу по размещению данных в регистрах берет на себя компилятор.

Локальная память (local memory) может быть использована компилятором при большом количестве локальных переменных в какой-либо функции, или нечастом использовании какой-либо локальной переменной. Летальность доступа к ней такая же, как и у глобальной памяти (400-600 тактов). Явных средств, позволяющих блокировать использование локальной памяти, не предусмотрено.

Глобальная память (global memory) – самый медленный тип памяти, из доступных GPU и имеет летальность доступа в 400-600 тактов. Глобальная память в основном служит для хранения больших объемов данных, поступивших на device с host’а.

7

Разделяемая память (shared memory) относиться к быстрому типу памяти. Разделяемую память рекомендуется использовать для минимизации обращение к глобальной памяти, а так же для хранения локальных переменных функций. Адресация разделяемой памяти между тредами одинакова в пределах одного блока, что может быть использовано для обмена данными между тредами в пределах одного блока.

Константная память (constant memory) является достаточно быстрой из доступных GPU. Отличительной особенностью константной памяти является возможность записи данных с хоста, но при этом в пределах GPU возможно лишь чтение из этой памяти, что и обуславливает еѐ название. Если необходимо использовать массив в константной памяти, то его размер необходимо указать заранее, так как динамическое выделение в отличие от глобальной памяти в константной не поддерживается.

Текстурная память (texture memory), как и следует из названия, предназначена главным образом для работы с текстурами. Текстурная память имеет специфические особенности в адресации, чтении и записи данных.

1.2.2 Использованные виды оптимизаций

Для расчетов оптимального количества блоков, тредов, регистров и разделяемой памяти используется xls таблица, входящая в состав CUDA SDK.

Одна интересная особенность – для доступа к памяти используется half-warp, то есть в начале к памяти обращаются первые 16 тредов, а затем вторая половина из 16 тредов.

Еще одной особенностью GPU является возможность объединения (coalescing global memory accesses) нескольких запросов в глобальную память в одну операция над блоком (транзакцию) [9]. Подобное объединение запросов в один запрос чтения/записи одного блока длиной 32/64/128 байт может происходить в пределах одного half-warp'а. Для того, чтобы такое объединение произошло, должны быть выполнены специальные требования на то, как отдельные нити half-warp'а обращаются к памяти.

Прежде всего получающийся общий блок обязательно должен быть выровнен в памяти (т.е. его адрес должен быть кратен его размеру). Также должны быть выполнены дополнительные требования, зависящие от Compute Capability GPU.

Для GPU с Compute Capability 1.0 и 1.1 объединения запросов в одну транзакцию будет происходит при выполнении следующих условий:

-треды должны обращаться либо к 32-битовым словам, давая при этом в результате один 64-байтовый блок (транзакцию), либо к 64-битовым словам, давая при этом один 128-байтовый блок (транзакцию);

-все 16 слов должны лежать в пределах данного блока (64 или 128 байт);

-треды должны обращаться к словам последовательно, т.е. k-ый тред должна обращаться к k-му слову (при этом допускается что какие-то треды вообще не производят обращения к соответствующим им словам).

Если треды half-warp'а не удовлетворяют какому-либо из данных условий, то каждое обращение к памяти происходит как отдельная транзакция. На рисунке 6 приводятся типичные паттерны обращения дающие объединения, а на рисунке 7 не дающие объединения.

8

Рисунок 6 – Паттерны обращения к памяти, дающие объединение для Compute Capability 1.0 и 1.1

Рисунок 7 – Паттерны обращения к памяти, не дающие объединение для CC 1.0 и 1.1.

Для GPU с Compute Capability 1.2 и выше объединения запросов в один будет происходить, если слова, к которым идет обращение нитей, лежат в одном сегменте размера 32 байта (если все нити обращаются к 8-битовым словам), 64 байта (если все нити обращаются к 16-битовым словам) и 128 байт (если все нити обращаются к 32-битовым или 64-битовым словам). Получающийся

9

сегмент (блок) должен быть выровнен по 32/64/128 байтам. В этом случае порядок, в котором нити обращаются к словам, не играет никакой роли. Если идет обращения к n соответствующим сегментам, то происходит группировка запросов в n транзакций.

Для того чтобы видеокарты соответствующие Compute Capability 1.0 и 1.1 работали в полную силу необходимо переупорядочить исходные данные перед отправкой их на GPU и использовать специальную функцию для их отправки, которая выровняет данные в 2D массиве. На рисунке 8 изображен цикл, который переупорядочивает данные нужным для нас образом.

for(int i=0;i<4;i++)

for(int j=0;j<16;j++) ibufCu[i*16+j]=ibuf[j*4+i];

Рисунок 8 – Цикл прямого переупорядочивания На рисунке 9 изображен цикл, который переупорядочивает обратно данные после обработки

их на GPU.

for(int i=0;i<4;i++)

for(int j=0;j<16;j++) buf64[j*4+i]=ibufCu[i*16+j];

Рисунок 9 – Цикл обратного переупорядочивания На рисунке 10 изображена функция для инициализации работы с GPU.

#define height 4*8*32 #define width 16

__constant__ uI32 cs_box[256], cft[4][256], cencryption_round_key[64]; uI32 *cuio;

cudaEvent_t syncEvent; size_t tPitch;

void cuda_enc_i(int sizeGPU){

cudaMemcpyToSymbol(cs_box, s_box, 256*sizeof(uI32)); cudaMemcpyToSymbol(cft, ft, 256*4*sizeof(uI32));

cudaMemcpyToSymbol(cencryption_round_key,encryption_round_key,64*sizeof(uI32)); cudaMallocPitch((void**)&cuio, &tPitch, width*sizeof(uI32), height*sizeGPU);

cudaEventCreate(&syncEvent); cudaEventRecord(syncEvent, 0);

}

Рисунок 10 – Инициализация GPU

Функция ―cudaMemcpyToSymbol‖ копирует с хоста в константную память девайса. Функция ―cudaMallocPitch‖ выделяет выровненный для 2D линейный массив в глобальной памяти девайса. Функции семейства ―cudaEvent*‖ используются для определения окончания выполнения ядра и для определения времени его выполнения. На рисунке 11 показано как происходит проверка завершения работы ядра [6].

Рисунок 11 – Синхронизация девайса и хоста

10

Существуют и другой способ синхронизации, но он вызывает 100% загрузку CPU.

После инициализации GPU можно начинать обработку при помощи функции, изображенной на рисунке 12.

void cuda_encrypt(uI32 *_io, int sizeGPU, int sizeCPU)

{

cudaMemcpy2DAsync(cuio, tPitch, _io, width*sizeof(uI32), width*sizeof(uI32), height*sizeGPU, cudaMemcpyHostToDevice,0);

encrypt<<<32,dim3(16, 8, 1),0,0>>>(cuio, sizeGPU); cpu_encrypt(_io+width*height*sizeGPU, sizeCPU);

cudaEventSynchronize(syncEvent);

cudaMemcpy2D(_io, width*sizeof(uI32), cuio, tPitch, width*sizeof(uI32), height*sizeGPU, cudaMemcpyDeviceToHost);

}

Рисунок 12 – Начало шифрования

Функция ―cudaMemcpy2D*‖ копирует с автовыравниванием линейный 2D массив. Функции ―*Async‖ выполняют свои действия асинхронно, в данном случае программа продолжит свое выполнение не дожидаясь окончания копирования. При вызове ядра указывается количество блоков в гриде, тредов в блоке, дополнительной разделяемой памяти и номер потока, эти параметры заключены в ―<<<>>>‖. При помощи ―dim3(16,8,1)‖ указываем размерность блока

(16x8x1).

Одновременно с выполнением шифрования на GPU начинается шифрование на CPU, т.к. в нашем случае GPU обрабатывает данные кратные 64 килобайтам, и оставшийся кусок идет на CPU. Это увеличивает утилизацию ресурсов системы.

По окончании всех операций незабываем вызвать функцию, изображенную на рисунке 13.

void cuda_enc_e()

{

cudaEventDestroy(syncEvent);

cudaFree(cuio);

}

Рисунок 13 – Освобождаем ресурсы Важно помнить, что после освобождения ресурсов глобальной памяти (включая память

констант и текстур) они не обнуляются и с вероятностью 98% они останутся нетронутыми до следующего запуска приложения. Так что ключ будет свободно доступен до следующей перезагрузки компьютера.

Сама шифрующая функция изображена на рисунке 14.

11

Соседние файлы в папке 5