Используем мощное расширение С++ для решения научных проблем

Для более простого написания векторизированного кода, расширения Cilk Plus включают в себя нотацию массивов, которая выходит за пределы стадарта С++. Данный мощный механизм применяется во многих сферах и является ценным инструментом для научных программистов и инженеров.

Нотации массивов являются добавлениями к компилятору Intel, которые не входят в стандарт С++. Они используются при векторизации и многоядерном параллелизме для обеспечения высоко паралелльного и оптимизированного кода.

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


Линейная алгебра

Физика основывается на линейной алгебре. Многие научные проблемы могут быть смоделированы с помощью концептов векторов и матриц. Курсы элементарной физики начинаются с изучения взаимодействия векторов.

В более общем виде, вектор представляет собой набор чисел, расположенных в определенном порядке. В физике с помощью вектора изображается сила, ее направленость и её модуль (который называется значением). Когда вы давите на что-либо, вы прикладываете силу, которая заставляет объект двигаться в определенном направлении. Сила также имеет свое значение. Вы можете изобразить силу графически с помощью стрелки, указывающей в определенном направлении, причём длина данной стрелки будет определять значение силы. Если начало вектора лежит в трехмерном пространстве, то его конец будет иметь координату, состоящую из трех точек. Эти три цифры могут использоваться для полного представления вектора. Таким образом, сила может иметь вектор с такими цифрами:

{ 1.0, 2.0, 3.0 }

Если силы действуют одновременно, чтобы получить результирующую силу, нужно сложить компоненты. Так что если у вас есть вторая сила, которая выглядит следующим образом:

{ 7.0, 9.0, 11.0 } 

И эти силы действуют на объект одновременно, конечная результирующая сила будет являться простой суммой данных компонентов:

{ 1.0 + 7.0, 2.0 + 9.0, 3.0 + 11.0 }

Используя нотацию массива, мы легко можем сложить эти значения и получить новый вектор, совершив лишь одну операцию. Вот часть кода, которая именно это и делает:

float A[3] = { 1.0, 2.0, 3.0 };
float B[3] = { 7.0, 9.0, 11.0 };
float C[3];
C[:] = A[:] + B[:];
printf("{ %.2f, %.2f, %.2f }", C[0], C[1], C[2]);

Четвёртая строка - это ключ. Она складывает все элементы массивов, используя векторизацию, где это возможно. (Мы также должны убедиться в том, что данные выравнены, о чем я расскажу в следующей записи. Обратите внимание на то, что я также использую довольно удобный оператор printf, чтобы можно было сразу отформатировать итог, а не соединять различные вставки cout потоков).


Скалярные произведения

Ещё одной распространенной проблемой в инженерии является понятице скалярного произведения. С их помощью мы можем прийти к более сложным операциям над матрицами, которые также рассмотрим позже. А сейчас я только скажу, что скалярное произведение - это просто действие, когда два соответствующих компонента двух векторов перемножаются, и в результате получается новый вектор, а затем компоненты этого вектора суммируются. Например, если взять наши предыдущие два вектора, сначала мы перемножаем соответствующие значения:

{ 1.0 x 7.0, 2.0 x 9.0, 3.0 x 11.0 }

В результате чего получился такой вектор:

{ 7.0, 18.0, 33.0 }

Затем мы складываем эти значения:

7.0 + 18.0 + 33.0 = 58.0

Кажется, что такие операции можно легко выполнить с помощью Cilk Plus. Но это не так: стандартная библиотека Cilk Plus содержит функцию __sec_reduce_add, которая использует редукцию для параллельного сложения чисел. Мы можем вставить в неё результат операции над массивом:

float x = __sec_reduce_add(A[:] * B[:]);

где A и B имеют значения, определенные нами в вышеуказанном коде. Заметьте, что я делаю всё это в один приём. Операция

A[:] * B[:]

создаёт вектор, компоненты которого являются произведениями соответствующих компонентов в A и B. Далее я вставляю этот вектор в __sec_reduc_add, которая производит сложение всех членов вектора. И вот так мы получаем наш конечный результат. Вот код, который это делает:

float A[3] = { 1.0, 2.0, 3.0 };
float B[3] = { 7.0, 9.0, 11.0 };
float x = __sec_reduce_add(A[:] * B[:]);
printf("%.2f", x);


Вывод

Нотация массива - очень мощный и бесспорно полезный инструмент в приложениях для научных и инженерных исследований.

Джефф КОГСВЕЛЛ (Jeff Cogswell)

Версия для печатиВерсия для печати

Рубрики: 

  • 1
  • 2
  • 3
  • 4
  • 5
Всего голосов: 0
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!