arrayfun може бути значно повільнішим, ніж явний цикл у matlab. Чому?


105

Розглянемо наступний простий тест швидкості для arrayfun:

T = 4000;
N = 500;
x = randn(T, N);
Func1 = @(a) (3*a^2 + 2*a - 1);

tic
Soln1 = ones(T, N);
for t = 1:T
    for n = 1:N
        Soln1(t, n) = Func1(x(t, n));
    end
end
toc

tic
Soln2 = arrayfun(Func1, x);
toc

На моїй машині (Matlab 2011b на Linux Mint 12) результат цього тесту:

Elapsed time is 1.020689 seconds.
Elapsed time is 9.248388 seconds.

Що за?!? arrayfun, хоча, звичайно, чистіше рішення - це на порядок повільніше. Що тут відбувається?

Крім того, я зробив аналогічний стиль тестування для cellfunі виявив, що це приблизно в 3 рази повільніше, ніж явний цикл. Знову ж таки, цей результат протилежний тому, що я очікував.

Моє запитання: Чому так arrayfunі cellfunнабагато повільніше? І з огляду на це, чи є якісь вагомі причини їх використання (крім того, щоб код виглядав добре)?

Примітка: я говорю про тут стандартну версію arrayfun, а не про версію GPU з панелі інструментів паралельної обробки.

EDIT: Просто для того, щоб було зрозуміло, я знаю, що Func1вище можна векторизувати, як вказував Олі. Я вибрав його лише тому, що він дає просту перевірку швидкості для цілей власне питання.

РЕДАКТИРУВАННЯ: Після пропозиції грангетти я повторно зробив тест feature accel off. Результати:

Elapsed time is 28.183422 seconds.
Elapsed time is 23.525251 seconds.

Іншими словами, схоже, що велика частина різниці полягає в тому, що прискорювач JIT робить набагато кращу роботу, як пришвидшити явний forцикл, ніж це arrayfun. Мені це здається дивним, оскільки arrayfunфактично надає більше інформації, тобто його використання виявляє, що порядок дзвінків Func1не має значення. Крім того, я зазначив, що незалежно від того, увімкнено або вимкнено прискорювач JIT, моя система використовує лише один процесор ...


10
На щастя, "стандартне рішення" залишається найшвидшим на сьогоднішній день: tic; 3 * х. ^ 2 + 2 * х-1; Ток Пройдений час становить 0,030662 секунди.
Олі,

4
@ Олі, мабуть, я мав передбачити, що хтось на це вкаже і скористався функцією, яку неможливо векторизувати :-)
Colin T Bowers

3
Мені було б цікаво подивитися, як змінюється цей термін, коли прискорювач JIT вимкнено. Виконайте команду "Приєднання функції вимкнено", а потім повторно проведіть тест.
grungetta

@grungetta Цікава пропозиція. Я додав результати до питання разом із кількома коментарями.
Colin T Bowers

Відповіді:


101

Ви можете отримати ідею, застосувавши інші версії коду. Подумайте про те, щоб явно виписати обчислення, замість того, щоб використовувати функцію у своєму циклі

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.

9
Чудова відповідь! А посилання на центральний матлаб забезпечують дуже цікаві читання. Велике дякую.
Colin T Bowers

Це приємний аналіз.
H.Muster

І цікаве оновлення! Ця відповідь просто продовжує давати :-)
Colin T Bowers

3
лише невеликий коментар; ще в MATLAB 6.5, cellfunбув реалізований у вигляді файлу MEX (поруч із ним наявний вихідний код C). Це було насправді досить прямо. Звичайно, він підтримує лише застосування однієї з 6 жорстко закодованих функцій (ви не могли передати функцію ручки, лише рядок з одним ім'ям функцій)
Amro

1
arrayfun + функція управління = повільно! уникайте їх у важкому коді.
Івон

-8

Це тому, що !!!!

x = randn(T, N); 

не gpuarrayтип;

Все, що вам потрібно зробити - це

x = randn(T, N,'gpuArray');

2
Думаю, вам потрібно уважніше прочитати питання та чудову відповідь від @angainor. Це не має нічого спільного gpuarray. Це майже напевно, чому ця відповідь спростована.
Colin T Bowers

@Colin - Я погоджуюсь, що прихильник більш ретельний, але відповідь не згадує "gpuArray". Я думаю, що "gpuArray" є хорошим внеском тут (якщо він правильний). Крім того, питання було трохи неохайним: "Що тут відбувається?" , тому я думаю, що це відкрило двері для додаткових методів, таких як векторизація даних та пересилання їх до GPU. Я дозволяю цій відповіді їхати, бо це може принести користь для майбутніх відвідувачів. Мої вибачення, якщо я зробив неправильний дзвінок.
jww

1
Ви також забудете той факт, який gpuarrayпідтримується лише для відеокарт nVidia. Якщо вони не мають такого обладнання, то ваша порада (або її відсутність) безглузда. -1
rayryeng

З іншого боку, gpuarray - це світла шабля векторизованого програмування matlab.
MrIO
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.