Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 30.05.2014 | Доступ: свободный | Студентов: 302 / 34 | Длительность: 11:26:00

Самостоятельная работа 2: Оптимизация прикладных программ для Intel Xeon Phi с использованием Intel C/C++ Compiler. Векторизация

Использование Array notation

Иногда специфика алгоритма позволяет в явном виде записать его как последовательность операций над векторами. В этом случае использование расширения Intel Cilk Plus под названием Array notation позволяет получить высокую производительность и, в частности, автоматически векторизовать код.

Расширение Array notation позволяет записывать выражения и операции, исполняемые над несколькими элементами вектора, без написания цикла. Для этого вводится операция :, при ее использовании в индексе соответствующая операция применяется ко всем элементам указанного диапазона. В общем случае выражения имеют вид: A[start_index : length] , где первый параметр обозначает начальный индекс, а второй – количество элементов. Если оба параметра опущены, подразумевается весь массив.

Для простейшего примера сложения двух векторов запись через Array notation имеет следующий вид:

float A[100], B[100], C[100];
A[:] = B[:] + C[:];

Для рассматриваемого класса алгоритмов работа с Array notation интуитивно проста: бинарные операции выполняются поэлементно, вызов функций осуществляется для каждого элемента массива. Кроме того, поддерживаются специальные функции для выполнения редукции и других широко используемых операций. Подробнее использование Array notation рассматривается в "Элементы оптимизации прикладных программ для Intel Xeon Phi. Intel C/C++ Compiler " .

Вернемся к примеру из предыдущего раздела. Основной цикл имеет вид:

for (int i = 0; i < n; i++)
{
    a[i] = b[i] * c[i];
    c[i] = a[i] + b[i] - d[i];
}

Данную операцию можно записать в векторном виде с использованием Array notation следующим образом:

#include <cilk\cilk.h>
void vectorization_array(float* a, float* b, float* c,
                         float* d, int n)
{
    a[0:n] = b[0:n] * c[0:n];
    c[0:n] = a[0:n] + b[0:n] - d[0:n];
}

Данный код будет автоматически векторизован. Использование Array notation может приводить к повышению производительности и из-за лучшей оптимизации кода.

Использование элементарных функций

Одним из требований для автоматической векторизации кода является отсутствие вызовов функций внутри векторизованного кода (не считая функций, встроенных компилятором). В некоторых ситуациях не получается обеспечить автоматическое встраивание, например, из-за сложной структуры или большого объема функции. Ручное встраивание сложных функций часто нежелательно с точки зрения качества кода и удобства поддержки.

В этом случае для векторизации кода можно использовать механизм элементарных функций (Elemental functions) из Intel Cilk Plus. Векторизуемая операция выносится в функцию со специальной нотацией __declspec(vector) . В теле функции выполняются операции над одним элементом данных, а сама функция может вызываться для нескольких элементов данных одновременно при векторном исполнении цикла. На подобные функции накладывается ряд ограничений, они рассмотрены в "Элементы оптимизации прикладных программ для Intel Xeon Phi. Intel C/C++ Compiler " .

В рассматриваемом примере создадим элементарную функцию, выполняющую одну итерацию цикла:

#include <cilk\cilk.h>
__declspec(vector) void f(float* a, float* b,
                          float* c, float* d, int n, int i)
{
    a[i] = b[i] * c[i];
    c[i] = a[i] + b[i] - d[i];
} 
void vectorization_elemental(float* a, float* b, float* c,
                             float* d, int n)
{
    for (int i = 0; i < n; i++)
        f(a, b, c, d, n, i);
}

Векторизация циклов с вызовами математических функций

Рассмотрим следующий пример цикла с вызовом функции для вычисления экспоненты:

void exp_loop(float* a, float* b, float* c,
              float* d, int n)
{
    #pragma ivdep
    for (int i = 0; i < n; i++)
        a[i] = b[i] + c[i] + expf(d[i]);
}

Использование #pragma ivdep предотвращает проблему с потенциальными зависимостями, аналогичную возникавшей в рассмотренном ранее примере.

Отчет о векторизации показывает, что данный цикл векторизуется. Однако в процессоре нет векторных команд для вычисления экспоненты (в отличие от векторных команд для сложения или умножения). Тогда каким образом была произведена векторизация?

Ответ состоит в том, что компиляторы Intel содержат библиотеку реализаций математических функций для короткого вектора аргументов SVML (short vector math library). В случае векторизации цикла компилятор вставляет вызовы функций SVML. Если по каким-то причинам цикл не векторизуется, то вставляются вызовы обычных реализаций из скалярной библиотеки математических функций LibM.

При большом количестве итераций цикла может иметь смысл предварительно вычислить значения математических функций сразу для всех итераций цикла (если их аргументы известны заранее). Для такого случая идеально подходит библиотека VML (vector math library), являющаяся частью Intel MKL (math kernel library). При ее использовании код примет вид:

void exp_loop_vml(float* a, float* b, float* c,
                  float* d, int n)
{
    vsExp(n, d, d);
    #pragma ivdep
    for (int i = 0; i < n; i++)
        a[i] = b[i] + c[i] + d[i];
}

Оба варианта векторизуют и вычисление экспоненты, и цикл со сложением, поэтому являются достаточно эффективными и существенно превосходят невекторизованную версию. То, какая из них является более эффективной, зависит от количества итераций цикла n. В таких случаях наблюдается следующее поведение: при малом количестве итераций цикла порядка десятков или сотен, предпочтительна версия с использованием SVML, при значительном количестве итераций цикла предпочтительна версия с использованием VML. Данное соотношение зависит от используемого процессора, модели компилятора и других факторов, эмпирическая проверка для различного числа итераций цикла оставляется читателю на самостоятельную работу.

Svetlana Svetlana
Svetlana Svetlana

Здравствуйие! Я хочу пройти курс Введение в принципы функционирования и применения современных мультиядерных архитектур (на примере Intel Xeon Phi), в презентации самостоятельной работы №1 указаны логин и пароль для доступ на кластер и выполнения самостоятельных работ, но войти по такой паре логин-пароль не получается. Как предполагается выполнение самосоятельных работ в этом курсе?