|
"...Изучение и анализ примеров.
В и приведены описания и приложены исходные коды параллельных программ..." Непонятно что такое - "В и приведены описания" и где именно приведены и приложены исходные коды. |
Параллельный алгоритм дискретного преобразования Фурье
Напомним коротко основные понятия и определения, относящиеся к дискретному предобразованию Фурье (ДПФ). Более подробно об этом см., например, в главе 1 книги [ [ 4 ] ].
Пусть
является вектором с вещественными или комплексными компонентами. Дискретным преобразованием Фурье вектора называется вектор
длины
с комплексными компонентами, определяемыми равенствами:
![]() |
( 1) |
и
.Вычисление преобразования Фурье в виде (1), как оно записано выше, требует порядка
умножений и
сложений.
В книге [
[
5
]
], раздел 32.3, приведен алгоритм вычисления ДПФ, требующий порядка
операций, и потому названный алгоритмом быстрого преобразования Фурье (БПФ). Однако, этот алгоритм трудно поддается распараллеливанию, а потому, используя его, трудно получить дальнейшее снижение сложности алгоритма БПФ.
Однако, существует другой алгоритм - так называемый алгоритм Кули-Тьюки (книга [
[
4
]
], глава 4), который применим когда число
является составным. В частности, если
для некоторых
и
, то алгоритм Кули-Тьюки требует порядка
умножений, но который эффективно параллелится. Особенности параллельной реализации алгоритма Кули-Тьюки состоят в том, что
- входной и выходной векторы рассматриваются как двумерные таблицы, столбцы и строки которых могут обрабатываться независимо в различных потоках,
- в рамках одного потока, обработка отдельных столбцов и строк представляет собой вычисление ДПФ с помощью алгоритма БПФ.
Суть алгоритма Кули-Тьюки (дополнительные подробности см. в книге [
[
4
]
], глава 4) состоит в преобразовании выражения ~(\ref{travial}) в соответствии со следующими равенствами для индексов
и
:


Тогда, выражение (1) может быть переписано в виде
![]() |
( 2) |
в котором




В этом выражении, при каждом значении
внутренняя сумма представляет собой
-точечное преобразование Фурье, а внешняя сумма есть
-точечное преобразование Фурье. Соответственно, блок-схема последовательного алгоритма Кули-Тьюки может быть представлена следующим образом:
Заметим, что реального отображения исходного вектора в двумерный массив не происходит - эта операция заменяется соответствующим пересчетом индексов.
Ниже представлена базовая часть БПФ-алгоритма Кули-Тьюки, которая включает в себя 4 шага:
- применение БПФ к каждому столбцу,
- поэлементное умножение на
, - построчное применение БПФ,
- восстановление результирующего вектора из двумерной таблицы.
// Cooley-Tukey Algorithm
public Complex[] FFT(Complex[] X, int n1, int n2)
{
int n = X.Length;
Complex[,] Y = new Complex[n1,n2];
Complex[,] P = new Complex[n1,n2];
Complex[] T = new Complex[n];
for (int k = 0; k < n1; k++)
ColumnFFT(X, Y, n1, n2 ,k);
for (int k = 0; k < n1; k++)
TwiddleFactorMult(Y, n1, n2, k);
for (int i = 0; i < n2; i++)
RowFFT(Y, P, i, n1);
//Sort elements in right order to create output vector
for (int j = 0; j < n2; j++)
for (int i = 0; i < n1; i++)
T[i * n2 + j] = P[i, j];
return T;
}Здесь, ColumnFFT и RowFFT - функции, осуществляющие БПФ для одного столбца и, соответственно, строки матрицы, функция TwiddleFactorMult - функция, осуществляющая домножение столбца матрицы на дополнительный множитель, а функция TL_idx реализует транспонирование линеаризованной таблицы.
Параллельная реализация БПФ-алгоритма Кули-Тьюки состоит из двух следующих друг за другом циклов Parallel.For, где в первом цикле выполняются шаги (1) и (2) последовательного алгоритма, а во втором цикле - шаги (3) и (4).
// Cooley-Tukey Algorithm. Parallel Implementation
public Complex[] FFT(Complex[] X, int n1, int n2)
{
int n = X.Length;
Complex[,] Y = new Complex[n1, n2];
Complex[,] P = new Complex[n1, n2];
Complex[] T = new Complex[n];
Parallel.For(0,n1,k= > {
ColumnFFT(X, Y, n1, n2, k);
TwiddleFactorMult(Y, n1, n2, k);
});
Parallel.For(0, n2, q = >
{
RowFFT(Y, P, q, n1);
//Sort elements in right order to create output vector
for (int i = 0; i < n1; i++)
T[i * n2 + q] = P[i, q];
});
return T;
}Полностью код параллельной версии БПФ-алгоритма Кули-Тьюки приведен ниже:
//PFX Parallel Fast Fourier Transform (pFFT)
//This implementation is based on the Cooley-Tukey algorithm
using System;
using System.Text;
using System.Threading;
public class Complex
{
public double Re = 0.0;
public double Im = 0.0;
public Complex() { }
public Complex(double re, double im) { Re = re; Im = im; }
public override string ToString()
{
return Re + " " + Im;
}
}
class Program
{
public static void Main(string[] args)
{
if (args.Length != 3)
{
Console.WriteLine(Usage());
return;
}
//n - input vector length (must be power of two)
//n1 - number of Cooley-Tukey's matrix columns
//n2 - number of Cooley-Tukey's matrix rows
int n = 0, n1 = 0, n2 = 0;
n = (int)Math.Pow(2, Int32.Parse(args[0]));
n1 = Int32.Parse(args[1]);
//input vector generation
Complex[] X = new Complex[n];
Random r = new Random();
for (int i = 0; i < n; i++)
X[i] = new Complex(r.NextDouble() * 10, 0);
if ((n1 > n) || (n1 < = 0))
{
Console.WriteLine(Usage() + " Param n1 is invalid: n1= " + n1.ToString() + ". Vector length= " + X.Length.ToString());
return;
}
n2 = n / n1;
Console.WriteLine("*RUN*");
DateTime dt1 = DateTime.Now;
Complex[] pY = (new Program()).FFT(X, n1, n2);
DateTime dt2 = DateTime.Now;
Console.WriteLine(" Parallel FFT: ");
Console.WriteLine(" n= " + n + " n1= " + n1 + " n2= " + n2 +
" Elapsed time is " + (dt2 - dt1).TotalSeconds);
}
// Cooley-Tukey Algorithm. Parallel Implementation
public Complex[] FFT(Complex[] X, int n1, int n2)
{
int n = X.Length;
Complex[,] Y = new Complex[n1, n2];
Complex[,] P = new Complex[n1, n2];
Complex[] T = new Complex[n];
Parallel.For(0,n1,k= > {
ColumnFFT(X, Y, n1, n2, k);
TwiddleFactorMult(Y, n1, n2, k);
});
Parallel.For(0, n2, q = >
{
RowFFT(Y, P, q, n1);
//Sort elements in right order to create output vector
for (int i = 0; i < n1; i++)
T[i * n2 + q] = P[i, q];
});
return T;
}
private void TwiddleFactorMult(Complex[,] Y, int n1, int n2, int k)
{
//Column Twiddle Factor Multiplication
double wn_Re = 0, arg = 0, wn_Im = 0, tmp = 0;
for (int q = 0; q < n2; q++)
{
arg = 2 * Math.PI * k * q / (n2 * n1);
wn_Re = Math.Cos(arg);
wn_Im = Math.Sin(arg);
tmp = Y[k, q].Re * wn_Re - Y[k, q].Im * wn_Im;
Y[k, q].Im = Y[k, q].Re * wn_Im + Y[k, q].Im * wn_Re;
Y[k, q].Re = tmp;
}
}
public void ColumnFFT(Complex[] a, Complex[,] A, int n1, int n2, int k)
{
int q, m, m2, s;
double wn_Re, wn_Im, w_Re, w_Im;
double arg, t_Re, t_Im;
double u_Re, u_Im, tmp;
int logN = 0;
m = n2;
while (m > 1)
{
m = m / 2;
logN++;
}
int temp;
for (q = 0; q < n2; q++)
{
temp = bit_reverse(q, logN);
A[k, temp] = a[k + q * n1];
}
for (s = 1; s < = logN; s++)
{
m = 1 < < s;
arg = 2.0 * Math.PI / m;
wn_Re = Math.Cos(arg);
wn_Im = Math.Sin(arg);
w_Re = 1.0; w_Im = 0.0;
m2 = m > > 1;
for (int i = 0; i < m2; i++)
{
for (int j = i; j < n2; j += m)
{
t_Re = w_Re * A[k, j + m2].Re - w_Im * A[k, j + m2].Im;
t_Im = w_Re * A[k, j + m2].Im + w_Im * A[k, j + m2].Re;
u_Re = A[k, j].Re;
u_Im = A[k, j].Im;
A[k, j].Re = u_Re + t_Re;
A[k, j].Im = u_Im + t_Im;
A[k, j + m2].Re = u_Re - t_Re;
A[k, j + m2].Im = u_Im - t_Im;
}
tmp = w_Re * wn_Re - w_Im * wn_Im;
w_Im = w_Re * wn_Im + w_Im * wn_Re;
w_Re = tmp;
}
}
}
public void RowFFT(Complex[,] a, Complex[,] A, int q, int n1)
{
int j, k, m, m2, s;
double wn_Re, wn_Im, w_Re, w_Im;
double arg, t_Re, t_Im;
double u_Re, u_Im, tmp;
int logN = 0;
m = n1;
while (m > 1)
{
m = m / 2;
logN++;
}
for (k = 0; k < n1; k++)
A[bit_reverse(k, logN), q] = a[k, q];
for (s = 1; s < = logN; s++)
{
m = 1 < < s;
arg = 2.0 * Math.PI / m;
wn_Re = Math.Cos(arg);
wn_Im = Math.Sin(arg);
w_Re = 1.0; w_Im = 0.0;
m2 = m > > 1;
for (j = 0; j < m2; j++)
{
for (k = j; k <= n1; k += m)
{
t_Re = w_Re * A[k + m2, q].Re - w_Im * A[k + m2, q].Im;
t_Im = w_Re * A[k + m2, q].Im + w_Im * A[k + m2, q].Re;
u_Re = A[k, q].Re;
u_Im = A[k, q].Im;
A[k, q].Re = u_Re + t_Re;
A[k, q].Im = u_Im + t_Im;
A[k + m2, q].Re = u_Re - t_Re;
A[k + m2, q].Im = u_Im - t_Im;
}
tmp = w_Re * wn_Re - w_Im * wn_Im;
w_Im = w_Re * wn_Im + w_Im * wn_Re;
w_Re = tmp;
}
}
}
public int bit_reverse(int k, int size)
{
int right_unit = 1;
int left_unit = 1 < < (size - 1);
int result = 0, bit;
for (int i = 0; i < size; i++)
{
bit = k & right_unit;
if (bit != 0)
result = result | left_unit;
right_unit = right_unit < < 1;
left_unit = left_unit > > 1;
}
return (result);
}
static string Usage()
{
string pname = System.Reflection.Assembly.GetEntryAssembly().GetName().Name;
StringBuilder s = new StringBuilder();
for (int i = 0; i < Console.WindowWidth; i++) s.Append(" - ");
return (s.ToString() + Environment.NewLine +
" Usage: " + pname + " p n1 np " + Environment.NewLine +
" where " + Environment.NewLine +
" p - n=2^p - input vector length " + Environment.NewLine +
" n1 - width of block " + Environment.NewLine +
" np - number of processes " + Environment.NewLine +
s.ToString() + Environment.NewLine);
}
}

![}\label{travial}V_k1,k2=\sum_{i1=0}^{n1-1}b^{i1k1}[w^{i1k2}\sum_{i2=0}^{n2-1}y^{i2k2}v_{i1i2}].](/sites/default/files/tex_cache/b092efecfcc24ff9e6da5b6f22100562.png)
