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

Самостоятельная работа 4: Оптимизация расчетов на примере задачи вычисления справедливой цены опциона Европейского типа

Версия 7.1. Прогрев

Времена в последнем эксперименте стали достаточно малы, поэтому накладные расходы, связанные с созданием потоков, могут вносить существенный вклад в общее время работы параллельного кода. Попробуем от них избавиться. Подход основан на том факте, что большинство реализаций стандарта OpenMP не уничтожают потоки, созданные в начале некоторой параллельной секции, по ее окончании, а переводят их в состояние сна, вывод из которого занимает существенно меньше времени. Соответственно, если вызвать функцию GetOptionPricesV7() дважды подряд в функции main(), то во втором вызове накладные расходы будут минимальны. В некоторых источниках такой подход называется "прогрев".

Обсудим, является ли честным такой способ оценки производительности. Что касается исключения накладных расходов на создание потоков, сомнений нет. Дело в том, что в реальных программах мы всегда можем организовать однократное создание потоков, в результате чего все последующие параллельные расчеты в рамках конкретной программы будут лишены соответствующих накладных расходов. Однако есть еще один важный момент. Дополнительное уменьшение времени может быть получено за счет так называемого "прогрева" кэша. При повторном вызове функции GetOptionPricesV7() часть необходимых ей данных может остаться в кэше, что может ускорить ее работу. Хорошо это или плохо? С одной стороны – плохо или, скорее, не совсем честно. Известно, что типичная ошибка – проведение в рамках одного процесса экспериментов с однопоточной, двухпоточной и четырехпоточной версиями программы, работающими с одними и теми же данными. В результате обычно получается сверхлинейное ускорение, которое немедленно исчезает, когда экспери менты ставятся корректно – каждый в своем процессе. С другой стороны, в реальных программах выполняется много расчетов, и данные постепенно подгружаются в кэш.

В целом, наша рекомендация выглядит следующим образом:

  1. В случае если речь идет о больших временах, измеряющихся хотя бы в секундах, "прогрев" использовать не нужно.
  2. В случае если времена очень малы (доли секунды), а количество потоков велико, имеет смысл использовать "прогрев", в противном случае мы будем измерять не время работы алгоритма, а объем накладных расходов. При этом если мы считаем, что дополнительный "прогрев" кэша в нашей задаче будет смещать результат, изменяя истинное положение вещей, мы можем реализовать специальный параллельный цикл, который ничего полезного не делает, но приведет к созданию потоков. При этом данные заранее подгружаться в кэш не будут.

Проведем эксперименты с функциями GetOptionPricesV6() и GetOptionPricesV7().

Модифицируем функцию main() следующим образом.

int main(int argc, char *argv[])
{
  ...
  start = omp_get_wtime();
  GetOptionPrices[version](pT, pK, pS0, pC);
  finish = omp_get_wtime();
  t = finish - start;
  printf("v%d: %.8f; %lf\n", version, pC[0], t);

  start = omp_get_wtime();
  GetOptionPrices[version](pT, pK, pS0, pC);
  finish = omp_get_wtime();
  t = finish - start;
  printf("    %.8f; %lf\n", pC[0], t);
  ...
  return 0;
}

Соберите программу и проведите эксперименты.

На описанной ранее инфраструктуре авторы получили следующие времена.

Таблица 9.12. Время работы версий с 0 до 7.1 (в секундах), 16 ядер
N 60 000 000 120 000 000 180 000 000 240 000 000
Версия V0 17,002 34,004 51,008 67,970
Версия V1 16,776 33,549 50,337 66,989
Версия V2 2,871 5,727 8,649 11,230
Версия V3 0,522 1,049 1,583 2,091
Версия V4 0,521 1,036 1,566 2,067
Версия V5 0,527 1,047 1,580 2,085
Версия V6 0,538 1,071 1,614 2,133
Версия V6.1 0,539 1,072 1,617 2,135
Версия V6.2 0,438 0,871 1,314 1,724
Версия V7 0,058 0,084 0,126 0,153
Версия V6.3 0,409 0,812 1,226 1,603
Версия V7.1 0,033 0,062 0,091 0,118

Как видно из таблицы 9.12, ускорение версии 7.1 по отношению к версии 6.3 существенно выше, чем было у версии 7 по отношению к 6.2, и составляет от 12.54 (60 млн. образцов) до 13,61 (240 млн. образцов). Также видно, что версия 6.3 примерно на 7,5% быстрее, чем версия 6.2, а версия 7.1 – на треть быстрее, чем версия 7 (кроме эксперимента с 60 млн. образцов, где разница составляет более 70%, что неудивительно для такого малого объема вычислений).

Эксперименты на сопроцессоре Xeon Phi

Мощь сопроцессора Xeon Phi проявляется при использовании большого числа потоков (от 60 до 240), при этом каждое отдельное ядро существенно уступает в производительности ядрам процессора Xeon. Таким образом, сравнение имеет смысл проводить, начиная с версии 7. Однако, чтобы картина была полной, рекомендуем читателям провести эксперименты и с начальными версиями. В частности, можно увидеть, какие результаты дает векторизация кода при переходе от версии 2 к версии 3.

Здесь же мы приведем результаты экспериментов, начиная с версии 6, взятой нами за базу для процессора Xeon.

Для сборки программы под сопроцессор Xeon Phi добавьте в командную строку компилятора ключ -mmic.

Также не забудьте увеличить величину выравнивания в функции memalign() с 32 до 64.

Соберите программу и проведите эксперименты.

На описанной ранее инфраструктуре авторы получили следующие времена.

Таблица 9.13. Время работы на сопроцессоре Xeon Phi версий с 6 до 6.3 (в секундах)
N 60 000 000 120 000 000 180 000 000 240 000 000
Версия V6 1,544 3,089 4,633 6,174
Версия V6.1 1,545 3,091 4,634 6,179
Версия V6.2 0,676 1,352 2,027 2,703
Версия V6.3 0,422 0,845 1,269 1,690

Как видно из таблицы 9.13 на сопроцессоре Xeon Phi переход к вычислениям с пониженной точностью (версия 6.2) дает выигрыш не на 23%, как это было на процессоре, а почти в 2,3 раза. Также видно, что более значительный прирост (порядка 60%) дает прогрев, что неудивительно, принимая во внимание тот факт, что мы создаем на порядок больше потоков. Наконец, можно отметить, что времена работы версии 6.3. на сопроцессоре практически сравнялись с временами работы на процессоре. Теперь посмотрим на результаты работы параллельных версий.

Таблица 9.14. Время работы на сопроцессоре Xeon Phi версий с 7 до 7.1 (в секундах), 60 поток
N 60 000 000 120 000 000 180 000 000 240 000 000
Версия V7 0,134 0,149 0,164 0,175
S(V6.2/V7) 5,0336 9,050 12,331 15,437
Версия V7.1 0,008 0,017 0,025 0,033
S(V6.3/V7.1) 50,585 51,178 51,783 51,546
Таблица 9.15. Время работы на сопроцессоре Xeon Phi версий с 7 до 7.1 (в секундах), 120 потоков
N 60 000 000 120 000 000 180 000 000 240 000 000
Версия V7 0,234 0,255 0,257 0,255
S(V6.2/V7) 2,885 5,303 7,883 10,590
Версия V7.1 0,007 0,014 0,021 0,028
S(V6.3/V7.1) 59,422 59,587 60,389 59,839
Таблица 9.16. Время работы на сопроцессоре Xeon Phi версий с 7 до 7.1 (в секундах), 240 потоков
N 60 000 000 120 000 000 180 000 000 240 000 000
Версия V7 0,532 0,527 0,533 0,558
S(V6.2/V7) 1,269 2,564 3,800 4,842
Версия V7.1 0,008 0,016 0,024 0,031
S(V6.3/V7.1) 53,286 54,248 53,969 53,964

Как видно из табл. 9.14-9.16, версия 7 ускоряется весьма плохо. Как и для центрального процессора, накладные расходы на работу с потоками оказываются сопоставимыми с общим временем работы программы. В то же время за вычетом расходов на работу с потоками (версия 7.1) ускорение получается вполне неплохим (от 50,5 до 60,4). Также видим, что при запуске в 120 потоков ускорение выше, чем при запуске в 60 потоков, что согласуется с техническими особенностями исполнения кода на Xeon Phi.

Однако результаты на Xeon Phi можно улучшить.

Svetlana Svetlana
Svetlana Svetlana

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