Я разместил это на Matlab Central, но не получил никаких ответов, поэтому я решил, что я должен сделать репост здесь.
Недавно я написал простую подпрограмму в Matlab, которая использует FFT в цикле for; БПФ доминирует в расчетах. Я написал ту же самую процедуру в mex только для экспериментов, и она вызывает библиотеку FFTW 3.3. Оказывается, что для очень больших массивов процедура matlab выполняется быстрее, чем процедура mex (примерно в два раза быстрее). Процедура mex использует мудрость и выполняет те же вычисления FFT. Я также знаю, что Matlab использует FFTW, но возможно ли, что их версия немного более оптимизирована? Я даже использовал флаг FFTW_EXHAUSTIVE, и он все еще примерно вдвое медленнее для больших массивов, чем аналог MATLAB. Кроме того, я убедился, что используемый мной matlab был однопоточным с флагом «-singleCompThread», а используемый мной mex-файл не находился в режиме отладки. Просто любопытно, если это было так — или есть какие-то оптимизации, которые использует Matlab под капотом, о котором я не знаю. Благодарю.
Вот мекс часть:
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. Спасибо
Несколько наблюдений, а не определенный ответ, поскольку я не знаю ни одной из особенностей реализации MATLAB FFT:
Я предполагаю, что вы уже изучили второй вопрос и что число итераций сопоставимо. (Если это не так, это, скорее всего, связано с некоторыми проблемами точности и заслуживает дальнейшего изучения.)
Теперь по поводу сравнения скорости FFT:
Это классический прирост производительности благодаря низкоуровневой и специфичной для архитектуры оптимизации.
Matlab использует FFT от Intel MKL (Math Kernel Library) двоичный файл (mkl.dll). Эти процедуры оптимизированы (на уровне сборки) Intel для процессоров Intel. Кажется, что даже на AMD это дает хороший прирост производительности.
FFTW выглядит как обычная библиотека c, которая не так оптимизирована. Отсюда выигрыш в производительности для использования MKL.
На сайте MathWorks [1] я нашел следующий комментарий:
Примечание о больших степенях 2: для размеров БПФ, являющихся степенями
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 г.)РЕДАКТИРОВАТЬ: Ответ @wakjah на этот ответ точен: FFTW поддерживает разделение реальной и воображаемой памяти через интерфейс Guru. Мое утверждение о взломе, таким образом, не является точным, но может очень хорошо применяться, если интерфейс FFTW Guru не используется — что имеет место по умолчанию, так что будьте осторожны!
Во-первых, извините за опоздание на год. Я не уверен, что увеличение скорости происходит из-за MKL или других оптимизаций. Между FFTW и Matlab есть нечто принципиально иное, и именно так сложные данные хранятся в памяти.
В Matlab действительные и мнимые части комплексного вектора X представляют собой отдельные массивы Xre [i] и Xim [i] (линейные по памяти, эффективные при работе с любым из них по отдельности).
В FFTW действительная и мнимая части по умолчанию чередуются как double [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];
}
Следовательно, для этого требуется 2-кратное выделение памяти + 4-кратное чтение + 4-кратное запись — все размера N. Это требует больших затрат на большие проблемы.
У меня есть догадка, что Mathworks, возможно, взломал код FFTW3, чтобы позволить ему считывать входные векторы непосредственно в формате Matlab, что позволяет избежать всего вышеперечисленного.
В этом сценарии можно выделить только X и использовать X для Y, чтобы запустить FFTW на месте (как fftw_plan_*(N, X, X, ...)
вместо fftw_plan_*(N, X, Y, ...)
), поскольку он будет скопирован в вектор Yre и Yim Matlab, если только приложение не требует / получает преимущества от разделения X и Y.
РЕДАКТИРОВАТЬ: Если посмотреть на потребление памяти в реальном времени при запуске fft2 () в Matlab и моего кода на основе библиотеки fftw3, это показывает, что Matlab выделяет только один дополнительный сложный массив (вывод), тогда как моему коду нужны два таких массива ( *fftw_complex
буфер плюс выход Matlab). Преобразование на месте между форматами Matlab и fftw невозможно, поскольку реальные и воображаемые массивы Matlab не являются последовательными в памяти. Это говорит о том, что Mathworks взломал библиотеку fftw3 для чтения / записи данных в формате Matlab.
Еще одна оптимизация для нескольких вызовов — это постоянное распределение (используя mexMakeMemoryPersistent()
). Я не уверен, что реализация Matlab делает это также.
Приветствия.
постскриптум Напомним, что сложный формат хранения данных Matlab более эффективен для работы с действительными или мнимыми векторами отдельно. В формате FFTW вам придется читать ++ 2 памяти.