|
Здравствуйие! Я хочу пройти курс Введение в принципы функционирования и применения современных мультиядерных архитектур (на примере 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 массива. Такой подход позволит локализовать в памяти те данные, с которыми мы чаще всего работаем.
В данной задаче слушателям предлагается выяснить, какой из подходов более эффективен, экспериментальным путем.