arrayfun может быть значительно медленнее, чем явный цикл в matlab. Зачем?

Рассмотрим следующий простой тест скорости для 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 выше может быть векторизован, как указал Оли. Я выбрал его только потому, что он дает простой тест скорости для целей фактического вопроса.

EDIT: После предложения grungetta, я повторно сделал тест с feature accel off . Результаты:

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

Другими словами, казалось бы, большая часть разницы заключается в том, что ускоритель JIT делает намного лучшую работу по ускорению явного цикла цикла, чем arrayfun . Это кажется странным для меня, поскольку arrayfun фактически предоставляет больше информации, т. arrayfun Его использование показывает, что порядок вызовов Func1 не имеет значения. Кроме того, я отметил, что включение или выключение ускорителя JIT, моя система использует только один процессор …

Вы можете получить эту идею, запустив другие версии вашего кода. Рассмотрим явно выписывание вычислений вместо использования функции в вашем цикле

 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. Я предполагаю, что это показывает, что компилятор MATLABs JIT не поддерживает встроенные функции . Согласно ответу гновице, на самом деле лучше написать нормальную функцию, а не анонимную. Попробуй.

Следующий шаг – удалить (векторизовать) внутренний цикл:

 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? Нет никакой функции inlinig, так что много накладных расходов. Но почему гораздо хуже, чем двойной вложенный цикл? На самом деле, тема использования 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 медленнее, чем простая структура цикла? К сожалению, мы не можем точно сказать, так как нет исходного кода. Вы можете только догадываться, что поскольку arrayfun – это функция общего назначения, которая обрабатывает все виды различных структур данных и аргументов, это не обязательно очень быстро в простых случаях, которые вы можете непосредственно выразить как петлевые гнезда. Откуда возникают накладные расходы, мы не можем знать. Можно ли избежать накладных расходов благодаря лучшей реализации? Возможно, нет. Но, к сожалению, единственное, что мы можем сделать, это изучить производительность, чтобы определить случаи, в которых она работает хорошо, и те, где это не так.

Обновление Поскольку время выполнения этого теста невелико, чтобы получить надежные результаты, я добавил теперь цикл вокруг тестов:

 for i=1:1000 % compute end 

Несколько раз ниже:

 Soln5 8.192912 seconds. Soln7 13.419675 seconds. Oli 8.089113 seconds. 

Вы видите, что arrayfun по-прежнему плох, но по крайней мере на три порядка хуже, чем векторное решение. С другой стороны, один цикл с колонизованными вычислениями выполняется так же быстро, как полностью векторизованная версия … Это было сделано на одном процессоре. Результаты для Soln5 и Soln7 не меняются, если я переключаюсь на 2 ядра. В Soln5 мне пришлось бы использовать parfor для его распараллеливания. Забудьте об ускорении … Soln7 не запускается параллельно, потому что arrayfun не работает параллельно. Олизированная версия с другой стороны:

 Oli 5.508085 seconds. 

Это потому что !!!!

 x = randn(T, N); 

не gpuarray типом gpuarray ;

Все, что вам нужно сделать, это

 x = randn(T, N,'gpuArray'); 
  • Как выполнить UPSERT, чтобы я мог использовать как новые, так и старые значения в части обновления
  • Оптимизация кэширования файлов и HTTP2
  • В параллельном коде OpenMP будет ли какая-либо выгода для параллельной работы memset?
  • Почему компиляция C ++ занимает так много времени?
  • Когда использовать StringBuilder в Java
  • C # / F # Сравнение производительности
  • Как оптимизировать vlookup для высокого количества поиска? (альтернативы VLOOKUP)
  • Почему XCHG reg, reg 3 инструкции по микрооперации на современных архитектурах Intel?
  • Производительность HTTP против HTTPS
  • Ошибка производительности XmlSerializer при указании XmlRootAttribute
  • Каков самый быстрый способ обмена значениями в C?
  • Давайте будем гением компьютера.