Улучшение скорости выполнения БПФ

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

Мне удалось реализовать следующее:

Есть ли у кого-нибудь альтернативы и предложения, чтобы улучшить скорость программы, не теряя при этом точность.

short FFTMethod::FFTcalc(short int dir,long m,double *x,double *y)
{
long n,i,i1,j,k,i2,l,l1,l2;
double c1,c2,tx,ty,t1,t2,u1,u2,z;

/* Calculate the number of points */
n = 1;
for (i=0;i<m;i++) 
    n *= 2;

/* Do the bit reversal */
i2 = n >> 1;
j = 0;
for (i=0;i<n-1;i++) {
  if (i < j) {
     tx = x[i];
     ty = y[i];
     x[i] = x[j];
     y[i] = y[j];
     x[j] = tx;
     y[j] = ty;
  }
  k = i2;
  while (k <= j) {
     j -= k;
     k >>= 1;
  }
  j += k;
}

/* Compute the FFT */
c1 = -1.0; 
c2 = 0.0;
l2 = 1;
for (l=0;l<m;l++) {
   l1 = l2;
   l2 <<= 1;
   u1 = 1.0; 
   u2 = 0.0;
   for (j=0;j<l1;j++) {
     for (i=j;i<n;i+=l2) {
        i1 = i + l1;
        t1 = u1 * x[i1] - u2 * y[i1];
        t2 = u1 * y[i1] + u2 * x[i1];
        x[i1] = x[i] - t1; 
        y[i1] = y[i] - t2;
        x[i] += t1;
        y[i] += t2;
     }
     z =  u1 * c1 - u2 * c2;
     u2 = u1 * c2 + u2 * c1;
     u1 = z;
   }
   c2 = sqrt((1.0 - c1) / 2.0);
   if (dir == 1) 
     c2 = -c2;
     c1 = sqrt((1.0 + c1) / 2.0);
  }

/* Scaling for forward transform */
if (dir == 1) {
   for (i=0;i<n;i++) {
      x[i] /= n;
      y[i] /= n;
   }
 } 


   return(1);
}

Ответ 1

Недавно я нашел этот отличный PDF файл на построении высокопроизводительных БПФ Эриком Постщичем. Разработав несколько БПФ, я знаю, как трудно конкурировать с коммерческими библиотеками. Поверьте, у вас все хорошо, если ваш FFT всего в 4 раза медленнее Intel или FFTW, а не 40x! Однако вы можете конкурировать, и вот как.

Подводя итог этой статье, автор заявляет, что Radix2 FFT простые, но неэффективные, наиболее эффективной конструкцией является radix4 FFT. Еще более эффективным методом является Radix8, однако это часто не вписывается в регистры на CPU, поэтому Radix4 является предпочтительным.

БПФ могут быть построены поэтапно, поэтому для вычисления БПФ с 1024 точками вы можете выполнить 10 этапов Radix2 FFT (как 2 ^ 10 - 1024) или 5 этапов Radix4 FFT (4 ^ 5 = 1024). Вы можете даже вычислить БПФ с разрешением 1024 балла по этапам 8 * 4 * 4 * 4 * 2, если вы этого захотите. Меньше этапов означает меньшее количество чтения и записи в память (узким местом для производительности FFT является пропускная способность памяти), поэтому динамический выбор radix 4, 8 или выше является обязательным. Шаг Radix4 особенно эффективен, так как все веса равны 1 + 0i, 0 + 1i, -1 + 0i, 0-1i и код бабочки Radix4 могут быть записаны, чтобы полностью входить в кеш.

Во-вторых, каждый этап БПФ не является одним и тем же. На первом этапе веса все равны 1 + 0i. нет смысла вычислять этот вес и даже умножать на него, поскольку он комплексно умножается на 1, поэтому первый этап может выполняться без весов. Заключительный этап может также обрабатываться по-разному и может использоваться для выполнения Децимации во времени (разворот бит). Документ Эрика Postpischil охватывает все это.

Весы могут быть предварительно вычислены и сохранены в таблице. Расчеты Sin/cos занимают около 100-150 циклов на аппаратных средствах x86, поэтому их предварительная вычисление может сэкономить 10-20% от общего времени вычисления, поскольку доступ к памяти в этом случае будет быстрее, чем вычисления ЦП. Использование быстрых алгоритмов для вычисления sincos за один раз особенно полезно (обратите внимание, что cos равно sqrt (1,0 - синус * синус) или с использованием табличного поиска, cos - просто сдвиг фазы синуса).

Наконец, как только у вас будет супер оптимизированная реализация FFT, вы можете использовать векторизацию SIMD для вычисления 4x операций с плавающей запятой или 2x двойных операций с плавающей запятой за цикл внутри программы бабочки для повышения скорости на 100-300%. Принимая все вышесказанное, у вас будет очень быстрый и быстрый FFT!

Чтобы идти дальше, вы можете выполнять оптимизацию "на лету", предоставляя различные реализации этапов FFT, ориентированных на конкретные архитектуры процессоров. Размер кэша, количество регистров, наборов инструкций SSE/SSE2/3/4 и т.д. Различаются для каждой машины, поэтому выбор одного размера подходит для любого подхода часто избивается целевыми процедурами. Например, в FFTW многие БПФ меньшего размера представляют собой высоко оптимизированные развернутые (без циклов) реализации, предназначенные для конкретной архитектуры. Объединив эти небольшие конструкции (например, подпрограммы RadixN), вы можете выбрать самую быструю и лучшую процедуру для этой задачи.

Ответ 2

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

  • Если вы этого еще не сделали, напишите несколько тестов правильности для своего кода прямо сейчас. Простые тесты, такие как "выполнить БПФ этого массива и посмотреть, соответствуют ли результаты тем, которые я вам предоставил", но перед оптимизацией кода вам нужен фирменный и автоматизированный unit test, который подтверждает, что ваш оптимизированный код верен.
  • Затем профиль вашего кода, чтобы узнать, где находится фактическое узкое место. Хотя я подозреваю самый внутренний цикл for (i=j;i<n;i+=l2) {, видеть лучше, чем полагать.

Ответ 3

Есть несколько вещей, которые я могу порекомендовать:

  • Не заменяйте входные элементы, а вычисляйте индекс, инвертированный по битам. Это спасет вас от чтения и записи памяти.
  • Предварительно рассчитайте коэффициенты, если вы делаете много БПФ одного размера. Это позволит сэкономить некоторые вычисления.
  • Используйте radix-4 FFT вместо radix-2. Это приведет к меньшему количеству итераций во внутренних циклах.

Окончательный ответ можно, конечно, найти путем профилирования кода.

Ответ 4

Это выглядит как базовая реализация FFT-кода radix-2 прямо из старого учебника. Существует много десятков десятилетних документов по оптимизации БПФ по-разному, в зависимости от многих факторов. Например, ваши данные меньше кэша процессора?

Добавлено: например, если вектор данных плюс таблица коэффициентов будет вписываться в ЦП dcache и/или если умножения намного медленнее, чем обращения к памяти на вашем CPU, то предварительная вычисление таблицы факторов поворота может уменьшить общее количество циклов для повторного использования БПФ. Но если нет, предварительный расчет может быть на самом деле медленнее. Benchmark. YMMV.