Почему FFTW в Windows работает быстрее, чем в Linux?

Я написал две идентичные программы для Linux и Windows, используя библиотеки fftw (fftw3.a, fftw3.lib), и вычислил продолжительность оператора fftwf_execute(m_wfpFFTplan) (16-fft).

На 10000 запусков:

  • В Linux: среднее время 0,9
  • В Windows: среднее время 0,12

Я не понимаю, почему это в девять раз быстрее в Windows, чем в Linux.

Процессор: Intel(R) Core(TM) i7 CPU 870 @ 2,93 ГГц

Каждая ОС (32-разрядная версия Windows XP и 32-разрядная версия Linux OpenSUSE 11.4) устанавливаются на одни и те же машины.

Я скачал fftw.lib (для Windows) из Интернета и не знаю этих конфигураций. Как только я соберу FFTW с этой конфигурацией:

/configure --enable-float  --enable-threads --with-combined-threads  --disable-fortran  --with-slow-timer  --enable-sse  --enable-sse2  --enable-avx   

в Linux, и это приводит к тому, что библиотека работает в четыре раза быстрее, чем конфигурации по умолчанию (0,4 мс).


person Ali Nima    schedule 31.12.2011    source источник
comment
Скомпилированы ли библиотеки fftw одним и тем же компилятором (и версией)? Они скомпилированы с одинаковыми флагами? Скомпилированы для одной и той же архитектуры? Возможно, сборка Windows лучше использует возможности процессора. Возможно, это другой компилятор, поэтому он оптимизирован по-другому.   -  person Roger Lipscombe    schedule 31.12.2011
comment
Пожалуйста, опубликуйте подробную информацию о компиляторах, которые вы использовали на обеих платформах, и параметрах компиляции, используемых как для вашего кода, так и для создания этой библиотеки.   -  person Mat    schedule 31.12.2011
comment
разрядность одинаковая (32 против 64 бит)? такое же количество оперативной памяти доступно? какие еще процессы выполняются параллельно? любая активная виртуализация?   -  person Yahia    schedule 31.12.2011
comment
На этот вопрос невозможно ответить, не изучив исходный код двух программ и изучив аппаратное и программное обеспечение, а также программные конфигурации системы, на которой проводилось тестирование. :)   -  person vdbuilder    schedule 31.12.2011
comment
Я только что посмотрел некоторые тайминги БПФ, которые я вычислил, используя FFTW на одноядерном процессоре Pentium4 с тактовой частотой 3 ГГц (еще в 2005 году), и вычисление 16-длинного БПФ менее чем за 2 мс, то есть: 0,002 мс. Время не превышало 1 мс, пока длина не достигла 16384. 16-длинное БПФ должно быть чрезвычайно легким. Выполняете ли вы этап планирования вне контрольного таймера? Используете ли вы FFTW_MEASURE при планировании? Использование SSE/SSE2 и полная оптимизация являются обязательными, поскольку Windows (особенно Visual Studio) включит их по умолчанию, когда выбран режим выпуска.   -  person Dr. Andrew Burnett-Thompson    schedule 31.12.2011


Ответы (1)


16 FFT очень мало. Вы обнаружите, что БПФ меньше, чем, скажем, 64, будут жестко закодированы на ассемблере без циклов, чтобы получить максимально возможную производительность. Это означает, что они могут быть очень восприимчивы к изменениям в наборах инструкций, оптимизации компилятора и даже к 64- или 32-битным словам.

Что происходит, когда вы запускаете тест размеров БПФ от 16 до 1048576 в степени двойки? Я говорю это, потому что конкретная жестко запрограммированная процедура asm в Linux может быть не лучшим образом оптимизирована для вашей машины, тогда как вам может повезти в реализации Windows для этого конкретного размера. Сравнение всех размеров в этом диапазоне даст вам лучшее представление о производительности Linux и Windows.

Вы калибровали FFTW? При первом запуске FFTW угадывает самую быструю реализацию для каждой машины, однако, если у вас есть специальные наборы инструкций, кеш-память определенного размера или другие функции процессора, это может существенно повлиять на скорость выполнения. В результате выполнение калибровки проверит скорость различных процедур БПФ и выберет самую быструю для вашего конкретного оборудования. Калибровка включает в себя повторное вычисление планов и сохранение сгенерированного файла «Мудрость» FFTW. Сохраненные данные калибровки (это длительный процесс) можно затем использовать повторно. Я предлагаю сделать это один раз при запуске вашего программного обеспечения и каждый раз повторно использовать файл. Я заметил 4-10-кратное улучшение производительности для определенных размеров после калибровки!

Ниже приведен фрагмент кода, который я использовал для калибровки FFTW для определенных размеров. Обратите внимание, что этот код дословно вставлен из библиотеки DSP, над которой я работал, поэтому некоторые вызовы функций специфичны для моей библиотеки. Я надеюсь, что конкретные звонки FFTW будут полезны.

// Calibration FFTW
void DSP::forceCalibration(void)
{
// Try to import FFTw Wisdom for fast plan creation
FILE *fftw_wisdom = fopen("DSPDLL.ftw", "r");

// If wisdom does not exist, ask user to calibrate
if (fftw_wisdom == 0)
{
    int iStatus2 = AfxMessageBox("FFTw not calibrated on this machine."\
        "Would you like to perform a one-time calibration?\n\n"\
        "Note:\tMay take 40 minutes (on P4 3GHz), but speeds all subsequent FFT-based filtering & convolution by up to 100%.\n"\
        "\tResults are saved to disk (DSPDLL.ftw) and need only be performed once per machine.\n\n"\
        "\tMAKE SURE YOU REALLY WANT TO DO THIS, THERE IS NO WAY TO CANCEL CALIBRATION PART-WAY!", 
        MB_YESNO | MB_ICONSTOP, 0);

    if (iStatus2 == IDYES)
    {
        // Perform calibration for all powers of 2 from 8 to 4194304
        // (most heavily used FFTs - for signal processing)
        AfxMessageBox("About to perform calibration.\n"\
            "Close all programs, turn off your screensaver and do not move the mouse in this time!\n"\
            "Note:\tThis program will appear to be unresponsive until the calibration ends.\n\n"
            "\tA MESSAGEBOX WILL BE SHOWN ONCE THE CALIBRATION IS COMPLETE.\n");
        startTimer();

        // Create a whole load of FFTw Plans (wisdom accumulates automatically)
        for (int i = 8; i <= 4194304; i *= 2)
        {
            // Create new buffers and fill
            DSP::cFFTin = new fftw_complex[i];
            DSP::cFFTout = new fftw_complex[i];
            DSP::fconv_FULL_Real_FFT_rdat = new double[i];
            DSP::fconv_FULL_Real_FFT_cdat = new fftw_complex[(i/2)+1];
            for(int j = 0; j < i; j++)
            {
                DSP::fconv_FULL_Real_FFT_rdat[j] = j;
                DSP::cFFTin[j][0] = j;
                DSP::cFFTin[j][1] = j;
                DSP::cFFTout[j][0] = 0.0;
                DSP::cFFTout[j][1] = 0.0;
            }

            // Create a plan for complex FFT.
            // Use the measure flag to get the best possible FFT for this size
            // FFTw "remembers" which FFTs were the fastest during this test. 
            // at the end of the test, the results are saved to disk and re-used
            // upon every initialisation of the DSP Library
            DSP::pCF = fftw_plan_dft_1d
                (i, DSP::cFFTin, DSP::cFFTout, FFTW_FORWARD, FFTW_MEASURE);

            // Destroy the plan
            fftw_destroy_plan(DSP::pCF);

            // Create a plan for real forward FFT
            DSP::pCF = fftw_plan_dft_r2c_1d
                (i, fconv_FULL_Real_FFT_rdat, fconv_FULL_Real_FFT_cdat, FFTW_MEASURE);

            // Destroy the plan
            fftw_destroy_plan(DSP::pCF);

            // Create a plan for real inverse FFT
            DSP::pCF = fftw_plan_dft_c2r_1d
                (i, fconv_FULL_Real_FFT_cdat, fconv_FULL_Real_FFT_rdat, FFTW_MEASURE);

            // Destroy the plan
            fftw_destroy_plan(DSP::pCF);

            // Destroy the buffers. Repeat for each size
            delete [] DSP::cFFTin;
            delete [] DSP::cFFTout;
            delete [] DSP::fconv_FULL_Real_FFT_rdat;
            delete [] DSP::fconv_FULL_Real_FFT_cdat;
        }

        double time = stopTimer();

        char * strOutput;
        strOutput = (char*) malloc (100);
        sprintf(strOutput, "DSP.DLL Calibration complete in %d minutes, %d seconds\n"\
            "Please keep a copy of the DSPDLL.ftw file in the root directory of your application\n"\
            "to avoid re-calibration in the future\n", (int)time/(int)60, (int)time%(int)60);
        AfxMessageBox(strOutput);

        isCalibrated = 1;

        // Save accumulated wisdom
        char * strWisdom = fftw_export_wisdom_to_string();  
        FILE *fftw_wisdomsave = fopen("DSPDLL.ftw", "w");
        fprintf(fftw_wisdomsave, "%s", strWisdom);

        fclose(fftw_wisdomsave);
        DSP::pCF = NULL;
        DSP::cFFTin = NULL;
        DSP::cFFTout = NULL;
        fconv_FULL_Real_FFT_cdat = NULL;
        fconv_FULL_Real_FFT_rdat = NULL;
        free(strOutput);
    }
}
else 
{
    // obtain file size.
    fseek (fftw_wisdom , 0 , SEEK_END);
    long lSize = ftell (fftw_wisdom);
    rewind (fftw_wisdom);

    // allocate memory to contain the whole file.
    char * strWisdom = (char*) malloc (lSize);

    // copy the file into the buffer.
    fread (strWisdom,1,lSize,fftw_wisdom);

    // import the buffer to fftw wisdom
    fftw_import_wisdom_from_string(strWisdom);

    fclose(fftw_wisdom);
    free(strWisdom);

    isCalibrated = 1;

    return;
}
}

Секрет в том, чтобы создать план с использованием флага FFTW_MEASURE, который специально измеряет сотни процедур, чтобы найти самую быструю для вашего конкретного типа БПФ (действительного, комплексного, 1D, 2D) и размера:

DSP::pCF = fftw_plan_dft_1d (i, DSP::cFFTin, DSP::cFFTout, 
   FFTW_FORWARD, FFTW_MEASURE);

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

person Dr. Andrew Burnett-Thompson    schedule 31.12.2011