"...Изучение и анализ примеров.
В и приведены описания и приложены исходные коды параллельных программ..." Непонятно что такое - "В и приведены описания" и где именно приведены и приложены исходные коды. |
Параллельный алгоритм дискретного преобразования Фурье
Напомним коротко основные понятия и определения, относящиеся к дискретному предобразованию Фурье (ДПФ). Более подробно об этом см., например, в главе 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); } }