uses Arrays;
procedure ParallelMult(a,b,c: array [,] of real; n: integer);
begin
{$omp parallel for }
forvar i:=0 to n-1 do
forvar j:=0 to n-1 do
begin
c[i,j]:=0;
forvar l:=0 to n-1 do
c[i,j]:=c[i,j]+a[i,l]*b[l,j];
end;
end;
procedure Mult(a,b,c: array [,] of real; n: integer);
begin
{$omp parallel for }
forvar i:=0 to n-1 do
forvar j:=0 to n-1 do
begin
c[i,j]:=0;
forvar l:=0 to n-1 do
c[i,j]:=c[i,j]+a[i,l]*b[l,j];
end;
end;
const n = 400;
begin
var a := Arrays.CreateRandomRealMatrix(n,n);
var b := Arrays.CreateRandomRealMatrix(n,n);
var c := new real[n,n];
ParallelMult(a,b,c,n);
writeln('Параллельное перемножение матриц: ',Milliseconds,' миллисекунд');
var d := Milliseconds;
Mult(a,b,c,n);
writeln('Непараллельное перемножение матриц: ',Milliseconds-d,' миллисекунд');
end.
Редукция в директиве parallel for
Часто в цикле накапливается значение некоторой переменной, перед циклом эта переменная инициализируется, а на каждой итерации к ней добавляется некоторое значение или умножается на некоторое значение. Эта переменная должна быть объявлена вне цикла, а значит, будет общей. В таком случае возможны ошибки при параллельном выполнении:
var a: integer:=0;
{$omp parallel for}
forvar i:integer:=1 to 100 do
a := a+1;
Два потока могут считать старое значение, затем первый поток прибавит единицу и запишет в переменную a, затем второй поток прибавит единицу к старому значению и запишет результат в переменную a. При этом изменения, сделанные первым потоком, будут потеряны. Правильная работа программы возможна при некоторых запусках, но возможны и ошибки.
Опция reduction позволяет обеспечить правильное накопление результата:
{$omp parallel for reduction(действие : список переменных)}
При этом все переменные из списка будут объявлены частными, таким образом, разные потоки будут работать со своими экземплярами переменных. Эти экземпляры будут инициализированы в зависимости от действия, а в конце цикла новое значение переменной будет получено из значения этой переменной до цикла и всех частных копий применением действия из опции.
var a: integer := 1;
{$omp parallel for reduction(+:a)}
forvar i: integer:=1 to 2 do
a := a+1;
Здесь начальное значение переменной a – единица, для действия + локальные копии будут инициализированы нулями, будет выполнено две итерации и у каждого потока локальная копия переменной a примет значение 1. После завершения цикла к начальному значению (1) будут прибавлены обе локальные копии, и результирующее значение переменной a будет равно 3, так же как и при последовательном выполнении.
В таблице приведены допустимые операторы редукции и значения, которыми инициализируются локальные копии переменной редукции:
Оператор раздела reduction | Инициализированное значение |
+ | 0 |
* | 1 |
- | 0 |
and (побитовый) | ~0 (каждый бит установлен) |
or (побитовый) | 0 |
xor (побитовый) | 0 |
and (логический) | true |
or (логический) | false |
Параллельные секции и директива parallel sections
Директива parallel sections обеспечивает параллельное выполнение нескольких операторов, простых или составных.
{$omp parallel sections}
begin
...;
end;
Каждый оператор в блоке begin ... end, следующем за директивой является отдельной секцией.
{$omp parallel sections}
begin
begin
end;
end;
Здесь описаны три параллельные секции, первая – оператор 1, вторая – оператор 2 и третья – блок begin ... end, состоящий из операторов 3-5.
Все переменные, описанные вне параллельных секций, будут разделяемыми, то есть, если в секциях есть обращение к таким переменным, то потоки, выполняющие эти секции, будут обращаться к одной и той же ячейке памяти. Все переменные, объявленные внутри секции, будут доступны только в той секции, в которой они объявлены.