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

Самостоятельная работа 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 массива. Такой подход позволит локализовать в памяти те данные, с которыми мы чаще всего работаем.

В данной задаче слушателям предлагается выяснить, какой из подходов более эффективен, экспериментальным путем.

Svetlana Svetlana
Svetlana Svetlana

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