2011-01-25 3 views
2

J'espère que quelqu'un pourra revoir mon code ci-dessous et vous donner des conseils pour accélérer la section entre tic et toc. La fonction ci-dessous tente d'effectuer un IFFT plus rapidement que la fonction intégrée de Matlab puisque (1) presque tous les bots de coefficients fft sont nuls (c.-à-d. 10 à 1000 les bins sur 10M à 300M sont non nulles), et (2) seulement le tiers central des résultats de l'IFFT est conservé (le premier et le dernier tiers sont rejetés - donc pas besoin de les calculer en premier lieu).Accélérer les calculs FFT clairsemés

Les variables d'entrée sont les suivants:

fftcoef = complex fft-coef 1D array (10 to 1000 pts long) 
bins = index of fft coefficients corresponding to fftcoef (10 to 1000 pts long) 
DATAn = # of pts in data before zero padding and fft (in range of 10M to 260M) 
FFTn = DATAn + # of pts used to zero pad before taking fft (in range of 16M to 268M) (e.g. FFTn = 2^nextpow2(DATAn)) 

Actuellement, ce code prend quelques ordres de grandeur plus que l'approche de la fonction de Matlab ifft qui calcule l'ensemble du spectre élimine alors 2/3 « s de celui-ci. Par exemple, si les données d'entrée pour fftcoef et les bacs sont 9x1 tableaux (par exemple seulement 9 coefficients fft complexes par bande latérale, 18 pts lorsque l'on considère les deux bandes latérales), et DATAn=32781534, FFTn=33554432 (c.-à-2^25), l'approche IFFT prend 1.6 secondes alors que la boucle ci-dessous prend plus de 700 secondes.

J'ai évité d'utiliser une matrice pour vectoriser la boucle nn car parfois la taille du tableau pour fftcoef et bacs pourrait être jusqu'à 1000 pts long, et une matrice 260Mx1K serait trop grand pour la mémoire à moins qu'il puisse être divisé en quelque sorte .

Un conseil est très apprécié! Merci d'avance.

function fn_fft_v1p0(fftcoef, bins, DATAn, FFTn) 

fftcoef = [fftcoef; (conj(flipud(fftcoef)))];  % fft coefficients 
bins = [bins; (FFTn - flipud(bins) +2)];   % corresponding fft indices for fftcoef array 

ttrend = zeros((round(2*DATAn/3) - round(DATAn/3) + 1), 1); % preallocate 

start = round(DATAn/3)-1; 

tic; 
for nn = start+1 : round(2*DATAn/3) % loop over desired time indices 
    % sum over all fft indices having non-zero coefficients 
    arg = 2*pi*(bins-1)*(nn-1)/FFTn; 
    ttrend(nn-start) = sum(fftcoef.*(cos(arg) + 1j*sin(arg)); 
end 
toc; 

end 
+0

Voir http://www.fftw.org/pruned.html pour une analyse des économies potentielles. Cela ne vaut peut-être pas la peine. – mtrw

+0

Vous regardez les opérations 'length (bins) * (2 * DATAn/3)', mieux que 'DATAn * lg (DATAn)' pour l'approche FFT si '2 * length (bins)/3> lg (DAtan) '(puisque FFTW gère les tailles de transformation non-power-of-2, j'ignore le pad-zero). Pour le cas de 10 bins et de 2^25 points de sortie, c'est '20/3> 25 ', un facteur potentiel de 3 amélioration. Dès que vous arrivez à 75 coeffs FFT, vous avez perdu l'avantage. Et vous devez coder l'algorithme en C et le maintenir. – mtrw

+0

Merci mtrw, j'ai passé en revue le lien ci-dessus il y a quelques jours. Il m'a initialement donné l'espoir, car il dit: "Pour cette raison, je ne recommanderais pas d'envisager une FFT 1d élaguée sauf si vous voulez 1% des sorties ou moins (et/ou si 1% ou moins de vos entrées sont non nulles) " Dans mon cas, moins de 0,00001% de mes entrées (coefficients à IDFT) sont non nulles. Je pense que cela devrait être la principale raison de l'augmentation de la vitesse, plutôt que le facteur de 3 amélioration que vous citez ci-dessus. – ggkmath

Répondre

3

Vous devez garder à l'esprit que Matlab utilise une bibliothèque compilé fft (http://www.fftw.org/) pour ses fonctions fft qui, en plus d'exploitation beaucoup plus rapide puis un script Matlab, il est bien optimisé pour de nombreux cas d'utilisation. Donc, une première étape pourrait être d'écrire votre code dans c/C++ et de le compiler sous forme de fichier mex que vous pouvez utiliser dans Matlab. Cela va certainement accélérer votre code au moins un ordre de grandeur (probablement plus).

En outre, une optimisation simple que vous pouvez faire est en considérant 2 choses:

  1. Vous assumez votre série temporelle est une valeur réelle, vous pouvez donc utiliser la symétrie des coeffs fft.
  2. Votre série temporelle est généralement beaucoup plus longue que votre vecteur coeffs fft, il est donc préférable d'itérer sur les cases au lieu des points temporels (vectorisant ainsi le vecteur le plus long).

Ces deux points sont convertis à la boucle suivante:

nn=(start+1 : round(2*DATAn/3))'; 
ttrend2 = zeros((round(2*DATAn/3) - round(DATAn/3) + 1), 1); 
tic; 
for bn = 1:length(bins) 
    arg = 2*pi*(bins(bn)-1)*(nn-1)/FFTn; 
    ttrend2 = ttrend2 + 2*real(fftcoef(bn) * exp(i*arg)); 
end 
toc; 

Notez que vous devez utiliser cette boucle avant vous étendez bins et fftcoef, puisque la symétrie est déjà pris en compte. Cette boucle prend 8,3 secondes pour s'exécuter avec les paramètres de votre question, alors que cela prend mon ordinateur 141,3 secondes pour fonctionner avec votre code.

+0

Salut Itamar Katz, d'excellentes suggestions. Je reçois des chiffres similaires à ceux que vous montrez ci-dessus pour des améliorations en utilisant votre suggestion # 2 ci-dessus et le code fourni. Je ne suis pas sûr cependant comment coder votre suggestion # 1 ci-dessus (pour profiter de la symétrie). Pouvez-vous décrire plus? – ggkmath

+0

Oh, je vois, vous avez déjà inclus la suggestion 1 en incluant le code "2 * real" ci-dessus, et ensuite je commente simplement les deux lignes originales ci-dessus pour fftcoef = [...] et bins = [...] ] Bon tour, ça coupe le temps en deux. Ensuite, il n'y a aucune raison d'étendre les bacs et fftcoef. – ggkmath

+0

Oui, exactement. La symétrie vient du fait que pour les données réelles, le k'th coeff est le complexe-conjugué du (ek) 'e coeff, donc vous pouvez additionner chaque terme et son conjugué à 2 * réels (...) –

Questions connexes