Ви можете отримати ідею, застосувавши інші версії коду. Подумайте про те, щоб явно виписати обчислення, замість того, щоб використовувати функцію у своєму циклі
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
Час для обчислення на моєму комп’ютері:
Soln1 1.158446 seconds.
Soln2 10.392475 seconds.
Soln3 0.239023 seconds.
Oli 0.010672 seconds.
Тепер, хоча повністю «векторизоване» рішення явно найшвидше, ви можете бачити, що визначення функції, яку потрібно викликати для кожного запису x, є величезною витратою. Щойно явно виписавши обчислення, ми отримали прискорення фактора 5. Я думаю, це показує, що компілятор MITLABs JIT не підтримує вбудовані функції . Відповідно до відповіді gnovice там, насправді краще написати нормальну функцію, а не анонімну. Спробуй це.
Наступний крок - видаліть (векторизуйте) внутрішню петлю:
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.
Ще одне прискорення фактору 5: є щось у цих твердженнях, яке говорить про те, що слід уникати циклів у MATLAB ... Або це дійсно? Погляньте на це тоді
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.
Набагато ближче до "повністю" векторизованої версії. Matlab зберігає матриці в стовпцях. Ви завжди повинні (коли це можливо) структурувати свої обчислення так, щоб вони були векторизовані "стовпчиком".
Ми можемо повернутися до Soln3 зараз. Порядок циклу є "рядком". Давайте змінити його
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.
Краще, але все-таки дуже погано. Одинарна петля - хороша. Подвійна петля - погано. Я думаю, MATLAB зробив певну гідну роботу над покращенням роботи циклів, але все-таки петля накладних є. Якби у вас була якась важка робота всередині, ви б не помітили. Але оскільки це обчислення обмежено пропускною здатністю пам’яті, ви бачите петлю над головою. І ви будете ще більш ясно побачити накладні витрати виклику FUNC1 там.
То що з arrayfun? Ніякої функції також немає, тому багато накладних витрат. Але чому так гірше, ніж подвійний вкладений цикл? Власне, тема використання Cellfun / arrayfun багато разів обговорювалася (наприклад, тут , тут , тут і тут ). Ці функції просто повільні, ви не можете використовувати їх для таких дрібнозернистих обчислень. Ви можете використовувати їх для стислості коду та фантазійних перетворень між клітинками та масивами. Але функція повинна бути важче, ніж те, що ви написали:
tic
Soln7 = arrayfun(@(a)(3*x(:,a).^2 + 2*x(:,a) - 1), 1:N, 'UniformOutput', false);
toc
Soln7 0.016786 seconds.
Зауважте, що Soln7 - це клітина зараз. Іноді це корисно. Продуктивність коду зараз досить хороша, і якщо вам потрібна клітинка як вихід, вам не потрібно перетворювати матрицю після використання повністю векторного рішення.
То чому ж масив масивів повільніше, ніж проста структура циклу? На жаль, це неможливо сказати точно, оскільки немає вихідного коду. Ви можете лише здогадуватися, що оскільки arrayfun - це загальна функція, яка обробляє всілякі різні структури даних та аргументи, це не обов'язково дуже швидко в простих випадках, що ви можете безпосередньо виразити як петлеві гнізда. Звідки береться наклад, ми не можемо знати. Чи можна уникнути накладних витрат шляхом кращого впровадження? Можливо, не. Але, на жаль, єдине, що ми можемо зробити, - це вивчити ефективність для виявлення випадків, коли це добре працює, і тих, де цього немає.
Оновлення Оскільки час виконання цього тесту короткий, для отримання надійних результатів я додав цикл навколо тестів:
for i=1:1000
% compute
end
Кілька разів наведено нижче:
Soln5 8.192912 seconds.
Soln7 13.419675 seconds.
Oli 8.089113 seconds.
Ви бачите, що масив все ще поганий, але принаймні не на три порядки гірший, ніж векторизований розчин. З іншого боку, одинарний цикл з обчисленнями в стовпцях є таким же швидким, як і повністю векторизована версія ... Це було зроблено на одному процесорі. Результати для Soln5 і Soln7 не змінюються, якщо я перейду на 2 ядра - У Soln5 мені доведеться використовувати парфор, щоб отримати його паралельним. Забудьте про швидкість ... Soln7 не працює паралельно, тому що arrayfun не працює паралельно. З іншого боку, велітаризована версія Olis:
Oli 5.508085 seconds.