Здравствуйие! Я хочу пройти курс Введение в принципы функционирования и применения современных мультиядерных архитектур (на примере Intel Xeon Phi), в презентации самостоятельной работы №1 указаны логин и пароль для доступ на кластер и выполнения самостоятельных работ, но войти по такой паре логин-пароль не получается. Как предполагается выполнение самосоятельных работ в этом курсе? |
Самостоятельная работа 4: Оптимизация расчетов на примере задачи вычисления справедливой цены опциона Европейского типа
Версия 7.1. Прогрев
Времена в последнем эксперименте стали достаточно малы, поэтому накладные расходы, связанные с созданием потоков, могут вносить существенный вклад в общее время работы параллельного кода. Попробуем от них избавиться. Подход основан на том факте, что большинство реализаций стандарта OpenMP не уничтожают потоки, созданные в начале некоторой параллельной секции, по ее окончании, а переводят их в состояние сна, вывод из которого занимает существенно меньше времени. Соответственно, если вызвать функцию GetOptionPricesV7() дважды подряд в функции main(), то во втором вызове накладные расходы будут минимальны. В некоторых источниках такой подход называется "прогрев".
Обсудим, является ли честным такой способ оценки производительности. Что касается исключения накладных расходов на создание потоков, сомнений нет. Дело в том, что в реальных программах мы всегда можем организовать однократное создание потоков, в результате чего все последующие параллельные расчеты в рамках конкретной программы будут лишены соответствующих накладных расходов. Однако есть еще один важный момент. Дополнительное уменьшение времени может быть получено за счет так называемого "прогрева" кэша. При повторном вызове функции GetOptionPricesV7() часть необходимых ей данных может остаться в кэше, что может ускорить ее работу. Хорошо это или плохо? С одной стороны – плохо или, скорее, не совсем честно. Известно, что типичная ошибка – проведение в рамках одного процесса экспериментов с однопоточной, двухпоточной и четырехпоточной версиями программы, работающими с одними и теми же данными. В результате обычно получается сверхлинейное ускорение, которое немедленно исчезает, когда экспери менты ставятся корректно – каждый в своем процессе. С другой стороны, в реальных программах выполняется много расчетов, и данные постепенно подгружаются в кэш.
В целом, наша рекомендация выглядит следующим образом:
- В случае если речь идет о больших временах, измеряющихся хотя бы в секундах, "прогрев" использовать не нужно.
- В случае если времена очень малы (доли секунды), а количество потоков велико, имеет смысл использовать "прогрев", в противном случае мы будем измерять не время работы алгоритма, а объем накладных расходов. При этом если мы считаем, что дополнительный "прогрев" кэша в нашей задаче будет смещать результат, изменяя истинное положение вещей, мы можем реализовать специальный параллельный цикл, который ничего полезного не делает, но приведет к созданию потоков. При этом данные заранее подгружаться в кэш не будут.
Проведем эксперименты с функциями 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; }
Соберите программу и проведите эксперименты.
На описанной ранее инфраструктуре авторы получили следующие времена.
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.
Соберите программу и проведите эксперименты.
На описанной ранее инфраструктуре авторы получили следующие времена.
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. на сопроцессоре практически сравнялись с временами работы на процессоре. Теперь посмотрим на результаты работы параллельных версий.
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 |
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 |
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 можно улучшить.