SlideShare a Scribd company logo
Параллельные алгоритмы обработки данных
Андрей Карпов, Антон Миронов
Декабрь 2008
Аннотация
Статья посвящена вопросам использования параллельных алгоритмов для создания современных
эффективных программных решений. Актуальность данной тематики обусловлена снижением темпов роста
тактовой частоты микропроцессоров и возрастанием внимания к использованию всех возможностей
многоядерных и многопроцессорных систем. В работе рассмотрен ряд базовых параллельных алгоритмов,
таких как умножение матриц, параллельная сортировка Бэтчера, метод Гаусса решения систем линейных
алгебраических уравнений и так далее. Приведена реализация этих алгоритмов c использованием языка
программирования Си++.
Введение
В ближайшее время под эффективным использованием аппаратных средств компьютера будут пониматься
применение параллельных алгоритмов. Это связано с замедлением темпов роста тактовой частоты
микропроцессоров и быстрым распространением многоядерных микропроцессоров.
Создание эффективных программных решений для параллельных систем складывается из трех основных
компонентов: параллельных алгоритмов, средств реализации параллельности и систем отладки.
Под средствами реализации параллельности понимаются языки программирования или библиотеки,
обеспечивающие инфраструктуру параллельных программ. Таких система достаточно много. К ним можно
отнести: Occam, MPI, HPF, OpenMP, DVM, OpenTS, Boost.Thread, Posix Threads. Сюда также можно отнести
библиотеку Integrated Performance Primitives компании Intel.
Поскольку отладка параллельной программы является процессом более трудоемким, чем отладка
последовательной программы, то системы отладки и профилирования являются важнейшей частью в процессе
разработки таких систем. В области отладчиков хочется обратить внимание на системы TotalView и PGDBG.
Среди средств профилирования можно назвать такие инструменты как Nupshot, Pablo, Vampir. Существуют
системы статической верификации, например VivaMP. Последней инструмент, на который хочется обратить
особое внимание, является средства анализа многопоточности Threading Analysis Tools.
Но самый важный компонент, без которого все другие средства не смогут сделать программу параллельной,
это параллельный алгоритм. Именно этому компоненту и будет посвящена эта статья.
В статье будут рассмотрены несколько базовых параллельных алгоритмов, имеющих практическое значение
во многих областях, в том числе в экономике, в численном моделировании и в задачах реального времени.
Для некоторых алгоритмов приведены оценки времени выполнения последовательной и параллельной
версий алгоритма и выведены коэффициенты ускорения. Приведен пример реализации описанных
алгоритмов c использованием языка программирования Си++ в среде Windows.
1. Параллельная сортировка Бэтчера
Алгоритм сортировки Бэтчера не является наиболее эффективным алгоритмом сортировки, однако он
обладает одним важным компенсирующим качеством: все сравнения и/или обмены, определяемые данной
итерацией алгоритма можно выполнять одновременно. С такими параллельными операциями сортировка
осуществляется за . Например, 1024 элемента можно рассортировать методом Бэтчера
всего за 55 параллельных шагов. Схема сортировки Бэтчера несколько напоминает сортировку Шелла, но
сравнения выполняются по-новому, а потому цепочки операций обмена записей не возникает. Поскольку в
алгоритме Бэтчера, по существу, происходит слияние пар рассортированных подпоследовательностей, его
можно назвать обменной сортировкой со слиянием.
Алгоритм Бэтчера (обменная сортировка со слиянием). Записи перекомпоновываются в пределах того
же пространства в памяти. После завершения сортировки их ключи будут упорядочены: .
Предполагается, что .
1)[начальная установка .] Установить , где — наименьшее целое число, такое, что .
(Шаги 2-5 будут выполняться с .)
2)[начальная установка .] Установить .
3)[цикл по .] Для всех , таких, что и , выполнять шаг 4.
Затем перейти к шагу 5. (Здесь через обозначена операция "поразрядное логическое И" над
представлениями целых чисел и ; все биты результата равны 0, кроме тех битов, для которых в
соответствующих разрядах и находятся 1. Так . К этому моменту —
нечётное кратное (т.е. частное от деления на нечётно), а — степень двойки, так что .
Отсюда следует, что шаг 4 можно выполнять при всех нужных значениях в любом порядке или даже
одновременно.)
4)[Сравнение/обмен .] Если , поменять местами записи .
5)[Цикл по .] Если , установить и возвратиться к шагу 3.
6)[Цикл по ] (К этому моменту перестановка будет -упорядочена.) Установить .
Если , возвратиться к шагу 2.
////////////////////////////////////////
// Параллельная сортировка Бэтчера
#include <stdio.h>
#include <stdlib.h>
#define N 16
int Arr[N];
void FillArray()
{
for (unsigned i = 0; i < N; i++)
Arr[i] = rand() % 10;
}
void PrintArray()
{
for (unsigned i = 0; i < N; i++)
printf("%d ", Arr[i]);
printf("n");
}
void BatcherSort()
{
unsigned p = N;
while (p > 0)
{
unsigned q = N, r = 0, d = p;
bool b;
do
{
unsigned nTo = N - d;
for (unsigned i = 0; i < nTo; i++)
if ((i & p) == r)
{
if (Arr[i] > Arr[i + d])
{
int temp = Arr[i];
Arr[i] = Arr[i + d];
Arr[i + d] = temp;
}
}
b = q != p;
if (b)
{
d = q - p;
q >>= 1;
r = p;
}
} while (b);
p >>= 1;
}
}
int main(int argc, char* argv[])
{
FillArray();
PrintArray();
BatcherSort();
PrintArray();
return 0;
}
2. Вычисление корня алгебраического или трансцендентного уравнения
Пусть дано уравнение . Требуется на отрезке найти корень при условии . Для
построения алгоритма ветви используется метод хорд, заключающийся в следующем (рисунок N1).
Кривая заменяется хордой , определяется
Рисунок 1. Метод хорд.
точка , после чего проводится хорда определяется и т. д., пока .
Для реализации задачи на процессоров, т.е. для построения параллельного алгоритма из ветвей,
отрезок разбивается на подотрезков на границах которых вычисляется . Это определяет
подотрезок длиной , содержащий корень . Точки и — соседние, в них значения функции
имеют разные знаки (рисунок N1).
После отрезка рассматривается отрезок , где
(1)
Отрезок разбивается на частей, повторяется описанная процедура, получается отрезок и т. д.,
пока длина отрезка не станет меньше заданного значения. Параллельный счет оправдан, когда объём
вычислений значения намного превосходит объём вычислений границ отрезка по (1).
Аналогичный результат получается и при использовании метода Ньютона.
////////////////////////////////////////
// Параллельное вычисление корня уравнения
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <math.h>
#define NumberOfProcessors 3
float Func(float arg)
{
return arg * (arg - 5) + 6;
}
template <typename t> inline int sgn(t arg)
{
return arg < 0 ? -1 : (arg > 0 ? 1 : 0);
}
float x1 = 2.5;
float x2 = 10;
float e = 1;
float M = 5;
#define EPSILON 1e-4
enum Commands { Continue = 0, Stop = 1 };
int Action = Continue;
HANDLE hEventStart[NumberOfProcessors];
HANDLE hEventFinish[NumberOfProcessors];
float Values[NumberOfProcessors];
unsigned __stdcall ThreadFunction(void *pData)
{
unsigned Branch = (unsigned)pData;
for (;;)
{
WaitForSingleObject(hEventStart[Branch], INFINITE);
if (Action == Stop)
return 0;
Values[Branch] = Func(x1 + Branch * (x2 - x1) /
(float)(NumberOfProcessors - 1));
SetEvent(hEventFinish[Branch]);
}
return 0;
}
int main(int argc, char* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
HANDLE hThreads[NumberOfProcessors];
unsigned i, tid;
float Result = 0.0f;
for (i = 0; i < NumberOfProcessors; i++)
{
hEventStart[i] = CreateEvent(NULL, FALSE,
TRUE, NULL);
hEventFinish[i] = CreateEvent(NULL, FALSE,
FALSE, NULL);
}
for (i = 0; i < NumberOfProcessors; i++)
hThreads[i] =
(HANDLE)_beginthreadex(NULL, 0, ThreadFunction,
(void *)i, 0, &tid);
for (;;)
{
WaitForMultipleObjects(NumberOfProcessors,
hEventFinish,
TRUE, INFINITE);
int Sign = sgn(Values[0]);
if (Sign == 0)
{
Result = x1;
Action = Stop;
}
if (Action != Stop)
for (i = 1; i < NumberOfProcessors; i++)
{
int NewSign = sgn(Values[i]);
if (NewSign == 0)
{
Result = x1 + i * (x2 - x1) /
(float)(NumberOfProcessors - 1);
Action = Stop;
break;
}
if (NewSign != Sign)
{
float a = x1 + (i - 1) * (x2 - x1) /
(float)(NumberOfProcessors - 1);
float b = x1 + i * (x2 - x1) /
(float)(NumberOfProcessors - 1);
x1 = a + (Values[i - 1] * (b - a)) /
(Values[i] - Values[i - 1]);
x2 = b - Values[i] / M;
e = b - a;
break;
}
}
if (e < EPSILON)
{
Result = (x2 + x1) / 2;
Action = Stop;
}
for (i = 0; i < NumberOfProcessors; i++)
SetEvent(hEventStart[i]);
if (Action == Stop)
break;
}
printf("%7.4f", Result);
for (i = 0; i < NumberOfProcessors; i++)
{
CloseHandle(hThreads[i]);
CloseHandle(hEventStart[i]);
CloseHandle(hEventFinish[i]);
}
return 0;
}
3. Решение системы линейных алгебраических уравнений методом простой
итерации
Система уравнений размерности записывается в виде , где — матрицы;
и — векторы; — единичная матрица.
Последовательные приближения вычисляются по формуле , начиная с некоторого исходного
приближения . Процесс завершается, если . Последовательность в методе простой
итерации сходится, если для матрицы выполняется одно из неравенств
Применим к матрице и векторам и распределение горизонтальными полосами. Для нахождения
в соответствующей ветви необходимы строка матрицы , вектор и значение . Поэтому можно
обеспечить каждую ветвь всеми необходимыми данными для автономной работы. Отсюда
где — число строк матрицы и компонентов векторов и , обрабатываемых отдельной ветвью, —
время обмена данными, — время выполнения сложения, — время выполнения умножения.
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <math.h>
#define N 4
#define NumberOfProcessors 2
#define EPSILON 1e-5
float Matrix[N][N + 1] =
{
{ 1.0f, 0.1f, 0.1f, 0.1f, 1.0f },
{ 0.1f, 1.2f, 0.1f, 0.1f, 1.0f },
{ 0.2f, 0.1f, 1.0f, 0.1f, 1.0f },
{ 0.2f, 0.2f, 0.2f, 1.0f, 1.0f }
};
float NewX[N];
float CurX[N] = { 1.0, 1.0, 1.0, 1.0 };
void InitializeMatrix()
{
printf("Source Matrixn");
for (unsigned i = 0; i < N; i++)
{
for (unsigned j = 0; j <= N; j++)
{
// calculate (E - A)
if (i == j)
Matrix[i][j] = 1.0f - Matrix[i][j];
else
if (j < N)
Matrix[i][j] = -Matrix[i][j];
printf("%7.4f ", Matrix[i][j]);
}
printf("n");
}
}
enum Commands { Continue, Stop };
struct IterState
{
HANDLE Starters[NumberOfProcessors];
HANDLE evIterationFinished[NumberOfProcessors];
};
IterState State;
unsigned Command = Continue;
unsigned __stdcall ThreadFunction(void *pData)
{
unsigned Branch = (unsigned)pData;
unsigned nFrom = Branch * N / NumberOfProcessors;
unsigned nTo = (Branch + 1) * N / NumberOfProcessors;
unsigned i;
for (;;)
{
WaitForSingleObject(State.Starters[Branch], INFINITE);
if (Command == Stop)
return 0;
for (i = nFrom; i < nTo; i++)
{
NewX[i] = Matrix[i][0] * CurX[0];
for (unsigned j = 1; j < N; j++)
NewX[i] += Matrix[i][j] * CurX[j];
NewX[i] += Matrix[i][N];
}
// synchronization
SetEvent(State.evIterationFinished[Branch]);
if (Branch == 0)
{
WaitForMultipleObjects(NumberOfProcessors,
State.evIterationFinished, TRUE, INFINITE);
float d = -1;
for (i = 0; i < N; i++)
if (fabs(NewX[i] - CurX[i]) > d)
d = (float)fabs(NewX[i] - CurX[i]);
if (d < EPSILON)
Command = Stop;
memcpy(CurX, NewX, sizeof(NewX));
for (i = 0; i < NumberOfProcessors; i++)
SetEvent(State.Starters[i]);
}
}
}
int main(int argc, char* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
InitializeMatrix();
HANDLE hThreads[NumberOfProcessors];
unsigned i, tid;
for (i = 0; i < NumberOfProcessors; i++)
{
State.Starters[i] =
CreateEvent(NULL, FALSE, TRUE, NULL);
State.evIterationFinished[i] =
CreateEvent(NULL, FALSE, FALSE, NULL);
}
for (i = 0; i < NumberOfProcessors; i++)
hThreads[i] = (HANDLE)_beginthreadex(
NULL, 0, ThreadFunction,
(void *)i, 0, &tid);
WaitForMultipleObjects(NumberOfProcessors, hThreads,
TRUE, INFINITE);
for (i = 0; i < NumberOfProcessors; i++)
{
CloseHandle(State.Starters[i]);
CloseHandle(State.evIterationFinished[i]);
CloseHandle(hThreads[i]);
}
for (i = 0; i < N; i++)
printf("x%d = %7.4fn", i, NewX[i]);
return 0;
}
4. Решение системы линейных уравнений методом Гаусса
Метод Гаусса основан на последовательном исключении неизвестных. Пусть дана система линейных
алгебраических уравнений
Вычтем из второго уравнения системы первое, умноженное на такое число, чтобы уничтожился коэффициент
при . Затем таким же образом вычтем первое уравнение из третьего, четвёртого и т.д. Тогда исключатся все
коэффициенты первого столбца, лежащие ниже главной диагонали. Затем при помощи второго уравнения
исключим из третьего, четвёртого и т.д. уравнений коэффициенты второго столбца. Последовательно
продолжая этот процесс, исключим из матрицы все коэффициенты, лежащие ниже главной диагонали.
Запишем общие формулы процесса. Пусть проведено исключение коэффициентов из столбца. Тогда остались
такие уравнения с ненулевыми коэффициентами ниже главной диагонали:
Умножим k-ю строку на число
и вычтем из m-й строки. Первый элемент этой строки обратится в нуль, а остальные изменятся по формулам
Производя вычисления по этим формулам при всех указанных индексах, исключим элементы k-го столбца.
Будем называть такое исключение циклом процесса. Выполнение всех циклов называется прямым ходом
исключения. После выполнения прямого хода получим треугольную систему
с матрицей
Далее треугольная система решается обратным ходом по формулам
Оценим время выполнения описанного алгоритма. Не нарушая общности, будем предполагать, что первый
элемент первой строки не равен нулю и элементы на главной диагонали в ходе расчёта также не обращаются
в нуль.
Пусть , , , — время, затрачиваемое на сложение, вычитание, умножение и деление двух чисел
соответственно. Количество уравнений в системе обозначим .
Рассмотрим сначала прямой ход метода Гаусса. Первая строка обстается без изменений, поэтому e
исключение выполняется за время:
Тогда время, затраченное на выполнение прямого хода, будет равно:
Рассмотрим обратный ход метода Гаусса. Определение требует времени:
Соответственно, время выполнения обратного хода равно:
Итого, общее время
Рассмотрим параллельный алгоритм, реализующий метод Гаусса. Пусть имеется процессоров, причём для
простоты, , где — целое.
Процесс деления строк на коэффициенты перед независим и не требует информации от других ветвей.
Использование распределений горизонтальными или вертикальными полосами для данного метода ведет к
значительным простоям ветвей после исключения некоторого числа неизвестных. Поэтому распределим
вычисления таким образом, чтобы процессор обсчитывал строки с номерами
(распределение циклическими горизонтальными полосами с шириной полосы равной единице).
Рисунок 2. Схема параллельного метода Гаусса.
Так как и , то
,
Таким образом, если не учитывать простой процессоров вызванный (вообще говоря) неодинаковым
количеством обсчитываемых строк и неравномерностью самих вычислений, параллельный алгоритм работает
в K раз быстрее последовательного.
////////////////////////////////////////
// Приведение расширенной матрицы к диагональному виду
#include "stdafx.h"
#include "windows.h"
#include "process.h"
#include "stdio.h"
#include "stdlib.h"
#define N 4
#define NumberOfProcessors 2
float Matrix[N][N + 1] =
{
{ 1.0, 1.0, 1.0, 1.0, 1.0 },
{ 1.0, 2.0, 1.0, 1.0, 1.0 },
{ 2.0, 1.0, 1.0, 1.0, 1.0 },
{ 1.0, 2.0, 2.0, 1.0, 1.0 }
};
void InitializeMatrix()
{
printf("Source Matrixn");
for (unsigned i = 0; i < N; i++)
{
for (unsigned j = 0; j <= N; j++)
printf("%7.4f ", Matrix[i][j]);
printf("n");
}
}
struct GaussState
{
HANDLE Starters[NumberOfProcessors];
HANDLE evIterationFinished[NumberOfProcessors];
unsigned ResolveRow;
};
GaussState State;
unsigned __stdcall ThreadFunction(void *pData)
{
unsigned Branch = (unsigned)pData;
unsigned Iteration = 0;
while (Iteration + 1 < N)
{
WaitForSingleObject(State.Starters[Branch], INFINITE);
for (unsigned i = Branch + Iteration + 1; i < N;
i += NumberOfProcessors)
{
float Resolver =
-Matrix[State.ResolveRow][Iteration] /
Matrix[i][Iteration];
for (unsigned j = Iteration; j <= N; j++)
Matrix[i][j] = Matrix[State.ResolveRow][j] +
Resolver * Matrix[i][j];
}
Iteration++;
// synchronization
SetEvent(State.evIterationFinished[Branch]);
if (Branch == 0)
{
WaitForMultipleObjects(NumberOfProcessors,
State.evIterationFinished, TRUE,
INFINITE);
State.ResolveRow++;
for (unsigned i = 0;
i < NumberOfProcessors; i++)
SetEvent(State.Starters[i]);
}
}
return 0;
}
int main(int argc, char* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
InitializeMatrix();
State.ResolveRow = 0;
HANDLE hThreads[NumberOfProcessors];
unsigned i, j, tid;
for (i = 0; i < NumberOfProcessors; i++)
{
State.Starters[i] =
CreateEvent(NULL, FALSE, TRUE, NULL);
State.evIterationFinished[i] =
CreateEvent(NULL, FALSE, FALSE, NULL);
}
for (i = 0; i < NumberOfProcessors; i++)
hThreads[i] = (HANDLE)_beginthreadex(
NULL, 0, ThreadFunction, (void *)i, 0, &tid);
WaitForMultipleObjects(
NumberOfProcessors, hThreads, TRUE, INFINITE);
for (i = 0; i < NumberOfProcessors; i++)
{
CloseHandle(State.Starters[i]);
CloseHandle(State.evIterationFinished[i]);
CloseHandle(hThreads[i]);
}
printf("nTriangle Matrixn");
for (i = 0; i < N; i++)
{
for (j = 0; j <= N; j++)
printf("%7.4f ", Matrix[i][j]);
printf("n");
}
return 0;
}
5. Вычисление скалярного произведения
Рассмотрим алгоритм вычисления скалярного произведения двух n-мерных векторов. Пусть даны два
вектора и . Их скалярное произведение вычисляется по формуле:
Оценим время, затрачиваемое на умножение векторов. Пусть — время, затрачиваемое на умножение двух
чисел, — время сложения двух чисел. Тогда время умножения двух n-мерных векторов равно:
Таким образом, сложность алгоритма Теперь рассмотрим параллельный алгоритм вычисления
скалярного произведения векторов. Обозначим количество процессоров в системе, причем ,
где .
Тогда каждый процессор будет вычислять координат результирующего вектора.
Рисунок 3. Распределение для скалярного произведения.
Оценим время работы параллельного алгоритма следующим образом:
Рассмотрим отношение
Ввиду того, что мы видим, что использование параллельного алгоритма позволяет решить задачу
быстрее. Более того, при , параллельный алгоритм вычисляет скалярное произведение векторов
быстрее последовательного почти в раз.
////////////////////////////////////////
// Параллельное вычисление скалярного произведения
#include "stdafx.h"
#include <process.h>
struct Interval
{
HANDLE hThread;
HANDLE evStart;
unsigned First;
unsigned To;
float Result;
float *Vector1;
float *Vector2;
Interval() :
First(0), To(0), Vector1(NULL), Vector2(NULL),
Result(0.0f),
hThread(NULL), evStart(NULL) {}
};
unsigned __stdcall ThreadFunction(void *pData);
float DotProduct(float *Vector1, float *Vector2,
unsigned VectorSize);
unsigned GetNumberOfProcessors();
int main(int argc, char *argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
const unsigned VectorSize = 13;
float *Vector1 = new float[VectorSize];
float *Vector2 = new float[VectorSize];
printf("Dot product %fn",
DotProduct(Vector1, Vector2, VectorSize));
delete[] Vector2;
delete[] Vector1;
return 0;
}
float DotProduct(float *Vector1, float *Vector2,
unsigned VectorSize)
{
if (!Vector1 || !Vector2)
return 0.0f;
unsigned ProcessorCount = GetNumberOfProcessors();
unsigned IntervalLength = VectorSize / ProcessorCount;
unsigned Remainder = VectorSize % ProcessorCount;
unsigned ProcessorsUsed =
(IntervalLength ? ProcessorCount : Remainder);
HANDLE *Handles = new HANDLE[ProcessorsUsed];
if (!Handles)
return 0.0f;
Interval *Intervals = new Interval[ProcessorsUsed];
if (!Intervals)
{
delete Handles;
return 0.0f;
}
// Nonsignaled manual-reset event
HANDLE evStart = CreateEvent(NULL, TRUE, FALSE, NULL);
unsigned From = 0;
unsigned i;
for (i = 0; i < ProcessorCount; i++)
{
unsigned To = From + IntervalLength;
if (Remainder)
To++, Remainder--;
if (To == From)
continue;
unsigned tid;
Intervals[i].First = From;
Intervals[i].To = To;
Intervals[i].evStart = evStart;
Intervals[i].hThread = (HANDLE)
_beginthreadex(NULL, 0, ThreadFunction,
&Intervals[i], 0, &tid);
From = To;
}
for (i = 0; i < ProcessorsUsed; i++)
{
Intervals[i].Vector1 = Vector1;
Intervals[i].Vector2 = Vector2;
Handles[i] = Intervals[i].hThread;
}
SetEvent(evStart);
WaitForMultipleObjects(ProcessorsUsed, Handles, TRUE,
INFINITE);
float Result = 0.0f;
for (i = 0; i < ProcessorsUsed; i++)
{
Result += Intervals[i].Result;
CloseHandle(Intervals[i].hThread);
}
CloseHandle(evStart);
delete[] Intervals;
delete[] Handles;
return Result;
}
unsigned __stdcall ThreadFunction(void *pData)
{
if (pData)
{
Interval &iv = *(Interval *)pData;
WaitForSingleObject(iv.evStart, INFINITE);
for (unsigned i = iv.First; i < iv.To; i++)
iv.Result += iv.Vector1[i] * iv.Vector2[i];
}
return 0;
}
unsigned GetNumberOfProcessors()
{
DWORD dwProcessAffinityMask, dwSystemAffinityMask;
unsigned n = 0;
if (GetProcessAffinityMask(GetCurrentProcess(),
&dwProcessAffinityMask,
&dwSystemAffinityMask))
{
for (DWORD dwMask = 0x00000001; dwMask;
dwMask <<= 1)
if (dwMask & dwProcessAffinityMask)
n++;
}
else
n = 1;
return n;
}
6. Умножение матриц
Задача умножения матриц является базовой макрооперацией для многих задач линейной алгебры. Поэтому
эффективный параллельный алгоритм умножения матриц позволяет значительно увеличить размерность
подобных задач без увеличения времени их решения. Пусть даны две матрицы, и ., произведение
которых необходимо вычислить. Элементы результирующей матрицы определяются по формуле
Оценим время выполнения последовательного алгоритма. Пусть — время, затрачиваемое на умножение
двух чисел, — время сложения двух чисел. Время, затрачиваемое на вычисление одного элемента
результирующей матрицы . В результирующей матрице содержится элементов,
поэтому общее время выполнения умножения матриц равно
Обозначим Отсюда следует, что .
Рассмотрим параллельный алгоритм умножения матриц и оценим время его работы.
Пусть имеется процессоров, причём, , где — целое. Распараллелим вычисления так, чтобы
каждый процессор вычислял элементов результирующей матрицы. Тогда суммарное время выполнения
можно оценить следующим образом:
Обозначим Сравнивая и , получим: Имеем, что время, затрачиваемое на
умножение матриц с помощью параллельного алгоритма, в раз меньше, чем при использовании
последовательного.
Рассмотрим один из способов распараллеливания алгоритма. Пронумеруем элементы результирующей
матрицы по следующему правилу (нумерация элементов, а также строк и столбцов в данном случае ведётся с
нуля):
Вычисления распределим так, чтобы первый процессор вычислял первые элементов результирующей
матрицы, второй — следующие и т.д. Таким образом, конечная и левая исходная матрица получаются
распределёнными горизонтальными полосами, а правая исходная матрица — вертикальными полосами.
Рисунок 4. Схема распределения работ при параллельном умножении матриц.
Рассмотрим случай, когда (количество процессоров больше, чем элементов в результирующей
матрице). Незадействованные процессоров можно использовать для ускорения вычисления
отдельных элементов результирующей матрицы. При этом следует использовать способ распараллеливания,
подобный использованному при вычислении скалярного произведения векторов.
////////////////////////////////////////
// Параллельное умножение матриц
#include "windows.h"
#include "process.h"
#include "stdio.h"
#define M 3
#define N 4
#define NumberOfProcessors 3
int A[M][N];
int B[N][M];
int C[M][M];
void InititalizeMatices()
{
unsigned i, j, n;
printf("nMatrix A:n");
for (i = 0, n = 1; i < M; i++)
{
for (j = 0; j < N; j++, n++)
{
A[i][j] = n;
printf("%4d ", n);
}
printf("n");
}
printf("nMatrix B:n");
for (i = 0, n = 1; i < N; i++)
{
for (j = 0; j < M; j++, n++)
{
B[i][j] = n;
printf("%4d ", n);
}
printf("n");
}
}
unsigned __stdcall ThreadFunction(void *pData)
{
unsigned Branch = (unsigned)pData;
unsigned Start = M / NumberOfProcessors * Branch;
unsigned End = M / NumberOfProcessors * (Branch + 1);
for (unsigned i = Start; i < End; i++)
for (unsigned j = 0; j < M; j++)
{
int Value = A[i][0] * B[0][j];
for (unsigned k = 1; k < N; k++)
Value += A[i][k] * B[k][j];
C[i][j] = Value;
}
return 0;
}
int main(int argc, char* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
assert(M % NumberOfProcessors == 0);
InititalizeMatices();
HANDLE hThreads[NumberOfProcessors];
unsigned i, j, n, tid;
for (i = 0; i < NumberOfProcessors; i++)
hThreads[i] = (HANDLE)_beginthreadex(NULL, 0,
ThreadFunction, (void *)i, 0, &tid);
WaitForMultipleObjects(NumberOfProcessors, hThreads, TRUE,
INFINITE);
for (i = 0; i < NumberOfProcessors; i++)
CloseHandle(hThreads[i]);
printf("nMatrix C = A * B:n");
for (i = 0, n = 1; i < M; i++)
{
for (j = 0; j < M; j++, n++)
printf("%4d ", C[i][j]);
printf("n");
}
return 0;
}
Заключение
Надеемся, статья помогла Вам немного лучше разобраться в вопросах параллельного программирования, а
приведенные примеры наглядно продемонстрировали некоторые параллельные алгоритмы. Желаем Вам
успехов в создании эффективных программных решений.
Литература
 Элементы параллельного программирования / В.А. Вальковский, В.Е. Котов, А.Г. Марчук, Н.Н. Миренков;
под ред. В.Е. Котова. — М.: Радио и связь, 1983. — 240с.,интеграл.
 Алгоритмы, математическое обеспечение и архитектура многопроцессорных вычислительных систем /
Под ред. В.Е. Котова и И. Миклошко — М.: Наука, 1982. — 336 с.
 Миренков Н.Н. Параллельное программирование для многомодульных вычислительных систем. — М.:
Радио и связь, 1989. — 320с.: ил. — ISBN 5-256-00196-5
 Шихаев К.Н. Разностные алгоритмы параллельных вычислительных процессов. — М.: Радио и связь,
1982. — 136с., ил.
 Вальковский В.А. Распараллеливание алгоритмов и программ. Структурный подход. М.: Радио и связь,
1989. —176с., ил. — ISBN 5-256-00195-7.
Об Авторах
Миронов Антон Александрович
Занимается вопросами создания и использования высокопроизводительных параллельных алгоритмов. Один
из ведущих разработчиков в компании Developer Express Inc.
Карпов Андрей Николаевич
Занимается вопросами создания программных решений в области повышения качества и быстродействия
ресурсоемких приложений. Является одним из авторов статического анализатора Viva64 иVivaMP,
предназначенных для верификации 64-битного и параллельного Си/Си++ кода.
Параллельные алгоритмы обработки данных

More Related Content

What's hot

Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
Alexey Paznikov
 
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Mikhail Kurnosov
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Alexey Paznikov
 
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Семинар 2. Многопоточное программирование на OpenMP (часть 2)Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Mikhail Kurnosov
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
Alexey Paznikov
 
Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)
Mikhail Kurnosov
 
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Mikhail Kurnosov
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
Mikhail Kurnosov
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
Mikhail Kurnosov
 
Векторизация кода (семинар 1)
Векторизация кода (семинар 1)Векторизация кода (семинар 1)
Векторизация кода (семинар 1)
Mikhail Kurnosov
 
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Mikhail Kurnosov
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)
Mikhail Kurnosov
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
Alexey Paznikov
 
битоническая сортировка
битоническая сортировкабитоническая сортировка
битоническая сортировка
Dmitry Protopopov
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Mikhail Kurnosov
 
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
Alexey Paznikov
 
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
Mikhail Kurnosov
 
корзинная сортировка
корзинная сортировкакорзинная сортировка
корзинная сортировка
Dmitry Protopopov
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
Mikhail Kurnosov
 
Multiprocessor Programming Intro (lecture 2)
Multiprocessor Programming Intro (lecture 2)Multiprocessor Programming Intro (lecture 2)
Multiprocessor Programming Intro (lecture 2)Dmitry Tsitelov
 

What's hot (20)

Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
 
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)Семинар 6. Многопоточное программирование на OpenMP (часть 6)
Семинар 6. Многопоточное программирование на OpenMP (часть 6)
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
 
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Семинар 2. Многопоточное программирование на OpenMP (часть 2)Семинар 2. Многопоточное программирование на OpenMP (часть 2)
Семинар 2. Многопоточное программирование на OpenMP (часть 2)
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
 
Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)Семинар 9. Параллельное программирование на MPI (часть 2)
Семинар 9. Параллельное программирование на MPI (часть 2)
 
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)Семинар 7. Многопоточное программирование на OpenMP (часть 7)
Семинар 7. Многопоточное программирование на OpenMP (часть 7)
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Лекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMPЛекция 6. Стандарт OpenMP
Лекция 6. Стандарт OpenMP
 
Векторизация кода (семинар 1)
Векторизация кода (семинар 1)Векторизация кода (семинар 1)
Векторизация кода (семинар 1)
 
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)Семинар 4. Многопоточное программирование на OpenMP (часть 4)
Семинар 4. Многопоточное программирование на OpenMP (часть 4)
 
Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)Семинар 11. Параллельное программирование на MPI (часть 4)
Семинар 11. Параллельное программирование на MPI (часть 4)
 
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
 
битоническая сортировка
битоническая сортировкабитоническая сортировка
битоническая сортировка
 
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
Лекция 7: Многопоточное программирование: часть 3 (OpenMP)
 
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
ПВТ - весна 2015 - Лекция 3. Реентерабельность. Сигналы. Локальные данные пот...
 
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
Лекция 12. Вероятностный анализ и рандомизированные алгоритмы (Randomized al...
 
корзинная сортировка
корзинная сортировкакорзинная сортировка
корзинная сортировка
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Multiprocessor Programming Intro (lecture 2)
Multiprocessor Programming Intro (lecture 2)Multiprocessor Programming Intro (lecture 2)
Multiprocessor Programming Intro (lecture 2)
 

Similar to Параллельные алгоритмы обработки данных

Иван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеИван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программирование
Yandex
 
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Yandex
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклы
Sergey Nemchinsky
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
Tatyanazaxarova
 
якобовский - введение в параллельное программирование (1)
якобовский - введение в параллельное программирование (1)якобовский - введение в параллельное программирование (1)
якобовский - введение в параллельное программирование (1)Michael Karpov
 
Luxoft async.net
Luxoft async.netLuxoft async.net
Luxoft async.net
Sergey Teplyakov
 
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOMYuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
OdessaJS Conf
 
Parallel STL
Parallel STLParallel STL
Parallel STL
Evgeny Krutko
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1Michael Karpov
 
презентации по информатике
презентации по информатикепрезентации по информатике
презентации по информатике
Nick535
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templates
Platonov Sergey
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesPlatonov Sergey
 
Типичный стек технологий для использования с Node.js
Типичный стек технологий для использования с Node.jsТипичный стек технологий для использования с Node.js
Типичный стек технологий для использования с Node.jsSerge Shirokov
 
трасировка Mpi приложений
трасировка Mpi приложенийтрасировка Mpi приложений
трасировка Mpi приложенийMichael Karpov
 
вспомогательные алгоритмы
вспомогательные алгоритмывспомогательные алгоритмы
вспомогательные алгоритмыЕлена Ключева
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
Eugeniy Tyumentcev
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
etyumentcev
 
Многопоточное программирование на C#, путевые заметки
Многопоточное программирование на C#, путевые заметкиМногопоточное программирование на C#, путевые заметки
Многопоточное программирование на C#, путевые заметки
DotNetConf
 

Similar to Параллельные алгоритмы обработки данных (20)

Иван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеИван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программирование
 
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклы
 
Вечный вопрос измерения времени
Вечный вопрос измерения времениВечный вопрос измерения времени
Вечный вопрос измерения времени
 
якобовский - введение в параллельное программирование (1)
якобовский - введение в параллельное программирование (1)якобовский - введение в параллельное программирование (1)
якобовский - введение в параллельное программирование (1)
 
Async
AsyncAsync
Async
 
Luxoft async.net
Luxoft async.netLuxoft async.net
Luxoft async.net
 
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOMYuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
Yuriy Sherstyuk - Algorithms in Front End: from V8 to VDOM
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
лекция 1
лекция 1лекция 1
лекция 1
 
20090720 hpc exercise1
20090720 hpc exercise120090720 hpc exercise1
20090720 hpc exercise1
 
презентации по информатике
презентации по информатикепрезентации по информатике
презентации по информатике
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templates
 
Оптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templatesОптимизация трассирования с использованием Expression templates
Оптимизация трассирования с использованием Expression templates
 
Типичный стек технологий для использования с Node.js
Типичный стек технологий для использования с Node.jsТипичный стек технологий для использования с Node.js
Типичный стек технологий для использования с Node.js
 
трасировка Mpi приложений
трасировка Mpi приложенийтрасировка Mpi приложений
трасировка Mpi приложений
 
вспомогательные алгоритмы
вспомогательные алгоритмывспомогательные алгоритмы
вспомогательные алгоритмы
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
 
разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3разработка серверов и серверных приложений лекция №3
разработка серверов и серверных приложений лекция №3
 
Многопоточное программирование на C#, путевые заметки
Многопоточное программирование на C#, путевые заметкиМногопоточное программирование на C#, путевые заметки
Многопоточное программирование на C#, путевые заметки
 

Параллельные алгоритмы обработки данных

  • 1. Параллельные алгоритмы обработки данных Андрей Карпов, Антон Миронов Декабрь 2008
  • 2. Аннотация Статья посвящена вопросам использования параллельных алгоритмов для создания современных эффективных программных решений. Актуальность данной тематики обусловлена снижением темпов роста тактовой частоты микропроцессоров и возрастанием внимания к использованию всех возможностей многоядерных и многопроцессорных систем. В работе рассмотрен ряд базовых параллельных алгоритмов, таких как умножение матриц, параллельная сортировка Бэтчера, метод Гаусса решения систем линейных алгебраических уравнений и так далее. Приведена реализация этих алгоритмов c использованием языка программирования Си++.
  • 3. Введение В ближайшее время под эффективным использованием аппаратных средств компьютера будут пониматься применение параллельных алгоритмов. Это связано с замедлением темпов роста тактовой частоты микропроцессоров и быстрым распространением многоядерных микропроцессоров. Создание эффективных программных решений для параллельных систем складывается из трех основных компонентов: параллельных алгоритмов, средств реализации параллельности и систем отладки. Под средствами реализации параллельности понимаются языки программирования или библиотеки, обеспечивающие инфраструктуру параллельных программ. Таких система достаточно много. К ним можно отнести: Occam, MPI, HPF, OpenMP, DVM, OpenTS, Boost.Thread, Posix Threads. Сюда также можно отнести библиотеку Integrated Performance Primitives компании Intel. Поскольку отладка параллельной программы является процессом более трудоемким, чем отладка последовательной программы, то системы отладки и профилирования являются важнейшей частью в процессе разработки таких систем. В области отладчиков хочется обратить внимание на системы TotalView и PGDBG. Среди средств профилирования можно назвать такие инструменты как Nupshot, Pablo, Vampir. Существуют системы статической верификации, например VivaMP. Последней инструмент, на который хочется обратить особое внимание, является средства анализа многопоточности Threading Analysis Tools.
  • 4. Но самый важный компонент, без которого все другие средства не смогут сделать программу параллельной, это параллельный алгоритм. Именно этому компоненту и будет посвящена эта статья. В статье будут рассмотрены несколько базовых параллельных алгоритмов, имеющих практическое значение во многих областях, в том числе в экономике, в численном моделировании и в задачах реального времени. Для некоторых алгоритмов приведены оценки времени выполнения последовательной и параллельной версий алгоритма и выведены коэффициенты ускорения. Приведен пример реализации описанных алгоритмов c использованием языка программирования Си++ в среде Windows.
  • 5. 1. Параллельная сортировка Бэтчера Алгоритм сортировки Бэтчера не является наиболее эффективным алгоритмом сортировки, однако он обладает одним важным компенсирующим качеством: все сравнения и/или обмены, определяемые данной итерацией алгоритма можно выполнять одновременно. С такими параллельными операциями сортировка осуществляется за . Например, 1024 элемента можно рассортировать методом Бэтчера всего за 55 параллельных шагов. Схема сортировки Бэтчера несколько напоминает сортировку Шелла, но сравнения выполняются по-новому, а потому цепочки операций обмена записей не возникает. Поскольку в алгоритме Бэтчера, по существу, происходит слияние пар рассортированных подпоследовательностей, его можно назвать обменной сортировкой со слиянием. Алгоритм Бэтчера (обменная сортировка со слиянием). Записи перекомпоновываются в пределах того же пространства в памяти. После завершения сортировки их ключи будут упорядочены: . Предполагается, что . 1)[начальная установка .] Установить , где — наименьшее целое число, такое, что . (Шаги 2-5 будут выполняться с .) 2)[начальная установка .] Установить . 3)[цикл по .] Для всех , таких, что и , выполнять шаг 4.
  • 6. Затем перейти к шагу 5. (Здесь через обозначена операция "поразрядное логическое И" над представлениями целых чисел и ; все биты результата равны 0, кроме тех битов, для которых в соответствующих разрядах и находятся 1. Так . К этому моменту — нечётное кратное (т.е. частное от деления на нечётно), а — степень двойки, так что . Отсюда следует, что шаг 4 можно выполнять при всех нужных значениях в любом порядке или даже одновременно.) 4)[Сравнение/обмен .] Если , поменять местами записи . 5)[Цикл по .] Если , установить и возвратиться к шагу 3. 6)[Цикл по ] (К этому моменту перестановка будет -упорядочена.) Установить . Если , возвратиться к шагу 2.
  • 7. //////////////////////////////////////// // Параллельная сортировка Бэтчера #include <stdio.h> #include <stdlib.h> #define N 16 int Arr[N]; void FillArray() { for (unsigned i = 0; i < N; i++) Arr[i] = rand() % 10; } void PrintArray() { for (unsigned i = 0; i < N; i++) printf("%d ", Arr[i]); printf("n"); } void BatcherSort() { unsigned p = N; while (p > 0) { unsigned q = N, r = 0, d = p; bool b; do { unsigned nTo = N - d; for (unsigned i = 0; i < nTo; i++) if ((i & p) == r) { if (Arr[i] > Arr[i + d]) { int temp = Arr[i];
  • 8. Arr[i] = Arr[i + d]; Arr[i + d] = temp; } } b = q != p; if (b) { d = q - p; q >>= 1; r = p; } } while (b); p >>= 1; } } int main(int argc, char* argv[]) { FillArray(); PrintArray(); BatcherSort(); PrintArray(); return 0; }
  • 9. 2. Вычисление корня алгебраического или трансцендентного уравнения Пусть дано уравнение . Требуется на отрезке найти корень при условии . Для построения алгоритма ветви используется метод хорд, заключающийся в следующем (рисунок N1). Кривая заменяется хордой , определяется Рисунок 1. Метод хорд. точка , после чего проводится хорда определяется и т. д., пока . Для реализации задачи на процессоров, т.е. для построения параллельного алгоритма из ветвей, отрезок разбивается на подотрезков на границах которых вычисляется . Это определяет подотрезок длиной , содержащий корень . Точки и — соседние, в них значения функции имеют разные знаки (рисунок N1).
  • 10. После отрезка рассматривается отрезок , где (1) Отрезок разбивается на частей, повторяется описанная процедура, получается отрезок и т. д., пока длина отрезка не станет меньше заданного значения. Параллельный счет оправдан, когда объём вычислений значения намного превосходит объём вычислений границ отрезка по (1). Аналогичный результат получается и при использовании метода Ньютона. //////////////////////////////////////// // Параллельное вычисление корня уравнения #include <windows.h> #include <process.h> #include <stdio.h> #include <math.h> #define NumberOfProcessors 3 float Func(float arg) { return arg * (arg - 5) + 6; } template <typename t> inline int sgn(t arg) { return arg < 0 ? -1 : (arg > 0 ? 1 : 0); } float x1 = 2.5; float x2 = 10; float e = 1;
  • 11. float M = 5; #define EPSILON 1e-4 enum Commands { Continue = 0, Stop = 1 }; int Action = Continue; HANDLE hEventStart[NumberOfProcessors]; HANDLE hEventFinish[NumberOfProcessors]; float Values[NumberOfProcessors]; unsigned __stdcall ThreadFunction(void *pData) { unsigned Branch = (unsigned)pData; for (;;) { WaitForSingleObject(hEventStart[Branch], INFINITE); if (Action == Stop) return 0; Values[Branch] = Func(x1 + Branch * (x2 - x1) / (float)(NumberOfProcessors - 1)); SetEvent(hEventFinish[Branch]); } return 0; } int main(int argc, char* argv[]) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); HANDLE hThreads[NumberOfProcessors]; unsigned i, tid; float Result = 0.0f; for (i = 0; i < NumberOfProcessors; i++) { hEventStart[i] = CreateEvent(NULL, FALSE, TRUE, NULL); hEventFinish[i] = CreateEvent(NULL, FALSE,
  • 12. FALSE, NULL); } for (i = 0; i < NumberOfProcessors; i++) hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, (void *)i, 0, &tid); for (;;) { WaitForMultipleObjects(NumberOfProcessors, hEventFinish, TRUE, INFINITE); int Sign = sgn(Values[0]); if (Sign == 0) { Result = x1; Action = Stop; } if (Action != Stop) for (i = 1; i < NumberOfProcessors; i++) { int NewSign = sgn(Values[i]); if (NewSign == 0) { Result = x1 + i * (x2 - x1) / (float)(NumberOfProcessors - 1); Action = Stop; break; } if (NewSign != Sign) { float a = x1 + (i - 1) * (x2 - x1) / (float)(NumberOfProcessors - 1); float b = x1 + i * (x2 - x1) /
  • 13. (float)(NumberOfProcessors - 1); x1 = a + (Values[i - 1] * (b - a)) / (Values[i] - Values[i - 1]); x2 = b - Values[i] / M; e = b - a; break; } } if (e < EPSILON) { Result = (x2 + x1) / 2; Action = Stop; } for (i = 0; i < NumberOfProcessors; i++) SetEvent(hEventStart[i]); if (Action == Stop) break; } printf("%7.4f", Result); for (i = 0; i < NumberOfProcessors; i++) { CloseHandle(hThreads[i]); CloseHandle(hEventStart[i]); CloseHandle(hEventFinish[i]); } return 0; }
  • 14. 3. Решение системы линейных алгебраических уравнений методом простой итерации Система уравнений размерности записывается в виде , где — матрицы; и — векторы; — единичная матрица. Последовательные приближения вычисляются по формуле , начиная с некоторого исходного приближения . Процесс завершается, если . Последовательность в методе простой итерации сходится, если для матрицы выполняется одно из неравенств Применим к матрице и векторам и распределение горизонтальными полосами. Для нахождения в соответствующей ветви необходимы строка матрицы , вектор и значение . Поэтому можно обеспечить каждую ветвь всеми необходимыми данными для автономной работы. Отсюда
  • 15. где — число строк матрицы и компонентов векторов и , обрабатываемых отдельной ветвью, — время обмена данными, — время выполнения сложения, — время выполнения умножения. #include <windows.h> #include <process.h> #include <stdio.h> #include <math.h> #define N 4 #define NumberOfProcessors 2 #define EPSILON 1e-5 float Matrix[N][N + 1] = { { 1.0f, 0.1f, 0.1f, 0.1f, 1.0f }, { 0.1f, 1.2f, 0.1f, 0.1f, 1.0f }, { 0.2f, 0.1f, 1.0f, 0.1f, 1.0f }, { 0.2f, 0.2f, 0.2f, 1.0f, 1.0f } }; float NewX[N]; float CurX[N] = { 1.0, 1.0, 1.0, 1.0 }; void InitializeMatrix() { printf("Source Matrixn"); for (unsigned i = 0; i < N; i++) { for (unsigned j = 0; j <= N; j++) { // calculate (E - A) if (i == j) Matrix[i][j] = 1.0f - Matrix[i][j]; else if (j < N)
  • 16. Matrix[i][j] = -Matrix[i][j]; printf("%7.4f ", Matrix[i][j]); } printf("n"); } } enum Commands { Continue, Stop }; struct IterState { HANDLE Starters[NumberOfProcessors]; HANDLE evIterationFinished[NumberOfProcessors]; }; IterState State; unsigned Command = Continue; unsigned __stdcall ThreadFunction(void *pData) { unsigned Branch = (unsigned)pData; unsigned nFrom = Branch * N / NumberOfProcessors; unsigned nTo = (Branch + 1) * N / NumberOfProcessors; unsigned i; for (;;) { WaitForSingleObject(State.Starters[Branch], INFINITE); if (Command == Stop) return 0; for (i = nFrom; i < nTo; i++) { NewX[i] = Matrix[i][0] * CurX[0]; for (unsigned j = 1; j < N; j++) NewX[i] += Matrix[i][j] * CurX[j]; NewX[i] += Matrix[i][N]; } // synchronization
  • 17. SetEvent(State.evIterationFinished[Branch]); if (Branch == 0) { WaitForMultipleObjects(NumberOfProcessors, State.evIterationFinished, TRUE, INFINITE); float d = -1; for (i = 0; i < N; i++) if (fabs(NewX[i] - CurX[i]) > d) d = (float)fabs(NewX[i] - CurX[i]); if (d < EPSILON) Command = Stop; memcpy(CurX, NewX, sizeof(NewX)); for (i = 0; i < NumberOfProcessors; i++) SetEvent(State.Starters[i]); } } } int main(int argc, char* argv[]) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); InitializeMatrix(); HANDLE hThreads[NumberOfProcessors]; unsigned i, tid; for (i = 0; i < NumberOfProcessors; i++) { State.Starters[i] = CreateEvent(NULL, FALSE, TRUE, NULL); State.evIterationFinished[i] = CreateEvent(NULL, FALSE, FALSE, NULL); }
  • 18. for (i = 0; i < NumberOfProcessors; i++) hThreads[i] = (HANDLE)_beginthreadex( NULL, 0, ThreadFunction, (void *)i, 0, &tid); WaitForMultipleObjects(NumberOfProcessors, hThreads, TRUE, INFINITE); for (i = 0; i < NumberOfProcessors; i++) { CloseHandle(State.Starters[i]); CloseHandle(State.evIterationFinished[i]); CloseHandle(hThreads[i]); } for (i = 0; i < N; i++) printf("x%d = %7.4fn", i, NewX[i]); return 0; }
  • 19. 4. Решение системы линейных уравнений методом Гаусса Метод Гаусса основан на последовательном исключении неизвестных. Пусть дана система линейных алгебраических уравнений Вычтем из второго уравнения системы первое, умноженное на такое число, чтобы уничтожился коэффициент при . Затем таким же образом вычтем первое уравнение из третьего, четвёртого и т.д. Тогда исключатся все коэффициенты первого столбца, лежащие ниже главной диагонали. Затем при помощи второго уравнения исключим из третьего, четвёртого и т.д. уравнений коэффициенты второго столбца. Последовательно продолжая этот процесс, исключим из матрицы все коэффициенты, лежащие ниже главной диагонали. Запишем общие формулы процесса. Пусть проведено исключение коэффициентов из столбца. Тогда остались такие уравнения с ненулевыми коэффициентами ниже главной диагонали: Умножим k-ю строку на число
  • 20. и вычтем из m-й строки. Первый элемент этой строки обратится в нуль, а остальные изменятся по формулам Производя вычисления по этим формулам при всех указанных индексах, исключим элементы k-го столбца. Будем называть такое исключение циклом процесса. Выполнение всех циклов называется прямым ходом исключения. После выполнения прямого хода получим треугольную систему с матрицей Далее треугольная система решается обратным ходом по формулам
  • 21. Оценим время выполнения описанного алгоритма. Не нарушая общности, будем предполагать, что первый элемент первой строки не равен нулю и элементы на главной диагонали в ходе расчёта также не обращаются в нуль. Пусть , , , — время, затрачиваемое на сложение, вычитание, умножение и деление двух чисел соответственно. Количество уравнений в системе обозначим . Рассмотрим сначала прямой ход метода Гаусса. Первая строка обстается без изменений, поэтому e исключение выполняется за время: Тогда время, затраченное на выполнение прямого хода, будет равно: Рассмотрим обратный ход метода Гаусса. Определение требует времени: Соответственно, время выполнения обратного хода равно:
  • 22. Итого, общее время Рассмотрим параллельный алгоритм, реализующий метод Гаусса. Пусть имеется процессоров, причём для простоты, , где — целое. Процесс деления строк на коэффициенты перед независим и не требует информации от других ветвей. Использование распределений горизонтальными или вертикальными полосами для данного метода ведет к значительным простоям ветвей после исключения некоторого числа неизвестных. Поэтому распределим вычисления таким образом, чтобы процессор обсчитывал строки с номерами (распределение циклическими горизонтальными полосами с шириной полосы равной единице).
  • 23. Рисунок 2. Схема параллельного метода Гаусса. Так как и , то , Таким образом, если не учитывать простой процессоров вызванный (вообще говоря) неодинаковым количеством обсчитываемых строк и неравномерностью самих вычислений, параллельный алгоритм работает в K раз быстрее последовательного. //////////////////////////////////////// // Приведение расширенной матрицы к диагональному виду #include "stdafx.h"
  • 24. #include "windows.h" #include "process.h" #include "stdio.h" #include "stdlib.h" #define N 4 #define NumberOfProcessors 2 float Matrix[N][N + 1] = { { 1.0, 1.0, 1.0, 1.0, 1.0 }, { 1.0, 2.0, 1.0, 1.0, 1.0 }, { 2.0, 1.0, 1.0, 1.0, 1.0 }, { 1.0, 2.0, 2.0, 1.0, 1.0 } }; void InitializeMatrix() { printf("Source Matrixn"); for (unsigned i = 0; i < N; i++) { for (unsigned j = 0; j <= N; j++) printf("%7.4f ", Matrix[i][j]); printf("n"); } } struct GaussState { HANDLE Starters[NumberOfProcessors]; HANDLE evIterationFinished[NumberOfProcessors]; unsigned ResolveRow; }; GaussState State; unsigned __stdcall ThreadFunction(void *pData) { unsigned Branch = (unsigned)pData;
  • 25. unsigned Iteration = 0; while (Iteration + 1 < N) { WaitForSingleObject(State.Starters[Branch], INFINITE); for (unsigned i = Branch + Iteration + 1; i < N; i += NumberOfProcessors) { float Resolver = -Matrix[State.ResolveRow][Iteration] / Matrix[i][Iteration]; for (unsigned j = Iteration; j <= N; j++) Matrix[i][j] = Matrix[State.ResolveRow][j] + Resolver * Matrix[i][j]; } Iteration++; // synchronization SetEvent(State.evIterationFinished[Branch]); if (Branch == 0) { WaitForMultipleObjects(NumberOfProcessors, State.evIterationFinished, TRUE, INFINITE); State.ResolveRow++; for (unsigned i = 0; i < NumberOfProcessors; i++) SetEvent(State.Starters[i]); } } return 0; } int main(int argc, char* argv[]) { UNREFERENCED_PARAMETER(argc);
  • 26. UNREFERENCED_PARAMETER(argv); InitializeMatrix(); State.ResolveRow = 0; HANDLE hThreads[NumberOfProcessors]; unsigned i, j, tid; for (i = 0; i < NumberOfProcessors; i++) { State.Starters[i] = CreateEvent(NULL, FALSE, TRUE, NULL); State.evIterationFinished[i] = CreateEvent(NULL, FALSE, FALSE, NULL); } for (i = 0; i < NumberOfProcessors; i++) hThreads[i] = (HANDLE)_beginthreadex( NULL, 0, ThreadFunction, (void *)i, 0, &tid); WaitForMultipleObjects( NumberOfProcessors, hThreads, TRUE, INFINITE); for (i = 0; i < NumberOfProcessors; i++) { CloseHandle(State.Starters[i]); CloseHandle(State.evIterationFinished[i]); CloseHandle(hThreads[i]); } printf("nTriangle Matrixn"); for (i = 0; i < N; i++) { for (j = 0; j <= N; j++) printf("%7.4f ", Matrix[i][j]); printf("n"); } return 0; }
  • 27. 5. Вычисление скалярного произведения Рассмотрим алгоритм вычисления скалярного произведения двух n-мерных векторов. Пусть даны два вектора и . Их скалярное произведение вычисляется по формуле: Оценим время, затрачиваемое на умножение векторов. Пусть — время, затрачиваемое на умножение двух чисел, — время сложения двух чисел. Тогда время умножения двух n-мерных векторов равно: Таким образом, сложность алгоритма Теперь рассмотрим параллельный алгоритм вычисления скалярного произведения векторов. Обозначим количество процессоров в системе, причем , где . Тогда каждый процессор будет вычислять координат результирующего вектора.
  • 28. Рисунок 3. Распределение для скалярного произведения. Оценим время работы параллельного алгоритма следующим образом: Рассмотрим отношение Ввиду того, что мы видим, что использование параллельного алгоритма позволяет решить задачу быстрее. Более того, при , параллельный алгоритм вычисляет скалярное произведение векторов быстрее последовательного почти в раз. //////////////////////////////////////// // Параллельное вычисление скалярного произведения #include "stdafx.h" #include <process.h>
  • 29. struct Interval { HANDLE hThread; HANDLE evStart; unsigned First; unsigned To; float Result; float *Vector1; float *Vector2; Interval() : First(0), To(0), Vector1(NULL), Vector2(NULL), Result(0.0f), hThread(NULL), evStart(NULL) {} }; unsigned __stdcall ThreadFunction(void *pData); float DotProduct(float *Vector1, float *Vector2, unsigned VectorSize); unsigned GetNumberOfProcessors(); int main(int argc, char *argv[]) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); const unsigned VectorSize = 13; float *Vector1 = new float[VectorSize]; float *Vector2 = new float[VectorSize]; printf("Dot product %fn", DotProduct(Vector1, Vector2, VectorSize)); delete[] Vector2; delete[] Vector1; return 0; } float DotProduct(float *Vector1, float *Vector2,
  • 30. unsigned VectorSize) { if (!Vector1 || !Vector2) return 0.0f; unsigned ProcessorCount = GetNumberOfProcessors(); unsigned IntervalLength = VectorSize / ProcessorCount; unsigned Remainder = VectorSize % ProcessorCount; unsigned ProcessorsUsed = (IntervalLength ? ProcessorCount : Remainder); HANDLE *Handles = new HANDLE[ProcessorsUsed]; if (!Handles) return 0.0f; Interval *Intervals = new Interval[ProcessorsUsed]; if (!Intervals) { delete Handles; return 0.0f; } // Nonsignaled manual-reset event HANDLE evStart = CreateEvent(NULL, TRUE, FALSE, NULL); unsigned From = 0; unsigned i; for (i = 0; i < ProcessorCount; i++) { unsigned To = From + IntervalLength; if (Remainder) To++, Remainder--; if (To == From) continue; unsigned tid; Intervals[i].First = From; Intervals[i].To = To;
  • 31. Intervals[i].evStart = evStart; Intervals[i].hThread = (HANDLE) _beginthreadex(NULL, 0, ThreadFunction, &Intervals[i], 0, &tid); From = To; } for (i = 0; i < ProcessorsUsed; i++) { Intervals[i].Vector1 = Vector1; Intervals[i].Vector2 = Vector2; Handles[i] = Intervals[i].hThread; } SetEvent(evStart); WaitForMultipleObjects(ProcessorsUsed, Handles, TRUE, INFINITE); float Result = 0.0f; for (i = 0; i < ProcessorsUsed; i++) { Result += Intervals[i].Result; CloseHandle(Intervals[i].hThread); } CloseHandle(evStart); delete[] Intervals; delete[] Handles; return Result; } unsigned __stdcall ThreadFunction(void *pData) { if (pData) { Interval &iv = *(Interval *)pData; WaitForSingleObject(iv.evStart, INFINITE); for (unsigned i = iv.First; i < iv.To; i++)
  • 32. iv.Result += iv.Vector1[i] * iv.Vector2[i]; } return 0; } unsigned GetNumberOfProcessors() { DWORD dwProcessAffinityMask, dwSystemAffinityMask; unsigned n = 0; if (GetProcessAffinityMask(GetCurrentProcess(), &dwProcessAffinityMask, &dwSystemAffinityMask)) { for (DWORD dwMask = 0x00000001; dwMask; dwMask <<= 1) if (dwMask & dwProcessAffinityMask) n++; } else n = 1; return n; }
  • 33. 6. Умножение матриц Задача умножения матриц является базовой макрооперацией для многих задач линейной алгебры. Поэтому эффективный параллельный алгоритм умножения матриц позволяет значительно увеличить размерность подобных задач без увеличения времени их решения. Пусть даны две матрицы, и ., произведение которых необходимо вычислить. Элементы результирующей матрицы определяются по формуле Оценим время выполнения последовательного алгоритма. Пусть — время, затрачиваемое на умножение двух чисел, — время сложения двух чисел. Время, затрачиваемое на вычисление одного элемента результирующей матрицы . В результирующей матрице содержится элементов, поэтому общее время выполнения умножения матриц равно Обозначим Отсюда следует, что . Рассмотрим параллельный алгоритм умножения матриц и оценим время его работы.
  • 34. Пусть имеется процессоров, причём, , где — целое. Распараллелим вычисления так, чтобы каждый процессор вычислял элементов результирующей матрицы. Тогда суммарное время выполнения можно оценить следующим образом: Обозначим Сравнивая и , получим: Имеем, что время, затрачиваемое на умножение матриц с помощью параллельного алгоритма, в раз меньше, чем при использовании последовательного. Рассмотрим один из способов распараллеливания алгоритма. Пронумеруем элементы результирующей матрицы по следующему правилу (нумерация элементов, а также строк и столбцов в данном случае ведётся с нуля): Вычисления распределим так, чтобы первый процессор вычислял первые элементов результирующей матрицы, второй — следующие и т.д. Таким образом, конечная и левая исходная матрица получаются распределёнными горизонтальными полосами, а правая исходная матрица — вертикальными полосами.
  • 35. Рисунок 4. Схема распределения работ при параллельном умножении матриц. Рассмотрим случай, когда (количество процессоров больше, чем элементов в результирующей матрице). Незадействованные процессоров можно использовать для ускорения вычисления отдельных элементов результирующей матрицы. При этом следует использовать способ распараллеливания, подобный использованному при вычислении скалярного произведения векторов. //////////////////////////////////////// // Параллельное умножение матриц #include "windows.h" #include "process.h" #include "stdio.h" #define M 3 #define N 4 #define NumberOfProcessors 3 int A[M][N]; int B[N][M]; int C[M][M]; void InititalizeMatices() { unsigned i, j, n;
  • 36. printf("nMatrix A:n"); for (i = 0, n = 1; i < M; i++) { for (j = 0; j < N; j++, n++) { A[i][j] = n; printf("%4d ", n); } printf("n"); } printf("nMatrix B:n"); for (i = 0, n = 1; i < N; i++) { for (j = 0; j < M; j++, n++) { B[i][j] = n; printf("%4d ", n); } printf("n"); } } unsigned __stdcall ThreadFunction(void *pData) { unsigned Branch = (unsigned)pData; unsigned Start = M / NumberOfProcessors * Branch; unsigned End = M / NumberOfProcessors * (Branch + 1); for (unsigned i = Start; i < End; i++) for (unsigned j = 0; j < M; j++) { int Value = A[i][0] * B[0][j]; for (unsigned k = 1; k < N; k++) Value += A[i][k] * B[k][j]; C[i][j] = Value;
  • 37. } return 0; } int main(int argc, char* argv[]) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); assert(M % NumberOfProcessors == 0); InititalizeMatices(); HANDLE hThreads[NumberOfProcessors]; unsigned i, j, n, tid; for (i = 0; i < NumberOfProcessors; i++) hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunction, (void *)i, 0, &tid); WaitForMultipleObjects(NumberOfProcessors, hThreads, TRUE, INFINITE); for (i = 0; i < NumberOfProcessors; i++) CloseHandle(hThreads[i]); printf("nMatrix C = A * B:n"); for (i = 0, n = 1; i < M; i++) { for (j = 0; j < M; j++, n++) printf("%4d ", C[i][j]); printf("n"); } return 0; }
  • 38. Заключение Надеемся, статья помогла Вам немного лучше разобраться в вопросах параллельного программирования, а приведенные примеры наглядно продемонстрировали некоторые параллельные алгоритмы. Желаем Вам успехов в создании эффективных программных решений. Литература  Элементы параллельного программирования / В.А. Вальковский, В.Е. Котов, А.Г. Марчук, Н.Н. Миренков; под ред. В.Е. Котова. — М.: Радио и связь, 1983. — 240с.,интеграл.  Алгоритмы, математическое обеспечение и архитектура многопроцессорных вычислительных систем / Под ред. В.Е. Котова и И. Миклошко — М.: Наука, 1982. — 336 с.  Миренков Н.Н. Параллельное программирование для многомодульных вычислительных систем. — М.: Радио и связь, 1989. — 320с.: ил. — ISBN 5-256-00196-5  Шихаев К.Н. Разностные алгоритмы параллельных вычислительных процессов. — М.: Радио и связь, 1982. — 136с., ил.  Вальковский В.А. Распараллеливание алгоритмов и программ. Структурный подход. М.: Радио и связь, 1989. —176с., ил. — ISBN 5-256-00195-7.
  • 39. Об Авторах Миронов Антон Александрович Занимается вопросами создания и использования высокопроизводительных параллельных алгоритмов. Один из ведущих разработчиков в компании Developer Express Inc. Карпов Андрей Николаевич Занимается вопросами создания программных решений в области повышения качества и быстродействия ресурсоемких приложений. Является одним из авторов статического анализатора Viva64 иVivaMP, предназначенных для верификации 64-битного и параллельного Си/Си++ кода.