Как писать векторизованные функции в MATLAB

Я только изучаю MATLAB, и мне трудно понять коэффициенты производительности циклов против векторизованных функций.

в моем предыдущем вопросе:вложенные циклы for очень медленно в MATLAB (предварительно) я понял, что использование векторизованной функции против 4 вложенных циклов сделало 7X раз разница во времени выполнения.

в этом примере вместо зацикливания всех измерений 4-мерного массива и вычисления медиана для каждого вектора было намного чище и быстрее просто вызвать медиану(stack, n), где n означало рабочее измерение медианной функции.

но медиана-это просто очень простой пример и мне просто повезло что это реализован параметр dimension.

мой вопрос в том, что как вы сами пишете функцию, которая работает так же эффективно, как и та, которая имеет этот диапазон измерений реализовано?

например, у вас есть функция my_median_1D который работает только на 1-D векторе и возвращает число.

как вы пишете функцию my_median_nD который действует как медиана MATLAB, принимая n-мерный массив и "рабочий размер"

4 ответов


обновление 2 (чтобы ответить на ваш обновленный вопрос)

MATLAB оптимизирован для работы с массивами. Как только вы привыкнете к этому, на самом деле очень приятно просто ввести одну строку и заставить MATLAB сделать полный 4D-цикл, не беспокоясь об этом. MATLAB часто используется для прототипирования / одноразовых вычислений, поэтому имеет смысл сэкономить время для кодирования человека и отказаться от некоторых C [++ / #] гибкость.

вот почему MATLAB внутренне делает некоторые петли очень хорошо - часто путем кодирования их как скомпилированной функции.

фрагмент кода, который вы даете на самом деле не содержит соответствующей строки кода, которая выполняет основную работу, а именно

% Sort along given dimension
x = sort(x,dim);

другими словами, код, который вы показываете, должен только получить доступ к медианным значениям по их правильному индексу в теперь отсортированном многомерном массиве x (что не занимает много времени). Фактическая работа с доступом ко всем элементам массива была выполнена sort, который является встроенной (т. е. скомпилированной и высоко оптимизированной) функцией.

оригинальный ответ (о том, как построить свои собственные быстрые функции, работающие на массивах)

на самом деле существует довольно много встроенных модулей, которые принимают параметр измерения: min(stack, [], n), max(stack, [], n), mean(stack, n), std(stack, [], n), median(stack,n), sum(stack, n)... вместе с тем, что другие встроенные функции как exp(), sin() автоматически работать над каждым элементом всего массива (т. е. sin(stack) автоматически делает четыре вложенных цикла для вас, если stack is 4D),вы можете создать много функций, которые вам могут понадобиться, просто полагаясь на существующие встроенные.

если этого недостаточно для конкретного случая, вы должны взглянуть на repmat, bsxfun, arrayfun и accumarray которые являются очень мощными функциями для выполнения вещей "путь MATLAB". Просто найдите на SO вопросы (а точнее ответы) используя один of эти, я много узнал о сильных сторонах MATLABs таким образом.

как пример, скажем, вы хотели реализовать п-норма стека вдоль измерения n, вы могли бы написать

function result=pnorm(stack, p, n)
result=sum(stack.^p,n)^(1/p);

... где вы эффективно повторно используете "что-измерение емкости"sum.

обновление

как указывает Макс в комментариях, также посмотрите на Колон оператор (:) который является очень мощным инструментом для выбора элементов из массива (или даже изменения его формы, что в целом делается с reshape).

в общем, взгляните на раздел Работа С Массивами в справке-он содержит repmat et al. упоминалось выше, но и cumsum и некоторые более неясные вспомогательные функции, которые вы должны использовать в качестве строительных блоков.


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

проблема с вложенными циклами-это не сами вложенные циклы. Это операции, которые вы выполняете внутри.

каждый вызов функции (особенно для не встроенной функции) генерирует немного накладных расходов; тем более, если функция выполняет, например, проверку ошибок, которая занимает столько же независимо от размера входных данных. Таким образом, если функция имеет только 1 мс накладных расходов, если вы вызываете его 1000 раз, вы потеряете секунду. Если вы можете вызвать его один раз для выполнения векторизованного расчета, вы платите накладные расходы только один раз.

кроме того,JIT компилятор (pdf) может помочь векторизировать простой for-loops, где вы, например, выполняете только основные арифметические операции. Таким образом, петли с простыми вычислениями в вашем посте ускоряются на много, в то время как петли, вызывающие median нет.


векторизация

в дополнение к тому, что уже было сказано, вы также должны понять, что векторизация включает в себя распараллеливание, т. е. выполнение параллельных операций с данными в отличие от последовательного выполнения (например, инструкции SIMD) и даже использование потоков и мультипроцессоров в некоторых случаях...

MEX-files

теперь, хотя пункт" интерпретированный против скомпилированный " уже был аргументирован, никто не упомянул, что вы можно расширить MATLAB, написав MEX-файлы, которые скомпилированы исполняемыми файлами, написанными на C, которые можно вызвать непосредственно как нормальную функцию изнутри MATLAB. Это позволяет реализовать критически важные для производительности части, используя язык более низкого уровня, такой как C.

колонка-главный заказ

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

например, в вашем предыдущем связанном вопросе вы вычисляли median набора сложенных изображений вдоль некоторого измерения. Теперь порядок, в котором эти размеры упорядочены, сильно влияет на производительность. Иллюстрация:

%# sequence of 10 images
fPath = fullfile(matlabroot,'toolbox','images','imdemos');
files = dir( fullfile(fPath,'AT3_1m4_*.tif') );
files = strcat(fPath,{filesep},{files.name}');      %'

I = imread( files{1} );

%# stacked images along the 1st dimension: [numImages H W RGB]
stack1 = zeros([numel(files) size(I) 3], class(I));
for i=1:numel(files)
    I = imread( files{i} );
    stack1(i,:,:,:) = repmat(I, [1 1 3]);   %# grayscale to RGB
end

%# stacked images along the 4th dimension: [H W RGB numImages]
stack4 = permute(stack1, [2 3 4 1]);

%# compute median image from each of these two stacks
tic, m1 = squeeze( median(stack1,1) ); toc
tic, m4 = median(stack4,4); toc
isequal(m1,m4)

разница во времени была огромной:

Elapsed time is 0.257551 seconds.     %# stack1
Elapsed time is 17.405075 seconds.    %# stack4

в этом случае

M = median(A,dim) returns the median values for elements along the dimension of A specified by scalar dim

но с общей функцией вы можете попробовать разбить свой массив на mat2cell (который может работать с N-D массивами, а не только матрицами) и применять ваш через cellfun. Ниже я буду использовать median в качестве примера, чтобы показать, что вы получаете эквивалентные результаты, но вместо этого вы можете передать ему любую функцию, определенную в m-файле, или анонимную функцию, определенную с помощью @(args) нотации.

>> testarr = [[1 2 3]' [4 5 6]']

testarr =

     1     4
     2     5
     3     6

>> median(testarr,2)

ans =

    2.5000
    3.5000
    4.5000

>> shape = size(testarr)

shape =

     3     2

>> cellfun(@median,mat2cell(testarr,repmat(1,1,shape(1)),[shape(2)]))

ans =

    2.5000
    3.5000
    4.5000