[МУЗЫКА]
[МУЗЫКА]
Приветствую вас.
Во 2-й лекции этого модуля я советовал вам всячески
перекладывать работу по векторизации кода на компилятор.
Для этого в компиляторе Intel есть опция О2, которая включает автовекторизацию.
И к нашему с вами счастью, компилятор все лучше и лучше справляется с этой работой.
Одним из основных способов использования векторных инструкций для языков C,
C++ и Fortran, когда вы программируете для центрального процессора или сопроцессора
Xion Phi, является векторизация циклов.
Под векторизацией циклов понимается одновременное выполнение
нескольких итераций цикла за счет использования векторных инструкций.
Но, конечно, не все циклы можно векторизовать.
Компилятор проверяет условия возможности векторизации и целесообразности.
Если оба этих пункта выполнены, то он производит векторизацию.
Давайте обратимся к первому, очень простому примеру,
который отражает наиболее часто встречающуюся ситуацию в программах.
>> В данном случае мы видим, что это достаточно простая программа.
У нас имеется всего 4 массива длины n.
И далее идет цикл, который заполняет данный массив.
Массив K1 и K2 заполняется случайными целыми числами от 0 до 9.
А массивы L1 и L2 заполняются в зависимости от того,
какие значения приняли элементы массивов K1 и K2.
Если K1 > K2, то тогда элементу массива L1 присваивается K1.
Иначе элементу массива L2 присваивается значение K2.
Ну и дальше они выводятся.
Давайте проверим, произведена ли векторизация данных циклов при компиляции.
Воспользуемся компилятором Intel.
Соглашаемся.
Далее, заходим в «Свойства» и во вкладке «C/C++»,
«Диагностика» выбираем и выставляем уровень диагностики 2.
Он позволяет нам увидеть, какие циклы были векторизованы,
а какие циклы не были векторизованы.
Итак, компилируем, собираем наше приложение.
И видим, что в данном случае цикл,
который заполняет у нас массивы,
не был векторизован.
Это произошло из-за того, что у нас здесь встречается дважды функция rand.
Она не является векторной и она мешает векторизации данного цикла.
Что от нас требуется?
Давайте мы разобьем этот цикл на 2 независимых цикла.
Итак, в 1-м цикле у нас останется, соответственно,
заполнение массивов K1 и K2.
А во втором цикле у нас будет заполнение массивов L1 и L2.
По сути, мы немножко сделали больше программу,
но итог у нас будет один и тот же.
Давайте теперь попробуем собрать наше приложение, посмотрим, что произошло.
Как видите, в новых версиях достаточно удобно.
Вся диагностика выводится непосредственно в исходный текст программы.
Хотя можно его посмотреть так же и в нижнем окне,
где выводятся все диагностические сообщения.
Итак, у нас теперь есть два цикла.
1-й цикл, где у нас генерируются случайные числа и заполняются массивы K1 и K2,
также не был векторизован.
Опять же из-за того, что у нас функция rand не векторная.
А второй цикл, как мы видим, был векторизован,
и теперь все операции в нем будут векторные,
что позволяет надеяться, что данный цикл будет выполняться быстрее.
Как видите, иногда компилятору не просто нужно подсказать, что цикл можно
векторизовать, а нужно переписать программу, чтобы он смог ее векторизовать.
А теперь пример посложнее.
В нем используется не совсем обычная обработка массива,
которая встретилась в реальном приложении.
Здесь очень простой пример.
Взят он из реального приложения, но, конечно же, достаточно сильно упрощен.
Итак, у нас имеется массив, под который выделяется здесь память.
Память выделяется размера n умножить на n.
Далее, данный массив заполняется единицами, и идет основной цикл по i,
который осуществляет обработку данного массива.
Внешний цикл по K моделирует ситуацию реального приложения,
когда у нас массив обрабатывается не один раз, а несколько.
Пока здесь стоит единица, для того чтобы в дальнейшем сравнить
результаты последовательной и векторной версии.
Здесь осуществляется вывод массива на экран.
Итак, выбираем компилятор Intel.
Соглашаемся.
И для того чтобы увидеть, был ли векторизован цикл или нет,
«Свойства проекта», во вкладке «CC++», вкладка «Диагностика».
Выставляем уровень диагностики 2.
И теперь собираем наше приложение.
Итак, приложение было собрано.
Выведем теперь диагностическую информацию.
Самый первый цикл, простой, где было заполнение, как видим, был векторизован.
Все замечательно.
Теперь смотрим на основной цикл.
Цикл не был векторизован.
Компилятор предполагает зависимость по данным.
Давайте запустим приложение.
Оно выполняется, все замечательно.
Но и видим здесь значение данного массива.
Итак, мы выяснили, что цикл не был векторизован.
На первый взгляд сложно сказать, можно ли что-то здесь сделать.
Давайте нарисуем схему взаимодействия элементов, например,
для n = 6, перебрав все возможные значения для переменной i.
Итак, элемент с номером 6 зависит от элементов с номерами 3 и 4,
элемент с номером 7 от элементов с номерами 4 и 5.
То есть элементы с номерами с 6 по 11
зависят от элементов с номерами на 2 и 3 меньше, чем их номер.
Элементы с номерами с 11 по 17 зависят от элементов с
номерами на 3 и 4 номера меньше, чем их собственный номер.
И так далее.
Как видите, у группы, состоящей из n элементов,
однотипный доступ к необходимым элементам.
Давайте перейдем от одного цикла к двум
циклам по переменным i и j и избавимся от условного оператора.
Я специально не стал упрощать выражение,
чтобы вам было проще разобраться с данными циклами.
Итак, как я сказал уже до этого, мы перейдем к 2-м циклам, по i и по j.
Видите, мы сделали здесь 2 цикла и таким образом избавились от условного оператора.
Давайте сначала мы запустим нашу программу, посмотрим, все ли верно.
Итак, результат преобразованной версии и результат исходной версии.
Как видите, значения у нас совпадают, значит расчеты правильны.
Давайте теперь посмотрим,
был ли данный цикл после всех этих преобразований векторизован.
Перестраиваем наше приложение.
И компилятор добавил диагностическую информацию.
Итак, нас интересует основной цикл, как видим,
он попытался создать 2 версии для данного цикла.
Первая версия не была векторизована, опять остается зависимость по данным,
и вторая версия также не была векторизована.
Мы избавились от условного оператора.
Но, как вы видели, все равно компилятор не смог провести векторизацию данного цикла.
Если мы проанализируем, что происходит, то выясним,
переменная i отображает следующее: она является минимальным расстоянием между
вычисляемым элементом и элементами, от которых вычисляемый элемент зависит.
Поэтому для переменной i больше 3,
можно провести векторизацию внутреннего цикла по j.
Для этого мы воспользуемся новой директивой из OpenMP, директивой Sind.
Я уже сказал, что для цикла по i, при котором i больше или равно 3,
мы можем провести векторизацию.
Для этого этот цикл я разбил на 2.
Для i = 1 и 2, и для i от 3 до n.
Соответственно, в данном цикле мы можем векторизовать цикл по j.
Для этого мы воспользуемся директивой Omp sind.
Записываем #pragma omp sind.
Для того чтобы подсказать компилятору,
что минимальное расстояние между независимой операцией n3,
воспользуемся опцией safelan, для того чтобы он мог генерировать корректный код,
если вдруг векторные регистры будет у нас большей длины на новых архитектурах.
Для того чтобы данная директива работала, нужно включить поддержку OpenMp,
заходим в «Свойства проекта», вкладка «С/С++», «Language Intel C+»,
«С++» и включаем поддержку по OpenMP.
Собираем наше приложение и видим,
что после вывода диагностической информации наш цикл по j был векторизован.
Давайте теперь проверим, корректно ли работает эта программа, векторизованная.
Запускаем ее [НЕРАЗБОРЧИВО].
Итак.
Самая первая версия нашей программы.
Результаты работы второй версии программы после перехода к двум циклам.
И результаты работы 3-ей программы, когда мы провели векторизацию.
Как видите, результаты все совпадают.
Давайте теперь оценим и сравним время выполнения исходной программы и
векторизованной.
Для этого выбираем первый наш проект,
делаем его стартовым, открываем исходный файл,
и n задаем равное 10 тысяч.
А внешний цикл по k должен выполниться 100 раз.
Для того чтобы...
мы будем смотреть на большой вывод,
вывод всего массива, мы его закомментируем.
Итак, собираем наше приложение.
Отлично оно собралось.
Для оценки времени воспользуемся инструментом от Intel,
это Intel parallel amplifier, анализ basic hotspots.
Об этом инструменте я буду рассказывать в следующем модуле.
Запускаем его.
Для того чтобы не ждать, видео сейчас я ускорю.
Итак, программа подсчитала.
Время выполнения 19,6 секунды.
Давайте то же самое сейчас проделаем с векторизованной версией.
Делаем данный проект стартовым.
В исходном файле n задачем равным 10 тысяч,
это самое условие, и внешний цикл до 100.
Ну и комментируем вывод на экран.
Компилируем наше приложение и опять
запускаем basic hotspots analysis.
Итак, программа отработала,
и время ее выполнения составило 8,7 секунды.
Напомним, что время исходное программы было 19,6 секунды.
То есть мы ускорили почти в 2.5 раза за счет использования векторизации.
>> Как видите, иногда не так-то просто провести векторизацию, и нам пришлось
вручную ему подсказывать и говорить, что нужно сделать здесь векторизацию.
Но это нам позволило значительно повысить эффективность программы и, соответственно,
сократить время расчетов.
В этом модуле мы с вами рассмотрели, как производить векторизацию, что это такое.
И благодаря использованию векторизации вы можете использовать
более эффективно компьютеры при параллельных расчетах.
В следующем модуле мы рассмотрим несколько программ,
которые позволяют упростить создание многопоточных приложений.
До встречи!