FFTW против Matlab FFT

34

Я разместил это на центральном центральном процессоре Matlab, но не получил никаких ответов, поэтому решил, что переставлю здесь.

Недавно я написал простую процедуру в Matlab, которая использует FFT в for-loop; БПФ доминирует над расчетами. Я написал ту же процедуру в mex только для экспериментов, и она называет библиотеку FFTW 3.3. Оказывается, что процедура matlab работает быстрее, чем стандартная mex для очень больших массивов (примерно в два раза быстрее). Мексовая процедура использует мудрость и выполняет те же вычисления FFT. Я также знаю, что Matlab использует FFTW, но возможно ли, что их версия немного оптимизирована? Я даже использовал флаг FFTW_EXHAUSTIVE и его все еще примерно вдвое медленнее для больших массивов, чем аналог MATLAB. Кроме того, я гарантировал, что используемый мною Matlab был однопоточным с флагом "-singleCompThread", а файл mex, который я использовал, не был в режиме отладки. Любопытно, если это было так - или если есть некоторые оптимизации, которые использует Matlab под капотом, о котором я не знаю. Спасибо.

Здесь mex часть:

void class_cg_toeplitz::analysis() {
// This method computes CG iterations using FFTs
    // Check for wisdom
    if(fftw_import_wisdom_from_filename("cd.wis") == 0) {
        mexPrintf("wisdom not loaded.\n");
    } else {
        mexPrintf("wisdom loaded.\n");
    }

    // Set FFTW Plan - use interleaved FFTW
    fftw_plan plan_forward_d_buffer;    
    fftw_plan plan_forward_A_vec;       
    fftw_plan plan_backward_Ad_buffer;
    fftw_complex *A_vec_fft;
    fftw_complex *d_buffer_fft;
    A_vec_fft = fftw_alloc_complex(n);
    d_buffer_fft = fftw_alloc_complex(n);

    // CREATE MASTER PLAN - Do this on an empty vector as creating a plane 
    // with FFTW_MEASURE will erase the contents; 
    // Use d_buffer
    // This is somewhat dangerous because Ad_buffer is a vector; but it does not
    // get resized so &Ad_buffer[0] should work
    plan_forward_d_buffer = fftw_plan_dft_r2c_1d(d_buffer.size(),&d_buffer[0],d_buffer_fft,FFTW_EXHAUSTIVE);
    plan_forward_A_vec = fftw_plan_dft_r2c_1d(A_vec.height,A_vec.value,A_vec_fft,FFTW_WISDOM_ONLY);
    // A_vec_fft.*d_buffer_fft will overwrite d_buffer_fft
    plan_backward_Ad_buffer = fftw_plan_dft_c2r_1d(Ad_buffer.size(),d_buffer_fft,&Ad_buffer[0],FFTW_EXHAUSTIVE);

    // Get A_vec_fft
    fftw_execute(plan_forward_A_vec);

    // Find initial direction - this is the initial residual
    for (int i=0;i<n;i++) {
        d_buffer[i] = b.value[i];
        r_buffer[i] = b.value[i];
    }    

    // Start CG iterations
    norm_ro = norm(r_buffer);
    double fft_reduction = (double)Ad_buffer.size(); // Must divide by size of vector because inverse FFT does not do this
    while (norm(r_buffer)/norm_ro > relativeresidual_cutoff) {        
        // Find Ad - use fft
        fftw_execute(plan_forward_d_buffer);    
        // Get A_vec_fft.*fft(d) - A_vec_fft is only real, but d_buffer_fft
        // has complex elements; Overwrite d_buffer_fft        
        for (int i=0;i<n;i++) {
            d_buffer_fft[i][0] = d_buffer_fft[i][0]*A_vec_fft[i][0]/fft_reduction;
            d_buffer_fft[i][1] = d_buffer_fft[i][1]*A_vec_fft[i][0]/fft_reduction;
        }        
        fftw_execute(plan_backward_Ad_buffer); 

        // Calculate r'*r
        rtr_buffer = 0;
        for (int i=0;i<n;i++) {
            rtr_buffer = rtr_buffer + r_buffer[i]*r_buffer[i];
        }    

        // Calculate alpha
        alpha = 0;
        for (int i=0;i<n;i++) {
            alpha = alpha + d_buffer[i]*Ad_buffer[i];
        }    
        alpha = rtr_buffer/alpha;

        // Calculate new x
        for (int i=0;i<n;i++) {
            x[i] = x[i] + alpha*d_buffer[i];
        }   

        // Calculate new residual
        for (int i=0;i<n;i++) {
            r_buffer[i] = r_buffer[i] - alpha*Ad_buffer[i];
        }   

        // Calculate beta
        beta = 0;
        for (int i=0;i<n;i++) {
            beta = beta + r_buffer[i]*r_buffer[i];
        }  
        beta = beta/rtr_buffer;

        // Calculate new direction vector
        for (int i=0;i<n;i++) {
            d_buffer[i] = r_buffer[i] + beta*d_buffer[i];
        }  

        *total_counter = *total_counter+1;
        if(*total_counter >= iteration_cutoff) {
            // Set total_counter to -1, this indicates failure
            *total_counter = -1;
            break;
        }
    }

    // Store Wisdom
    fftw_export_wisdom_to_filename("cd.wis");

    // Free fft alloc'd memory and plans
    fftw_destroy_plan(plan_forward_d_buffer);
    fftw_destroy_plan(plan_forward_A_vec);
    fftw_destroy_plan(plan_backward_Ad_buffer);
    fftw_free(A_vec_fft);
    fftw_free(d_buffer_fft);
};

Здесь часть matlab:

% Take FFT of A_vec.
A_vec_fft = fft(A_vec); % Take fft once

% Find initial direction - this is the initial residual 
x = zeros(n,1); % search direction
r = zeros(n,1); % residual
d = zeros(n+(n-2),1); % search direction; pad to allow FFT
for i = 1:n
    d(i) = b(i); 
    r(i) = b(i); 
end

% Enter CG iterations
total_counter = 0;
rtr_buffer = 0;
alpha = 0;
beta = 0;
Ad_buffer = zeros(n+(n-2),1); % This holds the product of A*d - calculate this once per iteration and using FFT; only 1:n is used
norm_ro = norm(r);

while(norm(r)/norm_ro > 10^-6)
    % Find Ad - use fft
    Ad_buffer = ifft(A_vec_fft.*fft(d)); 

    % Calculate rtr_buffer
    rtr_buffer = r'*r;

    % Calculate alpha    
    alpha = rtr_buffer/(d(1:n)'*Ad_buffer(1:n));

    % Calculate new x
    x = x + alpha*d(1:n);

    % Calculate new residual
    r = r - alpha*Ad_buffer(1:n);

    % Calculate beta
    beta = r'*r/(rtr_buffer);

    % Calculate new direction vector
    d(1:n) = r + beta*d(1:n);      

    % Update counter
    total_counter = total_counter+1; 
end

В терминах времени для N = 50000 и b = 1: n требуется около 10,5 секунд с mex и 4,4 секунды с помощью matlab. Я использую R2011b. Благодаря

  • 0
    Каковы размеры ваших данных, и каковы абсолютные времена?
  • 0
    Они оба на месте?
Показать ещё 14 комментариев
Теги:
mex
fftw

4 ответа

13

Несколько замечаний, а не определенный ответ, поскольку я не знаю какой-либо специфики реализации MATLAB FFT:

  • На основе кода, который у вас есть, я вижу два объяснения разницы в скорости:
    • разность скоростей объясняется различиями в уровнях оптимизации FFT
    • цикл while в MATLAB выполняется значительно меньшее количество раз

Предполагаю, что вы уже рассмотрели вторую проблему и количество итераций сопоставимо. (Если это не так, это, скорее всего, некоторые вопросы точности и заслуживающие дальнейшего изучения.)

Теперь, если сравнивать скорость FFT:

  • Да, теория заключается в том, что FFTW быстрее других реализаций FFT на высоком уровне, но это актуально только при сравнении яблок с яблоками: здесь вы сравниваете реализации на уровне ниже, на уровне сборки, где играет не только выбор алгоритма, но и его фактическая оптимизация для конкретного процессора и разработчиков программного обеспечения с различными навыками.
  • Я оптимизировал или просмотрел оптимизированные БПФ в сборке на многих процессорах в течение года (я был в индустрии бенчмаркинга), и отличные алгоритмы - лишь часть истории. Существуют соображения, которые очень специфичны для архитектуры, которую вы кодируете (учет задержек, планирование инструкций, оптимизация использования регистров, организация данных в памяти, учет отложенных/не принятых латентностей и т.д.) И которые делают различия так же важно, как выбор алгоритма.
  • С N = 500000 мы также говорим о больших буферах памяти: еще одна дверь для большего количества оптимизаций, которые могут быстро стать довольно специфичными для платформы, на которой вы запускаете свой код: насколько хорошо вам удастся избежать промахов в кеше, не будет продиктованный алгоритмом так же, как и поток данных, и то, что оптимизаторы программного обеспечения могли использовать для эффективного и эффективного ввода данных в и из памяти.
  • Хотя я не знаю подробностей реализации MATLAB FFT, я вполне уверен, что армия инженеров DSP была (и по-прежнему) оттачивает свою оптимизацию, поскольку она является ключевым фактором для многих проектов. Это вполне может означать, что у MATLAB была правильная комбинация разработчиков для создания гораздо более быстрого БПФ.
  • 0
    Лоло, суть проблемы в том, что MATLAB реализует FFTW. Как ни странно, число итераций до сходимости в подпрограмме MATLAB представляется недетерминированным. для N = 1000 требуется 83-85, тогда как версия mex постоянна (85 для N = 1000, если я правильно помню). На данный момент я только что пришел к выводу, что matlab должен делать что-то «под капотом», о котором я не знаю ... Либо это, либо моя mex-реализация медленнее, потому что я где-то пропустил оптимизацию. Точно сказать не могу.
  • 1
    @jucestain Все, что вы говорите, указывает мне на один и тот же вывод: 83-85 против 85 означает, что только производительность FFT объясняет разницу, как и ваши данные профилирования 90% против 84,99%. Реализация MATLAB просто лучше оптимизирована, что вполне возможно при использовании такого алгоритма с таким большим количеством возможностей для оптимизации на каждом этапе. Я бы не назвал это уловками «под капотом», а просто потратил время на создание реализации FFT, оптимизированной на лучшем уровне, чем аналог MEX, который вы используете. Я не думаю, что вы ничего не упустили в своей реализации mex.
Показать ещё 6 комментариев
8

Это классическое увеличение производительности благодаря оптимизации на уровне низкого уровня и архитектуры.

Matlab использует FFT из библиотеки Intel MKL (Math Kernel Library) (mkl.dll). Эти подпрограммы оптимизированы (на уровне сборки) процессорами Intel для Intel. Даже на AMD это, похоже, дает хорошие повышения производительности.

FFTW выглядит как обычная библиотека c, которая не оптимизирована. Следовательно, усиление производительности для использования MKL.

  • 4
    MATLAB поставляется с собственной сборкой библиотеки FFTW с открытым исходным кодом, скомпилированной с поддержкой многопоточности и векторизованными инструкциями SSE / AVX. Вызывающая version('-fftw') показывает FFTW-3.3.3-sse2-avx . В папке bin MATLAB есть две общие библиотеки, которые экспортируют интерфейс API FFTW: libmwfftw3.dll и libmwfftw3f.dll (в дополнение к третьей lib libmwmfl_fft.dll построенной поверх двух предыдущих, предназначенной для абстрагирования использования планов FFTW) , Поэтому, хотя MATLAB использует Intel MKL в качестве оптимизированной реализации BLAS / LAPACK, насколько я могу судить, он не вызывает интерфейс FFTW из MKL.
  • 0
    @Amro Спасибо за разъяснения! Кстати, просто чтобы узнать, как вы узнали, что эти два двоичных файла экспортируют интерфейс API FFTW? А знаете ли вы, в чем разница между обоими двоичными файлами? В моем R2010a у меня есть только одна библиотека libmwfftw.dll ...
Показать ещё 3 комментария
2

EDIT: @wakjah ответ на этот ответ верен: FFTW поддерживает разделение реального и мнимого хранилища памяти через интерфейс Guru. Таким образом, мои претензии в отношении взлома неточны, но могут очень хорошо применяться, если интерфейс FFTW Guru не используется - по умолчанию, так что остерегайтесь!

Во-первых, извините за то, что вы опаздываете на год. Я не уверен, что увеличение скорости, которое вы видите, происходит от MKL или других оптимизаций. Между FFTW и Matlab существует нечто принципиально иное, и именно так хранятся сложные данные в памяти.

В Matlab действительная и мнимая части комплексного вектора X представляют собой отдельные массивы Xre [i] и Xim [i] (линейные по памяти, эффективные при работе на любом из них отдельно).

В FFTW действительная и мнимая части чередуются как двойные [2] по умолчанию, т.е. X [i] [0] - действительная часть, а X [i] [1] - мнимая часть.

Таким образом, чтобы использовать библиотеку FFTW в mex файлах, нельзя использовать массив Matlab напрямую, но сначала необходимо выделить новую память, а затем упаковать входные данные из Matlab в формат FFTW, а затем распаковать вывод из FFTW в формат Matlab. то есть.

X = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
Y = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);

затем

for (size_t i=0; i<N; ++i) {
    X[i][0] = Xre[i];
    X[i][1] = Xim[i];
}

затем

for (size_t i=0; i<N; ++i) {
    Yre[i] = Y[i][0];
    Yim[i] = Y[i][1];
}

Следовательно, для этого требуются 2x выделения памяти + 4x чтение + 4x записи - весь размер N. Это требует больших сбоев при больших проблемах.

У меня есть подозрение, что Mathworks, возможно, взломал код FFTW3, чтобы он мог читать входные векторы непосредственно в формате Matlab, что позволяет избежать всего вышеперечисленного.

В этом случае можно выделить X и использовать X для Y для запуска FFTW на месте (как fftw_plan_*(N, X, X, ...) вместо fftw_plan_*(N, X, Y, ...)), так как он будет скопирован в вектор Yre и Yim Matlab, если только приложение требует/выгоды от хранения X и Y отдельно.

EDIT. Рассматривая потребление памяти в режиме реального времени при запуске Matlab fft2() и моего кода на основе библиотеки fftw3, он показывает, что Matlab выделяет только один дополнительный сложный массив (вывод), тогда как моему коду нужны два таких массива (буфер *fftw_complex плюс вывод Matlab). Преобразование на месте между форматами Matlab и fftw невозможно, поскольку реальные и мнимые массивы Matlab не являются последовательными в памяти. Это говорит о том, что Mathworks взломал библиотеку fftw3 для чтения/записи данных с использованием формата Matlab.

Еще одна оптимизация для нескольких вызовов заключается в постоянном распределении (используя mexMakeMemoryPersistent()). Я не уверен, что реализация Matlab также делает это.

Приветствия.

p.s. В качестве побочного примечания, формат хранения сложных данных Matlab более эффективен для работы на реальных или мнимых векторах отдельно. В формате FFTW вам нужно будет читать + 2 чтения.

  • 1
    За исключением того, что интерфейс FFTW Guru поддерживает разделение действительных и сложных массивов, т. Е. Аналогично формату MATLAB, взлома не требуется.
  • 0
    @wakjah Я исправлюсь, +1 и спасибо! Я отредактировал свой ответ, чтобы отразить ваш ответ.
2

Я нашел следующий комментарий на веб-сайте MathWorks [1]:

Замечание о больших степенях 2: для измерений FFT, которые являются степенями 2, между 2 ^ 14 и 2 ^ 22, программное обеспечение MATLAB использует специальные предустановленные информацию во внутренней базе данных для оптимизации вычисления БПФ. Никакая настройка не выполняется, когда размер FTT составляет 2, если вы не очистите базу данных с помощью команды fftw ( "мудрость", []).

Хотя это относится к степеням 2, он может напомнить, что MATLAB использует свою собственную "особую мудрость" при использовании FFTW для определенных (больших) размеров массива. Рассмотрим: 2 ^ 16 = 65536.

[1] R2013b Документация доступна из http://www.mathworks.de/de/help/matlab/ref/fftw.html (доступ к ней 29 октября 2013 года)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню