Здравствуйие! Я хочу пройти курс Введение в принципы функционирования и применения современных мультиядерных архитектур (на примере Intel Xeon Phi), в презентации самостоятельной работы №1 указаны логин и пароль для доступ на кластер и выполнения самостоятельных работ, но войти по такой паре логин-пароль не получается. Как предполагается выполнение самосоятельных работ в этом курсе? |
Самостоятельная работа 4: Оптимизация расчетов на примере задачи вычисления справедливой цены опциона Европейского типа
Программная реализация
Точка отсчета. Расчет по аналитической формуле
Программную реализацию начнем с написания функции, выполняющей расчет по рассмотренной в разделе 2.3 формуле.
Создадим пустой файл main.cpp и напишем в нем заготовку функции main().
int numThreads = 1; int N = 60000000; int main(int argc, char *argv[]) { int version; if (argc < 2) { printf("Usage: <executable> size version [#of_threads]\n"); return 1; } N = atoi(argv[1]); version = atoi(argv[2]); if (argc > 3) numThreads = atoi(argv[3]); // Здесь будут вызовы функций для разных способов расчета return 0; }
Здесь version – номер модификации, эксперименты с которой мы будем проводить в конкретный момент времени, N – число образцов, numThreads – число потоков в параллельной версии.
Следующим шагом напишем функцию GetOptionPrice() для расчета цены опциона по формуле (5). Отметим, что здесь и во всех остальных функциях мы используем одинарную точность, то есть тип float. В силу специфики данной задачи двойная точность в ней явно избыточна (например, в связи с тем, что входные данные изначально приходят с точностью, для которой типа float достаточно). Более того, далее мы увидим, что точность можно понизить дополнительно.
Функция GetOptionPrice() предваряется объявлением необходимых констант, используемых в расчете. Обращаем внимание на необходимость согласованного объявления данных – поскольку обычно процентная ставка указывается в годовом исчислении, то и время также должно быть в годах.
// Волатильность (% годовых 0.2 -> 20%) const float sig = 0.2f; // Процентная ставка (% годовых 0.05 -> 5%) const float r = 0.05f; // Срок исполнения опциона (в годах) const float T = 3.0f; // Цена акции в момент времени t=0 (у.е.) const float S0 = 100.0f; // Цена исполнения - цена покупки, зафикс. в опционе (у.е.) const float K = 100.0f; float GetOptionPrice() { float C; float d1, d2, p1, p2; d1 = (logf(S0 / K) + (r + sig * sig * 0.5f) * T) / (sig * sqrtf(T)); d2 = (logf(S0 / K) + (r - sig * sig * 0.5f) * T) / (sig * sqrtf(T)); p1 = cdfnormf(d1); p2 = cdfnormf(d2); C = S0 * p1 - K * expf((-1.0f) * r * T) * p2; return C; }
Нетривиальная часть формулы (5) – функция стандартного нормального распределения F. Для ее вычисления можно написать собственный довольно несложный код, но учитывая, что это задача не входит напрямую в данную работу, мы использовали готовое решение – функцию cdfnormf().
Наконец, последнее, что осталось сделать в этой части лабораторной работы, – вызвать функцию GetOptionPrice() из функции main() с выводом результата на печать.
int main(int argc, char *argv[]) { int version; if (argc < 2) { printf("Usage: <executable> size version [#of_threads]\n"); return 1; } N = atoi(argv[1]); version = atoi(argv[2]); if (argc > 3) numThreads = atoi(argv[3]); float res = GetOptionPrice(); printf("%.8f;\n", res); // Здесь будут вызовы функций для разных способов расчета return 0; }
Для сборки разработанного кода используем следующую командную строку:
icc -O2 main.cpp -o option_prices
Выбор структуры хранения
Обсудим вопрос о хранении данных в нашей программе. Казалось бы, в чем проблема? Нужно завести четыре массива (три для хранения исходных данных и один для хранения результатов), а также несколько отдельных переменных. Дело в том, что порядок расположения данных в памяти существенно влияет на производительность программы. Так, во многих задачах, подобных той, что мы решаем, возникает дилемма: использовать набор массивов (паттерн SoA – structure of arrays) или массив структур (паттерн AoS – array of structures). В первом случае данные будут размещены в памяти так: сначала целиком разместится первый массив, потом целиком второй и т.д. Во втором случае сначала будут размещены все данные, относящиеся к первому объекту предметной области, потом – ко второму и т.д.
Вопрос о том, какой из подходов лучше, не имеет однозначного ответа. В некоторых случаях можно дать рекомендации. Так, например, интуитивно понятно, что в случае, если мы работаем с M массивами, из которых три используются в 95% случаев, а оставшиеся M–3 – в 5% случаев, имеет смысл отдельно хранить три массива и оставшиеся M–3 массива. Такой подход позволит локализовать в памяти те данные, с которыми мы чаще всего работаем.
В данной задаче слушателям предлагается выяснить, какой из подходов более эффективен, экспериментальным путем.