Puoi avere l'idea eseguendo altre versioni del tuo codice. Considera la possibilità di scrivere esplicitamente i calcoli, invece di utilizzare una funzione nel tuo ciclo
tic
Soln3 = ones(T, N);
for t = 1:T
for n = 1:N
Soln3(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
È ora di calcolare sul mio computer:
Soln1 1.158446 seconds.
Soln2 10.392475 seconds.
Soln3 0.239023 seconds.
Oli 0.010672 seconds.
Ora, mentre la soluzione completamente "vettorializzata" è chiaramente la più veloce, puoi vedere che la definizione di una funzione da chiamare per ogni voce x è un enorme sovraccarico. La semplice scrittura esplicita del calcolo ci ha fatto aumentare la velocità del fattore 5. Immagino che questo dimostri che il compilatore JIT di MATLAB non supporta le funzioni inline . Secondo la risposta di gnovice lì, in realtà è meglio scrivere una funzione normale piuttosto che anonima. Provalo.
Passaggio successivo: rimuovere (vettorializzare) il ciclo interno:
tic
Soln4 = ones(T, N);
for t = 1:T
Soln4(t, :) = 3*x(t, :).^2 + 2*x(t, :) - 1;
end
toc
Soln4 0.053926 seconds.
Un altro fattore di accelerazione 5: c'è qualcosa in quelle affermazioni che dice che dovresti evitare i loop in MATLAB ... O c'è davvero? Dai un'occhiata a questo, allora
tic
Soln5 = ones(T, N);
for n = 1:N
Soln5(:, n) = 3*x(:, n).^2 + 2*x(:, n) - 1;
end
toc
Soln5 0.013875 seconds.
Molto più vicino alla versione "completamente" vettorializzata. Matlab memorizza le matrici a livello di colonna. Dovresti sempre (quando possibile) strutturare i tuoi calcoli in modo che siano vettorializzati "a colonna".
Possiamo tornare a Soln3 ora. L'ordine del ciclo lì è "per riga". Consente di cambiarlo
tic
Soln6 = ones(T, N);
for n = 1:N
for t = 1:T
Soln6(t, n) = 3*x(t, n)^2 + 2*x(t, n) - 1;
end
end
toc
Soln6 0.201661 seconds.
Meglio, ma comunque molto male. Single loop - buono. Doppio ciclo - cattivo. Immagino che MATLAB abbia fatto un lavoro decente per migliorare le prestazioni dei loop, ma l'overhead del loop è ancora presente. Se avessi un lavoro più pesante all'interno, non te ne accorgeresti. Ma poiché questo calcolo è limitato dalla larghezza di banda della memoria, si vede il sovraccarico del ciclo. E vedrai ancora più chiaramente il sovraccarico di chiamare Func1 lì.
Allora che succede con arrayfun? Nessuna funzione in linea neanche lì, quindi un sacco di overhead. Ma perché tanto peggio di un doppio ciclo annidato? In realtà, l'argomento dell'utilizzo di cellfun / arrayfun è stato ampiamente discusso molte volte (ad esempio qui , qui , qui e qui ). Queste funzioni sono semplicemente lente, non è possibile utilizzarle per calcoli a grana fine. Puoi usarli per brevità del codice e conversioni fantasiose tra celle e array. Ma la funzione deve essere più pesante di quella che hai scritto:
tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc
Soln7 0.016786 seconds.
Nota che Soln7 è una cella ora .. a volte è utile. Le prestazioni del codice ora sono abbastanza buone e se hai bisogno di celle come output, non è necessario convertire la matrice dopo aver utilizzato la soluzione completamente vettorizzata.
Allora perché arrayfun è più lento di una semplice struttura ad anello? Sfortunatamente, è impossibile per noi dirlo con certezza, poiché non è disponibile alcun codice sorgente. Puoi solo immaginare che poiché arrayfun è una funzione generica, che gestisce tutti i tipi di diverse strutture di dati e argomenti, non è necessariamente molto veloce in casi semplici, che puoi esprimere direttamente come nidi di loop. Da dove viene l'overhead non possiamo sapere. Il sovraccarico potrebbe essere evitato con una migliore implementazione? Forse no. Ma sfortunatamente l'unica cosa che possiamo fare è studiare la performance per identificare i casi in cui funziona bene e quelli in cui non funziona.
Aggiornamento Poiché il tempo di esecuzione di questo test è breve, per ottenere risultati affidabili ho aggiunto ora un ciclo attorno ai test:
for i=1:1000
% compute
end
Alcune volte indicate di seguito:
Soln5 8.192912 seconds.
Soln7 13.419675 seconds.
Oli 8.089113 seconds.
Vedi che l'arrayfun è ancora cattivo, ma almeno non tre ordini di grandezza peggiore della soluzione vettorizzata. D'altra parte, un singolo ciclo con calcoli a colonna è veloce quanto la versione completamente vettorializzata ... Tutto questo è stato fatto su una singola CPU. I risultati per Soln5 e Soln7 non cambiano se passo a 2 core - In Soln5 dovrei usare un parfor per farlo parallelizzare. Dimentica la velocità ... Soln7 non funziona in parallelo perché arrayfun non funziona in parallelo. La versione vettorializzata di Olis invece:
Oli 5.508085 seconds.